Repository: casey/just
Pull request #2084 (2024-05-25 07:39:31)

Print timestamps when --timestamps is passed
Print timestamps when the --timestamps flag is passed

https://github.com/casey/just/issues/2078

neunenak: Thought I'd take a crack at https://github.com/casey/just/issues/2078 . 

```just


recipe:
    echo "ONE"
    sleep 10
    echo "TWO"
```

```
$ just --timestamps
[2024-05-25T07:41:28.376232233+00:00] echo "ONE"
ONE
[2024-05-25T07:41:28.377825276+00:00] sleep 10
[2024-05-25T07:41:38.380184613+00:00] echo "TWO"
TWO
```

A couple of questions:

* this feature doesn't seem all that useful unless there's a way to configure the format/time zone/etc. of the printed timestamp. The above is just printing the default `chrono` ISO8601 formatted timestamp. I'm not sure what the best way to configure this is - another option `--timestamp-format <format-string>` that gets passed directly into `chrono`? Have `--timestamp` accept an optional format string argument? something else?

* this could take the short argument `-t`, but I'm not sure if it's worth burning that single character on this as opposed to some other more useful option that could start with t
casey: > * this feature doesn't seem all that useful unless there's a way to configure the format/time zone/etc. of the printed timestamp. The above is just printing the default `chrono` ISO8601 formatted timestamp. I'm not sure what the best way to configure this is - another option `--timestamp-format <format-string>` that gets passed directly into `chrono`? Have `--timestamp` accept an optional format string argument? something else?

Yah, agreed. The default formatting is super long. From the original issue, it sounds like the main thing that they wanted to know was how long each command took. We could consider using a default format that only prints the time, like `16:55`, which would allow figuring out how long things took, and letting them pass `--timestamp-format` if they want to customize it. I don't think `--timestamp` can have an optional format argument, otherwise `just --timestamp RECIPE` would swallow the recipe.

I'd add `--timestamp` first and do `--timestamp-format` in a follow-up PR.

> * this could take the short argument `-t`, but I'm not sure if it's worth burning that single character on this as opposed to some other more useful option that could start with t

I'm always very stingy about short arguments, so I'd leave it out and wait for someone to complain that they use it a lot.

`--timestamp` should also be settable with an env var, i.e., `JUST_TIMESTAMP`.
casey: I removed subseconds from timestamps. My hunch is that this will be noisier for not much benefit. Timestamp output is human readable, so not covered by backwards compatibility guarantees, so we can add it back later if people want it.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 6df5e7d9..5384724c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -11,6 +11,21 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
 [[package]]
 name = "ansi_term"
 version = "0.12.1"
@@ -92,6 +107,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -139,6 +160,12 @@ dependencies = [
  "regex-automata 0.1.10",
 ]
 
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
 [[package]]
 name = "camino"
 version = "1.1.7"
@@ -163,6 +190,20 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
 
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-targets 0.52.5",
+]
+
 [[package]]
 name = "clap"
 version = "2.34.0"
@@ -237,6 +278,12 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
 
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
+
 [[package]]
 name = "cpufeatures"
 version = "0.2.12"
@@ -500,6 +547,29 @@ version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
 [[package]]
 name = "is_terminal_polyfill"
 version = "1.70.0"
@@ -512,6 +582,15 @@ version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
 
+[[package]]
+name = "js-sys"
+version = "0.3.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+dependencies = [
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "just"
 version = "1.27.0"
@@ -519,6 +598,7 @@ dependencies = [
  "ansi_term",
  "blake3",
  "camino",
+ "chrono",
  "clap 4.5.4",
  "clap_complete",
  "clap_mangen",
@@ -623,6 +703,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "num_cpus"
 version = "1.16.0"
@@ -633,6 +722,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
 [[package]]
 name = "option-ext"
 version = "0.2.0"
@@ -1173,6 +1268,60 @@ version = "0.11.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.66",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.66",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+
 [[package]]
 name = "which"
 version = "6.0.1"
@@ -1207,6 +1356,15 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
 
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets 0.52.5",
+]
+
 [[package]]
 name = "windows-sys"
 version = "0.48.0"
diff --git a/Cargo.toml b/Cargo.toml
index 809f51b9..4600384c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -21,6 +21,7 @@ members = [".", "crates/*"]
 ansi_term = "0.12.0"
 blake3 = { version = "1.5.0", features = ["rayon", "mmap"] }
 camino = "1.0.4"
+chrono = "0.4.38"
 clap = { version = "4.0.0", features = ["env", "wrap_help"] }
 clap_complete = "4.0.0"
 clap_mangen = "0.2.20"
