Repository: casey/just
Pull request #2106 (2024-05-29 07:23:59)

Timestamp format

casey: `.arg("--timestamp-format FORMAT")` passes the single arg `"--timestamp-format FORMAT"`, it needs to be `.arg("--timestamp-format").arg("FORMAT")` or `.args(["--timestamp-format", "FORMAT")`. I just pushed a commit fixing it.

Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 9db21dae..590ba9ea 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -32,7 +32,7 @@ jobs:
 
     - name: Completion Scripts
       run: |
-        ./bin/generate-completions
+        ./bin/update-completions
         git diff --no-ext-diff --quiet --exit-code
         ./tests/completions/just.bash
 
diff --git a/README.md b/README.md
index 72e55c75..b55b431c 100644
--- a/README.md
+++ b/README.md
@@ -3372,6 +3372,43 @@ Since `set windows-shell` has higher precedence than `set shell`, you can use
 `set windows-shell` to pick a shell on Windows, and `set shell` to pick a shell
 for all other platforms.
 
+### Timestamps
+
+`just` can print timestamps before each recipe commands:
+
+```just
+recipe:
+  echo one
+  sleep 2
+  echo two
+```
+
+```
+$ just --timestamp recipe
+[07:28:46] echo one
+one
+[07:28:46] sleep 2
+[07:28:48] echo two
+two
+```
+
+By default, timestamps are formatted as `HH:MM:SS`. The format can be changed
+with `--timestamp-format`:
+
+```
+$ just --timestamp recipe --timestamp-format '%H:%M:%S%.3f %Z'
+[07:32:11:.349 UTC] echo one
+one
+[07:32:11:.350 UTC] sleep 2
+[07:32:13:.352 UTC] echo two
+two
+```
+
+The argument to `--timestamp-format` is a `strftime`-style format string, see
+the
+[`chrono` library docs](https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
+for details.
+
 Changelog
 ---------
 
diff --git a/bin/generate-completions b/bin/update-completions
similarity index 100%
rename from bin/generate-completions
rename to bin/update-completions
diff --git a/completions/just.bash b/completions/just.bash
index 1566f3c4..c5397794 100644
--- a/completions/just.bash
+++ b/completions/just.bash
@@ -30,7 +30,7 @@ _just() {
 
     case "${cmd}" in
         just)
-            opts="-n -f -q -u -v -d -c -e -l -s -E -g -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --groups --man --show --summary --variables --dotenv-filename --dotenv-path --global-justfile --timestamps --help --version [ARGUMENTS]..."
+            opts="-n -f -q -u -v -d -c -e -l -s -E -g -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --groups --man --show --summary --variables --dotenv-filename --dotenv-path --global-justfile --timestamp --timestamp-format --help --version [ARGUMENTS]..."
                 if [[ ${cur} == -* ]] ; then
                     COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                     return 0
@@ -144,6 +144,10 @@ _just() {
                     COMPREPLY=($(compgen -f "${cur}"))
                     return 0
                     ;;
+                --timestamp-format)
+                    COMPREPLY=($(compgen -f "${cur}"))
+                    return 0
+                    ;;
                 *)
                     COMPREPLY=()
                     ;;
