diff --git a/README.md b/README.md
index 6ec0e0f613..b318a5dbcb 100644
--- a/README.md
+++ b/README.md
@@ -1877,6 +1877,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 +1912,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 |
|------|------|-------------|
diff --git a/justfile b/justfile
index 5ab153626e..e975d506fb 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/constants.rs b/src/constants.rs
index e9007ea771..5dd17681bf 100644
--- a/src/constants.rs
+++ b/src/constants.rs
@@ -1,15 +1,58 @@
use super::*;
+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"),
+];
+
pub(crate) fn constants() -> &'static HashMap<&'static str, &'static str> {
- static CONSTANTS: OnceLock> = OnceLock::new();
-
- CONSTANTS.get_or_init(|| {
- vec![
- ("HEX", "0123456789abcdef"),
- ("HEXLOWER", "0123456789abcdef"),
- ("HEXUPPER", "0123456789ABCDEF"),
- ]
- .into_iter()
- .collect()
+ 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")
+ );
+ }
+ }
+}