diff --git a/bin/generate-completions b/bin/generate-completions
index 9b5a3578..b10efff9 100755
--- a/bin/generate-completions
+++ b/bin/generate-completions
@@ -2,10 +2,12 @@
 
 set -euxo pipefail
 
+cargo build
+
 for script in completions/*; do
   shell=${script##*.}
   if [ $shell == nu ]; then
     continue
   fi
-  cargo run -- --completions $shell > $script
+  ./target/debug/just --completions $shell > $script
 done
diff --git a/completions/just.bash b/completions/just.bash
index 929746bd..165f7079 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 --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 --timestamps --help --version [ARGUMENTS]..."
                 if [[ ${cur} == -* ]] ; then
                     COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
                     return 0
diff --git a/completions/just.elvish b/completions/just.elvish
index 726d52ea..7c58fbbc 100644
--- a/completions/just.elvish
+++ b/completions/just.elvish
@@ -72,6 +72,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 -h 'Print help'
             cand --help 'Print help'
             cand -V 'Print version'
diff --git a/completions/just.fish b/completions/just.fish
index 1caf1afa..7d0b87c8 100644
--- a/completions/just.fish
+++ b/completions/just.fish
@@ -78,5 +78,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 -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 04f8137a..01648cd6 100644
--- a/completions/just.powershell
+++ b/completions/just.powershell
@@ -75,6 +75,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('-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 4a7ddc07..a30496f0 100644
--- a/completions/just.zsh
+++ b/completions/just.zsh
@@ -70,6 +70,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]' \
 '-h[Print help]' \
 '--help[Print help]' \
 '-V[Print version]' \
diff --git a/src/config.rs b/src/config.rs
index 064c94f4..060131e9 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -39,6 +39,7 @@ 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) unsorted: bool,
   pub(crate) unstable: bool,
   pub(crate) verbosity: Verbosity,
@@ -109,6 +110,7 @@ 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 UNSORTED: &str = "UNSORTED";
   pub(crate) const UNSTABLE: &str = "UNSTABLE";
   pub(crate) const VERBOSE: &str = "VERBOSE";
@@ -482,6 +484,13 @@ impl Config {
       .conflicts_with(arg::WORKING_DIRECTORY)
       .help("Use global justfile")
     )
+    .arg(
+      Arg::new(arg::TIMESTAMPS)
+      .action(ArgAction::SetTrue)
+      .long("timestamps")
+      .env("JUST_TIMESTAMPS")
+      .help("Print recipe command timestamps")
+    )
   }
 
   fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> {
@@ -723,6 +732,7 @@ impl Config {
       shell_args,
       shell_command: matches.get_flag(arg::SHELL_COMMAND),
       subcommand,
+      timestamps: matches.get_flag(arg::TIMESTAMPS),
       unsorted: matches.get_flag(arg::UNSORTED),
       unstable,
       verbosity,
diff --git a/src/lib.rs b/src/lib.rs
index 115437c9..25e79efc 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -60,6 +60,7 @@ 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 446c7bbe..d41bfffd 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -237,12 +237,20 @@ impl<'src, D> Recipe<'src, D> {
           || (context.settings.quiet && !self.no_quiet())
           || config.verbosity.quiet())
       {
-        let color = if config.highlight {
-          config.color.command(config.command_color)
-        } else {
-          config.color
-        };
-        eprintln!("{}", color.stderr().paint(command));
+        let color = config
+          .highlight
+          .then(|| config.color.command(config.command_color))
+          .unwrap_or(config.color)
+          .stderr();
+
+        if config.timestamps {
+          eprint!(
+            "[{}] ",
+            color.paint(&Utc::now().format("%H:%M:%S").to_string())
+          );
+        }
+
+        eprintln!("{}", color.paint(command));
       }
 
       if config.dry_run {


Test patch:
diff --git a/tests/lib.rs b/tests/lib.rs
index 00cbc956..cceda57b 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -102,6 +102,7 @@ mod string;
 mod subsequents;
 mod summary;
 mod tempdir;
+mod timestamps;
 mod undefined_variables;
 mod unstable;
 #[cfg(target_family = "windows")]
diff --git a/tests/timestamps.rs b/tests/timestamps.rs
new file mode 100644
index 00000000..8b18b602
--- /dev/null
+++ b/tests/timestamps.rs
@@ -0,0 +1,16 @@
+use super::*;
+
+#[test]
+fn print_timestamps() {
+  Test::new()
+    .justfile(
+      "
+     recipe:
+        echo 'one'
+    ",
+    )
+    .arg("--timestamps")
+    .stderr_regex(concat!(r"\[\d\d:\d\d:\d\d\] echo 'one'", "\n"))
+    .stdout("one\n")
+    .run();
+}