diff --git a/completions/just.elvish b/completions/just.elvish
index 43407805..41524dfe 100644
--- a/completions/just.elvish
+++ b/completions/just.elvish
@@ -40,6 +40,7 @@ set edit:completion:arg-completer[just] = {|@words|
             cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
             cand -E 'Load <DOTENV-PATH> as environment file instead of searching for one'
             cand --dotenv-path 'Load <DOTENV-PATH> as environment file instead of searching for one'
+            cand --timestamp-format 'Timestamp format string'
             cand --check 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.'
             cand --yes 'Automatically confirm all recipes.'
             cand -n 'Print what just would do without doing it'
@@ -72,7 +73,7 @@ set edit:completion:arg-completer[just] = {|@words|
             cand --variables 'List names of variables'
             cand -g 'Use global justfile'
             cand --global-justfile 'Use global justfile'
-            cand --timestamps 'Print recipe command timestamps'
+            cand --timestamp 'Print recipe command timestamps'
             cand -h 'Print help'
             cand --help 'Print help'
             cand -V 'Print version'
diff --git a/completions/just.fish b/completions/just.fish
index 50556caa..4a4615d0 100644
--- a/completions/just.fish
+++ b/completions/just.fish
@@ -52,6 +52,7 @@ complete -c just -s l -l list -d 'List available recipes and their arguments' -r
 complete -c just -s s -l show -d 'Show information about <RECIPE>' -r
 complete -c just -l dotenv-filename -d 'Search for environment file named <DOTENV-FILENAME> instead of `.env`' -r
 complete -c just -s E -l dotenv-path -d 'Load <DOTENV-PATH> as environment file instead of searching for one' -r -F
+complete -c just -l timestamp-format -d 'Timestamp format string' -r
 complete -c just -l check -d 'Run `--fmt` in \'check\' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.'
 complete -c just -l yes -d 'Automatically confirm all recipes.'
 complete -c just -s n -l dry-run -d 'Print what just would do without doing it'
@@ -78,6 +79,6 @@ complete -c just -l man -d 'Print man page'
 complete -c just -l summary -d 'List names of available recipes'
 complete -c just -l variables -d 'List names of variables'
 complete -c just -s g -l global-justfile -d 'Use global justfile'
-complete -c just -l timestamps -d 'Print recipe command timestamps'
+complete -c just -l timestamp -d 'Print recipe command timestamps'
 complete -c just -s h -l help -d 'Print help'
 complete -c just -s V -l version -d 'Print version'
diff --git a/completions/just.powershell b/completions/just.powershell
index 66210d3a..88098f04 100644
--- a/completions/just.powershell
+++ b/completions/just.powershell
@@ -43,6 +43,7 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
             [CompletionResult]::new('--dotenv-filename', 'dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named <DOTENV-FILENAME> instead of `.env`')
             [CompletionResult]::new('-E', 'E ', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one')
             [CompletionResult]::new('--dotenv-path', 'dotenv-path', [CompletionResultType]::ParameterName, 'Load <DOTENV-PATH> as environment file instead of searching for one')
+            [CompletionResult]::new('--timestamp-format', 'timestamp-format', [CompletionResultType]::ParameterName, 'Timestamp format string')
             [CompletionResult]::new('--check', 'check', [CompletionResultType]::ParameterName, 'Run `--fmt` in ''check'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.')
             [CompletionResult]::new('--yes', 'yes', [CompletionResultType]::ParameterName, 'Automatically confirm all recipes.')
             [CompletionResult]::new('-n', 'n', [CompletionResultType]::ParameterName, 'Print what just would do without doing it')
@@ -75,7 +76,7 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
             [CompletionResult]::new('--variables', 'variables', [CompletionResultType]::ParameterName, 'List names of variables')
             [CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Use global justfile')
             [CompletionResult]::new('--global-justfile', 'global-justfile', [CompletionResultType]::ParameterName, 'Use global justfile')
-            [CompletionResult]::new('--timestamps', 'timestamps', [CompletionResultType]::ParameterName, 'Print recipe command timestamps')
+            [CompletionResult]::new('--timestamp', 'timestamp', [CompletionResultType]::ParameterName, 'Print recipe command timestamps')
             [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
             [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
             [CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')
diff --git a/completions/just.zsh b/completions/just.zsh
index 7805d869..b8e0a2c8 100644
--- a/completions/just.zsh
+++ b/completions/just.zsh
@@ -38,6 +38,7 @@ _just() {
 '(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of \`.env\`]: : ' \
 '-E+[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
 '--dotenv-path=[Load <DOTENV-PATH> as environment file instead of searching for one]: :_files' \
+'--timestamp-format=[Timestamp format string]: : ' \
 '--check[Run \`--fmt\` in '\''check'\'' mode. Exits with 0 if justfile is formatted correctly. Exits with 1 and prints a diff if formatting is required.]' \
 '--yes[Automatically confirm all recipes.]' \
 '(-q --quiet)-n[Print what just would do without doing it]' \
@@ -70,7 +71,7 @@ _just() {
 '--variables[List names of variables]' \
 '(-f --justfile -d --working-directory)-g[Use global justfile]' \
 '(-f --justfile -d --working-directory)--global-justfile[Use global justfile]' \
-'--timestamps[Print recipe command timestamps]' \
+'--timestamp[Print recipe command timestamps]' \
 '-h[Print help]' \
 '--help[Print help]' \
 '-V[Print version]' \
diff --git a/justfile b/justfile
index 98fc4226..6ad4dd82 100755
--- a/justfile
+++ b/justfile
@@ -164,8 +164,8 @@ watch-readme:
   just render-readme
   fswatch -ro README.adoc | xargs -n1 -I{} just render-readme
 
-generate-completions:
-  ./bin/generate-completions
+update-completions:
+  ./bin/update-completions
 
 test-completions:
   ./tests/completions/just.bash
diff --git a/src/config.rs b/src/config.rs
index c6095292..9f31eec7 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -39,7 +39,8 @@ pub(crate) struct Config {
   pub(crate) shell_args: Option<Vec<String>>,
   pub(crate) shell_command: bool,
   pub(crate) subcommand: Subcommand,
-  pub(crate) timestamps: bool,
+  pub(crate) timestamp: bool,
+  pub(crate) timestamp_format: String,
   pub(crate) unsorted: bool,
   pub(crate) unstable: bool,
   pub(crate) verbosity: Verbosity,
@@ -109,7 +110,8 @@ mod arg {
   pub(crate) const SHELL: &str = "SHELL";
   pub(crate) const SHELL_ARG: &str = "SHELL-ARG";
   pub(crate) const SHELL_COMMAND: &str = "SHELL-COMMAND";
-  pub(crate) const TIMESTAMPS: &str = "TIMESTAMPS";
+  pub(crate) const TIMESTAMP: &str = "TIMESTAMP";
+  pub(crate) const TIMESTAMP_FORMAT: &str = "TIMESTAMP_FORMAT";
   pub(crate) const UNSORTED: &str = "UNSORTED";
   pub(crate) const UNSTABLE: &str = "UNSTABLE";
   pub(crate) const VERBOSE: &str = "VERBOSE";
@@ -486,12 +488,20 @@ impl Config {
       .help("Use global justfile")
     )
     .arg(
-      Arg::new(arg::TIMESTAMPS)
+      Arg::new(arg::TIMESTAMP)
       .action(ArgAction::SetTrue)
-      .long("timestamps")
-      .env("JUST_TIMESTAMPS")
+      .long("timestamp")
+      .env("JUST_TIMESTAMP")
       .help("Print recipe command timestamps")
     )
+    .arg(
+      Arg::new(arg::TIMESTAMP_FORMAT)
+      .action(ArgAction::Set)
+      .long("timestamp-format")
+      .env("JUST_TIMESTAMP_FORMAT")
+      .default_value("%H:%M:%S")
+      .help("Timestamp format string")
+    )
   }
 
   fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> {
@@ -743,7 +753,11 @@ impl Config {
       shell_args,
       shell_command: matches.get_flag(arg::SHELL_COMMAND),
       subcommand,
-      timestamps: matches.get_flag(arg::TIMESTAMPS),
+      timestamp: matches.get_flag(arg::TIMESTAMP),
+      timestamp_format: matches
+        .get_one::<String>(arg::TIMESTAMP_FORMAT)
+        .unwrap()
+        .into(),
       unsorted: matches.get_flag(arg::UNSORTED),
       unstable,
       verbosity,
diff --git a/src/lib.rs b/src/lib.rs
index 959ee56a..10cd3914 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -61,7 +61,6 @@ pub(crate) use {
   },
   {
     camino::Utf8Path,
-    chrono::Utc,
     derivative::Derivative,
     edit_distance::edit_distance,
     lexiclean::Lexiclean,
diff --git a/src/recipe.rs b/src/recipe.rs
index d41bfffd..97dc8476 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -243,10 +243,14 @@ impl<'src, D> Recipe<'src, D> {
           .unwrap_or(config.color)
           .stderr();
 
-        if config.timestamps {
+        if config.timestamp {
           eprint!(
             "[{}] ",
-            color.paint(&Utc::now().format("%H:%M:%S").to_string())
+            color.paint(
+              &chrono::Local::now()
+                .format(&config.timestamp_format)
+                .to_string()
+            ),
           );
         }
 


Test patch:
diff --git a/tests/timestamps.rs b/tests/timestamps.rs
index 8b18b602..f296d146 100644
--- a/tests/timestamps.rs
+++ b/tests/timestamps.rs
@@ -9,8 +9,23 @@ fn print_timestamps() {
         echo 'one'
     ",
     )
-    .arg("--timestamps")
+    .arg("--timestamp")
     .stderr_regex(concat!(r"\[\d\d:\d\d:\d\d\] echo 'one'", "\n"))
     .stdout("one\n")
     .run();
 }
+
+#[test]
+fn print_timestamps_with_format_string() {
+  Test::new()
+    .justfile(
+      "
+     recipe:
+        echo 'one'
+    ",
+    )
+    .args(["--timestamp", "--timestamp-format", "%H:%M:%S.%3f"])
+    .stderr_regex(concat!(r"\[\d\d:\d\d:\d\d\.\d\d\d] echo 'one'", "\n"))
+    .stdout("one\n")
+    .run();
+}