Repository: casey/just (rust)

Issue:
Get color code `just` will use for echoed recipe commands?
Sometimes in shebang recipes it is useful to be able to echo some individual commands before running, the same as `just` echoes commands during a non-shebang recipe.  Prior to https://github.com/casey/just/pull/1670 , simply using bold color when echoing those commands would do the job.  Now, it's no longer known in advance how echoed commands would look.

Could a `just` function to help this please be added?

Perhaps it could wrap a string in color codes same as `just` would wrap an echoed recipe line?  Or, alternatively, perhaps it could take no arguments and return the color code `just` would use for echoing recipe lines?

Thanks :slightly_smiling_face: 

Pull request description:
Start working on `style()` function
Fixes #1702.

Patch:
diff --git a/README.md b/README.md
index ae57d71a..622f4170 100644
--- a/README.md
+++ b/README.md
@@ -1851,6 +1851,24 @@ for details.
   `requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"`
   otherwise.
 
+#### Style
+
+- `style(name)`<sup>master</sup> - Return a named terminal display attribute
+  escape sequence used by `just`. Unlike terminal display attribute escape
+  sequence constants, which contain standard colors and styles, `style(name)`
+  returns an escape sequence used by `just` itself, and can be used to make
+  recipe output match `just`'s own output.
+
+  Recognized values for `name` are `'command'`, for echoed recipe lines,
+  `error`, and `warning`.
+
+  For example, to style an error message:
+
+  ```just
+  scary:
+    @echo '{{ style("error") }}OH NO{{ NORMAL }}'
+  ```
+
 ##### XDG Directories<sup>1.23.0</sup>
 
 These functions return paths to user-specific directories for things like
diff --git a/src/color.rs b/src/color.rs
index 953b8ae9..7742597b 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -35,7 +35,6 @@ impl Color {
     Self::default()
   }
 
-  #[cfg(test)]
   pub(crate) fn always() -> Self {
     Self {
       use_color: UseColor::Always,
diff --git a/src/function.rs b/src/function.rs
index a714a8d0..abeae943 100644
--- a/src/function.rs
+++ b/src/function.rs
@@ -98,6 +98,7 @@ pub(crate) fn get(name: &str) -> Option<Function> {
     "snakecase" => Unary(snakecase),
     "source_directory" => Nullary(source_directory),
     "source_file" => Nullary(source_file),
+    "style" => Unary(style),
     "titlecase" => Unary(titlecase),
     "trim" => Unary(trim),
     "trim_end" => Unary(trim_end),
@@ -623,6 +624,20 @@ fn source_file(context: Context) -> FunctionResult {
     })
 }
 
+fn style(context: Context, s: &str) -> FunctionResult {
+  match s {
+    "command" => Ok(
+      Color::always()
+        .command(context.evaluator.context.config.command_color)
+        .prefix()
+        .to_string(),
+    ),
+    "error" => Ok(Color::always().error().prefix().to_string()),
+    "warning" => Ok(Color::always().warning().prefix().to_string()),
+    _ => Err(format!("unknown style: `{s}`")),
+  }
+}
+
 fn titlecase(_context: Context, s: &str) -> FunctionResult {
   Ok(s.to_title_case())
 }


Test patch:
diff --git a/tests/functions.rs b/tests/functions.rs
index 76964b74..d68b3946 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -1183,3 +1183,78 @@ bar:
     .args(["foo", "bar"])
     .run();
 }
+
+#[test]
+fn style_command_default() {
+  Test::new()
+    .justfile(
+      r#"
+        foo:
+          @echo '{{ style("command") }}foo{{NORMAL}}'
+      "#,
+    )
+    .stdout("\x1b[1mfoo\x1b[0m\n")
+    .run();
+}
+
+#[test]
+fn style_command_non_default() {
+  Test::new()
+    .justfile(
+      r#"
+        foo:
+          @echo '{{ style("command") }}foo{{NORMAL}}'
+      "#,
+    )
+    .args(["--command-color", "red"])
+    .stdout("\x1b[1;31mfoo\x1b[0m\n")
+    .run();
+}
+
+#[test]
+fn style_error() {
+  Test::new()
+    .justfile(
+      r#"
+        foo:
+          @echo '{{ style("error") }}foo{{NORMAL}}'
+      "#,
+    )
+    .stdout("\x1b[1;31mfoo\x1b[0m\n")
+    .run();
+}
+
+#[test]
+fn style_warning() {
+  Test::new()
+    .justfile(
+      r#"
+        foo:
+          @echo '{{ style("warning") }}foo{{NORMAL}}'
+      "#,
+    )
+    .stdout("\x1b[1;33mfoo\x1b[0m\n")
+    .run();
+}
+
+#[test]
+fn style_unknown() {
+  Test::new()
+    .justfile(
+      r#"
+        foo:
+          @echo '{{ style("hippo") }}foo{{NORMAL}}'
+      "#,
+    )
+    .stderr(
+      r#"
+        error: Call to function `style` failed: unknown style: `hippo`
+         ——▶ justfile:2:13
+          │
+        2 │   @echo '{{ style("hippo") }}foo{{NORMAL}}'
+          │             ^^^^^
+      "#,
+    )
+    .status(EXIT_FAILURE)
+    .run();
+}