diff --git a/Cargo.lock b/Cargo.lock
index 80a7276d..e96dd65b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -237,7 +237,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn",
]
[[package]]
@@ -329,14 +329,14 @@ dependencies = [
]
[[package]]
-name = "derivative"
-version = "2.2.0"
+name = "derive-where"
+version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25"
dependencies = [
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn",
]
[[package]]
@@ -531,7 +531,7 @@ dependencies = [
"clap_complete",
"clap_mangen",
"ctrlc",
- "derivative",
+ "derive-where",
"dirs",
"dotenvy",
"edit-distance",
@@ -861,7 +861,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn",
]
[[package]]
@@ -930,7 +930,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn",
]
[[package]]
@@ -958,18 +958,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.79",
-]
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
+ "syn",
]
[[package]]
@@ -1038,7 +1027,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn",
]
[[package]]
@@ -1142,7 +1131,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn",
"wasm-bindgen-shared",
]
@@ -1164,7 +1153,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -1396,5 +1385,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.79",
+ "syn",
]
diff --git a/Cargo.toml b/Cargo.toml
index f74367d0..c579e2f8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,7 +26,7 @@ clap = { version = "4.0.0", features = ["derive", "env", "wrap_help"] }
clap_complete = "4.0.0"
clap_mangen = "0.2.20"
ctrlc = { version = "3.1.1", features = ["termination"] }
-derivative = "2.0.0"
+derive-where = "1.2.7"
dirs = "5.0.1"
dotenvy = "0.15"
edit-distance = "2.0.0"
diff --git a/README.md b/README.md
index 6ec0e0f6..622f4170 100644
--- a/README.md
+++ b/README.md
@@ -167,7 +167,7 @@ most Windows users.)
npm
rust-just
- npm install rust-just
+ npm install -g rust-just
PyPI
@@ -1851,6 +1851,24 @@ for details.
`requirement`, e.g., `">=0.1.0"`, returning `"true"` if so and `"false"`
otherwise.
+#### Style
+
+- `style(name)`master - 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 Directories1.23.0
These functions return paths to user-specific directories for things like
@@ -1877,6 +1895,30 @@ A number of constants are predefined:
| `HEX`1.27.0 | `"0123456789abcdef"` |
| `HEXLOWER`1.27.0 | `"0123456789abcdef"` |
| `HEXUPPER`1.27.0 | `"0123456789ABCDEF"` |
+| `CLEAR`master | `"\ec"` |
+| `NORMAL`master | `"\e[0m"` |
+| `BOLD`master | `"\e[1m"` |
+| `ITALIC`master | `"\e[3m"` |
+| `UNDERLINE`master | `"\e[4m"` |
+| `INVERT`master | `"\e[7m"` |
+| `HIDE`master | `"\e[8m"` |
+| `STRIKETHROUGH`master | `"\e[9m"` |
+| `BLACK`master | `"\e[30m"` |
+| `RED`master | `"\e[31m"` |
+| `GREEN`master | `"\e[32m"` |
+| `YELLOW`master | `"\e[33m"` |
+| `BLUE`master | `"\e[34m"` |
+| `MAGENTA`master | `"\e[35m"` |
+| `CYAN`master | `"\e[36m"` |
+| `WHITE`master | `"\e[37m"` |
+| `BG_BLACK`master | `"\e[40m"` |
+| `BG_RED`master | `"\e[41m"` |
+| `BG_GREEN`master | `"\e[42m"` |
+| `BG_YELLOW`master | `"\e[43m"` |
+| `BG_BLUE`master | `"\e[44m"` |
+| `BG_MAGENTA`master | `"\e[45m"` |
+| `BG_CYAN`master | `"\e[46m"` |
+| `BG_WHITE`master | `"\e[47m"` |
```just
@foo:
@@ -1888,9 +1930,29 @@ $ just foo
0123456789abcdef
```
+Constants starting with `\e` are
+[ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code).
+
+`CLEAR` clears the screen, similar to the `clear` command. The rest are of the
+form `\e[Nm`, where `N` is an integer, and set terminal display attributes.
+
+Terminal display attribute escape sequences can be combined, for example text
+weight `BOLD`, text style `STRIKETHROUGH`, foreground color `CYAN`, and
+background color `BG_BLUE`. They should be followed by `NORMAL`, to reset the
+terminal back to normal.
+
+Escape sequences should be quoted, since `[` is treated as a special character
+by some shells.
+
+```just
+@foo:
+ echo '{{BOLD + STRIKETHROUGH + CYAN + BG_BLUE}}Hi!{{NORMAL}}'
+```
+
### Attributes
-Recipes, `mod` statements, and aliases may be annotated with attributes that change their behavior.
+Recipes, `mod` statements, and aliases may be annotated with attributes that
+change their behavior.
| Name | Type | Description |
|------|------|-------------|
@@ -2711,8 +2773,9 @@ scripts interpreted by `COMMAND`. This avoids some of the issues with shebang
recipes, such as the use of `cygpath` on Windows, the need to use
`/usr/bin/env`, and inconsistences in shebang line splitting across Unix OSs.
-Recipes with an empty `[script]` attribute are executed with the value of
-`set script-interpreter := […]`1.33.0 , defaulting to `sh -eu`.
+Recipes with an empty `[script]` attribute are executed with the value of `set
+script-interpreter := […]`1.33.0 , defaulting to `sh -eu`, and *not*
+the value of `set shell`.
The body of the recipe is evaluated, written to disk in the temporary
directory, and run by passing its path as an argument to `COMMAND`.
diff --git a/README.中文.md b/README.中文.md
index 936fb183..f586f78e 100644
--- a/README.中文.md
+++ b/README.中文.md
@@ -220,6 +220,22 @@ list:
asdf install just <version>
+
+ Various
+ PyPI
+ rust-just
+
+ pipx install rust-just
+
+
+
+ Various
+ npm
+ rust-just
+
+ npm install -g rust-just
+
+
Debian and Ubuntu derivatives
MPR
diff --git a/justfile b/justfile
index 5ab15362..e975d506 100755
--- a/justfile
+++ b/justfile
@@ -169,6 +169,10 @@ build-book:
mdbook build book/en
mdbook build book/zh
+[group: 'dev']
+print-readme-constants-table:
+ cargo test constants::tests::readme_table -- --nocapture
+
# run all polyglot recipes
[group: 'demo']
polyglot: _python _js _perl _sh _ruby
diff --git a/src/color.rs b/src/color.rs
index ba437eff..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,
@@ -67,7 +66,7 @@ impl Color {
}
pub(crate) fn doc_backtick(self) -> Self {
- self.restyle(Style::new().fg(White).on(Black))
+ self.restyle(Style::new().fg(Cyan))
}
pub(crate) fn error(self) -> Self {
diff --git a/src/constants.rs b/src/constants.rs
index e9007ea7..5dd17681 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -1,15 +1,58 @@
use super::*;
-pub(crate) fn constants() -> &'static HashMap<&'static str, &'static str> {
- static CONSTANTS: OnceLock> = OnceLock::new();
+const CONSTANTS: [(&str, &str, &str); 27] = [
+ ("HEX", "0123456789abcdef", "1.27.0"),
+ ("HEXLOWER", "0123456789abcdef", "1.27.0"),
+ ("HEXUPPER", "0123456789ABCDEF", "1.27.0"),
+ ("CLEAR", "\x1bc", "master"),
+ ("NORMAL", "\x1b[0m", "master"),
+ ("BOLD", "\x1b[1m", "master"),
+ ("ITALIC", "\x1b[3m", "master"),
+ ("UNDERLINE", "\x1b[4m", "master"),
+ ("INVERT", "\x1b[7m", "master"),
+ ("HIDE", "\x1b[8m", "master"),
+ ("STRIKETHROUGH", "\x1b[9m", "master"),
+ ("BLACK", "\x1b[30m", "master"),
+ ("RED", "\x1b[31m", "master"),
+ ("GREEN", "\x1b[32m", "master"),
+ ("YELLOW", "\x1b[33m", "master"),
+ ("BLUE", "\x1b[34m", "master"),
+ ("MAGENTA", "\x1b[35m", "master"),
+ ("CYAN", "\x1b[36m", "master"),
+ ("WHITE", "\x1b[37m", "master"),
+ ("BG_BLACK", "\x1b[40m", "master"),
+ ("BG_RED", "\x1b[41m", "master"),
+ ("BG_GREEN", "\x1b[42m", "master"),
+ ("BG_YELLOW", "\x1b[43m", "master"),
+ ("BG_BLUE", "\x1b[44m", "master"),
+ ("BG_MAGENTA", "\x1b[45m", "master"),
+ ("BG_CYAN", "\x1b[46m", "master"),
+ ("BG_WHITE", "\x1b[47m", "master"),
+];
- CONSTANTS.get_or_init(|| {
- vec![
- ("HEX", "0123456789abcdef"),
- ("HEXLOWER", "0123456789abcdef"),
- ("HEXUPPER", "0123456789ABCDEF"),
- ]
- .into_iter()
- .collect()
+pub(crate) fn constants() -> &'static HashMap<&'static str, &'static str> {
+ static MAP: OnceLock> = OnceLock::new();
+ MAP.get_or_init(|| {
+ CONSTANTS
+ .into_iter()
+ .map(|(name, value, _version)| (name, value))
+ .collect()
})
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn readme_table() {
+ println!("| Name | Value |");
+ println!("|------|-------------|");
+ for (name, value, version) in CONSTANTS {
+ println!(
+ "| `{name}`{version} | `\"{}\"` |",
+ value.replace('\x1b', "\\e")
+ );
+ }
+ }
+}
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 {
"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())
}
diff --git a/src/lib.rs b/src/lib.rs
index 75e3332b..799cc378 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -34,7 +34,7 @@ pub(crate) use {
},
camino::Utf8Path,
clap::ValueEnum,
- derivative::Derivative,
+ derive_where::derive_where,
edit_distance::edit_distance,
lexiclean::Lexiclean,
libc::EXIT_FAILURE,
diff --git a/src/node.rs b/src/node.rs
index f8788c4d..6bfb042a 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -197,7 +197,7 @@ impl<'src> Node<'src> for UnresolvedRecipe<'src> {
t.push_mut("quiet");
}
- if let Some(doc) = self.doc {
+ if let Some(doc) = &self.doc {
t.push_mut(Tree::string(doc));
}
diff --git a/src/parser.rs b/src/parser.rs
index 00246bf3..229f5aea 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -936,6 +936,14 @@ impl<'run, 'src> Parser<'run, 'src> {
let private = name.lexeme().starts_with('_') || attributes.contains(&Attribute::Private);
+ let mut doc = doc.map(ToOwned::to_owned);
+
+ for attribute in &attributes {
+ if let Attribute::Doc(attribute_doc) = attribute {
+ doc = attribute_doc.as_ref().map(|doc| doc.cooked.clone());
+ }
+ }
+
Ok(Recipe {
shebang: shebang || script,
attributes,
diff --git a/src/recipe.rs b/src/recipe.rs
index 05516225..d57bc938 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -22,7 +22,7 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
pub(crate) attributes: BTreeSet>,
pub(crate) body: Vec>,
pub(crate) dependencies: Vec,
- pub(crate) doc: Option<&'src str>,
+ pub(crate) doc: Option,
#[serde(skip)]
pub(crate) file_depth: u32,
#[serde(skip)]
@@ -465,7 +465,8 @@ impl<'src, D> Recipe<'src, D> {
return doc.as_ref().map(|s| s.cooked.as_ref());
}
}
- self.doc
+
+ self.doc.as_deref()
}
pub(crate) fn subsequents(&self) -> impl Iterator- {
@@ -475,8 +476,14 @@ impl<'src, D> Recipe<'src, D> {
impl<'src, D: Display> ColorDisplay for Recipe<'src, D> {
fn fmt(&self, f: &mut Formatter, color: Color) -> fmt::Result {
- if let Some(doc) = self.doc {
- writeln!(f, "# {doc}")?;
+ if !self
+ .attributes
+ .iter()
+ .any(|attribute| matches!(attribute, Attribute::Doc(_)))
+ {
+ if let Some(doc) = &self.doc {
+ writeln!(f, "# {doc}")?;
+ }
}
for attribute in &self.attributes {
diff --git a/src/thunk.rs b/src/thunk.rs
index 82668998..8c7ddfa8 100644
--- a/src/thunk.rs
+++ b/src/thunk.rs
@@ -1,46 +1,46 @@
use super::*;
-#[derive(Derivative)]
-#[derivative(Debug, Clone, PartialEq = "feature_allow_slow_enum")]
+#[derive_where(Debug, PartialEq)]
+#[derive(Clone)]
pub(crate) enum Thunk<'src> {
Nullary {
name: Name<'src>,
- #[derivative(Debug = "ignore", PartialEq = "ignore")]
+ #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context) -> FunctionResult,
},
Unary {
name: Name<'src>,
- #[derivative(Debug = "ignore", PartialEq = "ignore")]
+ #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str) -> FunctionResult,
arg: Box
>,
},
UnaryOpt {
name: Name<'src>,
- #[derivative(Debug = "ignore", PartialEq = "ignore")]
+ #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, Option<&str>) -> FunctionResult,
args: (Box>, Box>>),
},
UnaryPlus {
name: Name<'src>,
- #[derivative(Debug = "ignore", PartialEq = "ignore")]
+ #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &[String]) -> FunctionResult,
args: (Box>, Vec>),
},
Binary {
name: Name<'src>,
- #[derivative(Debug = "ignore", PartialEq = "ignore")]
+ #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &str) -> FunctionResult,
args: [Box>; 2],
},
BinaryPlus {
name: Name<'src>,
- #[derivative(Debug = "ignore", PartialEq = "ignore")]
+ #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &str, &[String]) -> FunctionResult,
args: ([Box>; 2], Vec>),
},
Ternary {
name: Name<'src>,
- #[derivative(Debug = "ignore", PartialEq = "ignore")]
+ #[derive_where(skip(Debug, EqHashOrd))]
function: fn(function::Context, &str, &str, &str) -> FunctionResult,
args: [Box>; 3],
},
diff --git a/tests/fmt.rs b/tests/fmt.rs
index 013c0a27..110b6e59 100644
--- a/tests/fmt.rs
+++ b/tests/fmt.rs
@@ -1096,3 +1096,27 @@ fn multi_argument_attribute() {
)
.run();
}
+
+#[test]
+fn doc_attribute_suppresses_comment() {
+ Test::new()
+ .justfile(
+ "
+ set unstable
+
+ # COMMENT
+ [doc('ATTRIBUTE')]
+ foo:
+ ",
+ )
+ .arg("--dump")
+ .stdout(
+ "
+ set unstable := true
+
+ [doc('ATTRIBUTE')]
+ foo:
+ ",
+ )
+ .run();
+}
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();
+}
diff --git a/tests/json.rs b/tests/json.rs
index 3819c040..d3ea7d56 100644
--- a/tests/json.rs
+++ b/tests/json.rs
@@ -1429,3 +1429,58 @@ fn recipes_with_private_attribute_are_private() {
}),
);
}
+
+#[test]
+fn doc_attribute_overrides_comment() {
+ case(
+ "
+ # COMMENT
+ [doc('ATTRIBUTE')]
+ foo:
+ ",
+ json!({
+ "aliases": {},
+ "assignments": {},
+ "first": "foo",
+ "doc": null,
+ "groups": [],
+ "modules": {},
+ "recipes": {
+ "foo": {
+ "attributes": [{"doc": "ATTRIBUTE"}],
+ "body": [],
+ "dependencies": [],
+ "doc": "ATTRIBUTE",
+ "name": "foo",
+ "namepath": "foo",
+ "parameters": [],
+ "priors": 0,
+ "private": false,
+ "quiet": false,
+ "shebang": false,
+ }
+ },
+ "settings": {
+ "allow_duplicate_recipes": false,
+ "allow_duplicate_variables": false,
+ "dotenv_filename": null,
+ "dotenv_load": false,
+ "dotenv_path": null,
+ "dotenv_required": false,
+ "export": false,
+ "fallback": false,
+ "ignore_comments": false,
+ "positional_arguments": false,
+ "quiet": false,
+ "shell": null,
+ "tempdir" : null,
+ "unstable": false,
+ "windows_powershell": false,
+ "windows_shell": null,
+ "working_directory" : null,
+ },
+ "unexports": [],
+ "warnings": [],
+ }),
+ );
+}
diff --git a/tests/list.rs b/tests/list.rs
index f7ea7528..53a1c737 100644
--- a/tests/list.rs
+++ b/tests/list.rs
@@ -452,7 +452,7 @@ fn backticks_highlighted() {
.stdout(
"
Available recipes:
- recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[40;37m``\u{1b}[0m\u{1b}[34m \u{1b}[0m\u{1b}[40;37m`with backticks`\u{1b}[0m\u{1b}[34m and trailing text\u{1b}[0m
+ recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[36m``\u{1b}[0m\u{1b}[34m \u{1b}[0m\u{1b}[36m`with backticks`\u{1b}[0m\u{1b}[34m and trailing text\u{1b}[0m
")
.run();
}
@@ -470,7 +470,7 @@ fn unclosed_backticks() {
.stdout(
"
Available recipes:
- recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[40;37m`with unclosed backick\u{1b}[0m
+ recipe \u{1b}[34m#\u{1b}[0m \u{1b}[34mComment \u{1b}[0m\u{1b}[36m`with unclosed backick\u{1b}[0m
")
.run();
}