diff --git a/nodejs/README.md b/nodejs/README.md index a03417ef..f7e97373 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -161,6 +161,7 @@ Please look at the coding style and work with it, not against it ;) ## Release History +* 3.1.1 - Fixed #58 gradient color bug, added `gray` to gradient colors * 3.1.0 - Added support for -V flag fallback * 3.0.0 - Added rust library port, aligned APIs, added hex background colors, fixed minor alignment bugs, updated license from GPLv2 to GPLv3 * 2.10.1 - bumped dependencies diff --git a/nodejs/package.json b/nodejs/package.json index b9c0877b..8b398837 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,7 +1,7 @@ { "name": "cfonts", "description": "Sexy ANSI fonts for the console", - "version": "3.1.0", + "version": "3.1.1", "homepage": "https://github.com/dominikwilkowski/cfonts", "author": { "name": "Dominik Wilkowski", diff --git a/nodejs/src/Color.js b/nodejs/src/Color.js index c247d0ef..7571522f 100644 --- a/nodejs/src/Color.js +++ b/nodejs/src/Color.js @@ -382,7 +382,7 @@ function ansi_2562ansi_16(code, bg = false) { (code >= 132 && code <= 135) || (code >= 139 && code <= 141) || (code >= 145 && code <= 147) || - (code >= 196 && code <= 171) || + (code >= 169 && code <= 171) || (code >= 175 && code <= 177) ) { ansi_16_code = 35; @@ -393,7 +393,7 @@ function ansi_2562ansi_16(code, bg = false) { if ( (code >= 164 && code <= 165) || (code >= 182 && code <= 183) || - (code >= 200 && code <= 200) || + (code >= 200 && code <= 201) || (code >= 218 && code <= 219) ) { ansi_16_code = 95; diff --git a/nodejs/src/constants.js b/nodejs/src/constants.js index 4c1e2698..9a891f51 100644 --- a/nodejs/src/constants.js +++ b/nodejs/src/constants.js @@ -134,6 +134,8 @@ const GRADIENTCOLORS = { magenta: 'magenta', cyan: 'cyan', white: 'white', + gray: 'gray', + grey: 'grey', }; const GRADIENTS = { diff --git a/nodejs/test/unit/Color.spec.js b/nodejs/test/unit/Color.spec.js index 72e8ee21..9a1be53d 100644 --- a/nodejs/test/unit/Color.spec.js +++ b/nodejs/test/unit/Color.spec.js @@ -205,6 +205,11 @@ test(`Color - ansi_2562ansi_16 - Convert RGB to ANSI256 correctly`, () => { expect(inspect(ansi_2562ansi_16(240))).toEqual(inspect('\x1B[90m')); expect(inspect(ansi_2562ansi_16(247))).toEqual(inspect('\x1B[37m')); + for (let i = 0; i <= 255; i++) { + expect(`${inspect(ansi_2562ansi_16(i)) !== "'\\x1B[undefinedm'"}${i}`).toEqual(`true${i}`); + expect(`${inspect(ansi_2562ansi_16(i)) !== "'\\x1B[NaNdm'"}${i}`).toEqual(`true${i}`); + } + expect(inspect(ansi_2562ansi_16(52, true))).toEqual(inspect('\x1B[41m')); }); diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 5435632c..84c305fa 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -46,7 +46,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfonts" -version = "1.0.4" +version = "1.1.0" dependencies = [ "assert_cmd", "enable-ansi-support", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index d5ba9f38..d5b8c602 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cfonts" -version = "1.0.4" +version = "1.1.0" edition = "2021" authors = ["Dominik Wilkowski "] license = "GPL-3.0-or-later" diff --git a/rust/README.md b/rust/README.md index 9dbe52ab..cd03fc4b 100644 --- a/rust/README.md +++ b/rust/README.md @@ -151,6 +151,7 @@ We also have an [end-to-end test script](https://github.com/dominikwilkowski/cfo ## Release History +* 1.1.0 - Added `yellow` to the gradient colors * 1.0.4 - Fixed NO_COLOR not being respected in help, fixed color conversion rgb to ansi_256 * 1.0.3 - Fixed NO_COLOR support when run without FORCE_COLOR * 1.0.2 - Fixed help and version flags in first position diff --git a/rust/src/args.rs b/rust/src/args.rs index ec62d943..962342e9 100644 --- a/rust/src/args.rs +++ b/rust/src/args.rs @@ -410,6 +410,7 @@ pub fn parse(args: Vec) -> Result { "red" => Ok(String::from("#ff0000")), "green" => Ok(String::from("#00ff00")), "blue" => Ok(String::from("#0000ff")), + "yellow" => Ok(String::from("#ffff00")), "magenta" => Ok(String::from("#ff00ff")), "cyan" => Ok(String::from("#00ffff")), "white" => Ok(String::from("#ffffff")), @@ -419,7 +420,7 @@ pub fn parse(args: Vec) -> Result { // parsing hex round trip to make sure it's in a good format Ok(rgb2hex(&hex2rgb(unknown, &options), &options)) } else { - Err(format!("The gradient color \"{}\" is not supported.\nAllowed options are: black, red, green, blue, magenta, cyan, white, gray, grey", color(unknown, Colors::Green))) + Err(format!("The gradient color \"{}\" is not supported.\nAllowed options are: black, red, green, blue, yellow, magenta, cyan, white, gray, grey", color(unknown, Colors::Green))) } } }).collect::,String>>()?; diff --git a/rust/src/cli.rs b/rust/src/cli.rs index 473d2b1f..283a31e8 100644 --- a/rust/src/cli.rs +++ b/rust/src/cli.rs @@ -1,5 +1,6 @@ //! The contents of this module is all about cli specific functionality use std::env; +use std::fmt::Write as _; use crate::color::{color, get_term_color_support, TermColorSupport}; use crate::config::{Align, BgColors, Colors, Env, Fonts, OptionType, Options, CLIOPTIONS}; @@ -61,19 +62,19 @@ pub fn help(options: &Options) -> String { output += "This is a tool for sexy fonts in the console. Give your cli some love.\n"; output += "\n"; output += "Usage: cfonts \"\" [option1] [option2] , [option3]\n"; - output += - &format!("Example: {}$ cfonts \"sexy font\" -f chrome -a center -c red,green,gray{}\n", bold_start, bold_end); + let _ = + writeln!(output, "Example: {}$ cfonts \"sexy font\" -f chrome -a center -c red,green,gray{}", bold_start, bold_end); output += "\n"; output += "Options:\n"; for option in CLIOPTIONS { - output += &format!("\n{}{}, {}", bold_start, option.name, option.shortcut); + let _ = write!(output, "\n{}{}, {}", bold_start, option.name, option.shortcut); if !option.fallback_shortcut.is_empty() { - output += &format!(", {}", option.fallback_shortcut); + let _ = write!(output, ", {}", option.fallback_shortcut); } - output += &format!("{}\n", bold_end); - output += &format!("{}\n", option.description); - output += &format!("{}${} cfonts {}", bold_start, bold_end, option.example); + let _ = writeln!(output, "{}", bold_end); + let _ = writeln!(output, "{}", option.description); + let _ = write!(output, "{}${} cfonts {}", bold_start, bold_end, option.example); match option.kind { OptionType::Font => { output += &color(&format!(" [ {} ]", Fonts::list()), Colors::Green).to_string(); diff --git a/rust/tests/chars_test.rs b/rust/tests/chars_test.rs index 711e16da..a93c9f3d 100644 --- a/rust/tests/chars_test.rs +++ b/rust/tests/chars_test.rs @@ -307,10 +307,10 @@ mod chars { ]; options.colors = vec![Colors::Red, Colors::Blue]; output = vec![ - String::from("\u{1b}[34mgreen\u{1b}[39m \u{1b}[31mred\u{1b}[39m blue \u{1b}[31mred\u{1b}[39m nothing"), + String::from("\x1B[34mgreen\x1B[39m \x1B[31mred\x1B[39m blue \x1B[31mred\x1B[39m nothing"), String::from("color"), String::from("nothing"), - String::from("\u{1b}[31mred\u{1b}[39m blue"), + String::from("\x1B[31mred\x1B[39m blue"), ]; assert_eq!(paint_letter(&letter, 4, &options), output); diff --git a/rust/tests/color_test.rs b/rust/tests/color_test.rs index beb2a7ff..83c694b7 100644 --- a/rust/tests/color_test.rs +++ b/rust/tests/color_test.rs @@ -467,23 +467,20 @@ mod color { }); temp_env::with_var("FORCE_COLOR", Some("1"), || { - assert_eq!( - color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[91m testing \u{1b}[39m") - ); + assert_eq!(color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), String::from("\x1B[91m testing \x1B[39m")); }); temp_env::with_var("FORCE_COLOR", Some("2"), || { assert_eq!( color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[38;5;211m testing \u{1b}[39m") + String::from("\x1B[38;5;211m testing \x1B[39m") ); }); temp_env::with_var("FORCE_COLOR", Some("3"), || { assert_eq!( color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[38;2;243;79;168m testing \u{1b}[39m") + String::from("\x1B[38;2;243;79;168m testing \x1B[39m") ); }); } @@ -497,21 +494,21 @@ mod color { temp_env::with_var("FORCE_COLOR", Some("1"), || { assert_eq!( bg_color(" testing ", BgColors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[101m testing \u{1b}[49m") + String::from("\x1B[101m testing \x1B[49m") ); }); temp_env::with_var("FORCE_COLOR", Some("2"), || { assert_eq!( bg_color(" testing ", BgColors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[48;5;211m testing \u{1b}[49m") + String::from("\x1B[48;5;211m testing \x1B[49m") ); }); temp_env::with_var("FORCE_COLOR", Some("3"), || { assert_eq!( bg_color(" testing ", BgColors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[48;2;243;79;168m testing \u{1b}[49m") + String::from("\x1B[48;2;243;79;168m testing \x1B[49m") ); }); } @@ -523,23 +520,20 @@ mod color { }); temp_env::with_vars(vec![("FORCE_COLOR", Some("1")), ("NO_COLOR", Some(""))], || { - assert_eq!( - color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[91m testing \u{1b}[39m") - ); + assert_eq!(color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), String::from("\x1B[91m testing \x1B[39m")); }); temp_env::with_vars(vec![("FORCE_COLOR", Some("2")), ("NO_COLOR", Some(""))], || { assert_eq!( color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[38;5;211m testing \u{1b}[39m") + String::from("\x1B[38;5;211m testing \x1B[39m") ); }); temp_env::with_vars(vec![("FORCE_COLOR", Some("3")), ("NO_COLOR", Some(""))], || { assert_eq!( color(" testing ", Colors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[38;2;243;79;168m testing \u{1b}[39m") + String::from("\x1B[38;2;243;79;168m testing \x1B[39m") ); }); } @@ -553,21 +547,21 @@ mod color { temp_env::with_vars(vec![("FORCE_COLOR", Some("1")), ("NO_COLOR", Some(""))], || { assert_eq!( bg_color(" testing ", BgColors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[101m testing \u{1b}[49m") + String::from("\x1B[101m testing \x1B[49m") ); }); temp_env::with_vars(vec![("FORCE_COLOR", Some("2")), ("NO_COLOR", Some(""))], || { assert_eq!( bg_color(" testing ", BgColors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[48;5;211m testing \u{1b}[49m") + String::from("\x1B[48;5;211m testing \x1B[49m") ); }); temp_env::with_vars(vec![("FORCE_COLOR", Some("3")), ("NO_COLOR", Some(""))], || { assert_eq!( bg_color(" testing ", BgColors::Rgb(Rgb::Val(243, 79, 168))), - String::from("\u{1b}[48;2;243;79;168m testing \u{1b}[49m") + String::from("\x1B[48;2;243;79;168m testing \x1B[49m") ); }); } diff --git a/rust/tests/debug_test.rs b/rust/tests/debug_test.rs index 9ebe8bd1..dc59df51 100644 --- a/rust/tests/debug_test.rs +++ b/rust/tests/debug_test.rs @@ -37,15 +37,15 @@ mod tests { d("test", 1, Dt::Head, &options, &mut output); let mut stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, "\u{1b}[43m\n \u{1b}[30mtest\u{1b}[39m \u{1b}[49m\n"); + assert_eq!(stdout, "\x1B[43m\n \x1B[30mtest\x1B[39m \x1B[49m\n"); d("test", 1, Dt::Log, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, "\u{1b}[43m\n \u{1b}[30mtest\u{1b}[39m \u{1b}[49m\n \u{1b}[33mtest\u{1b}[39m\n"); + assert_eq!(stdout, "\x1B[43m\n \x1B[30mtest\x1B[39m \x1B[49m\n \x1B[33mtest\x1B[39m\n"); d("test", 1, Dt::Error, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, "\u{1b}[43m\n \u{1b}[30mtest\u{1b}[39m \u{1b}[49m\n \u{1b}[33mtest\u{1b}[39m\n\u{1b}[41m\u{1b}[37m ERROR \u{1b}[39m\u{1b}[49m \u{1b}[31mtest\u{1b}[39m\n"); + assert_eq!(stdout, "\x1B[43m\n \x1B[30mtest\x1B[39m \x1B[49m\n \x1B[33mtest\x1B[39m\n\x1B[41m\x1B[37m ERROR \x1B[39m\x1B[49m \x1B[31mtest\x1B[39m\n"); }); } @@ -69,7 +69,7 @@ mod tests { d("test", 1, Dt::Log, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, " \u{1b}[33mtest\u{1b}[39m\n"); + assert_eq!(stdout, " \x1B[33mtest\x1B[39m\n"); output = Vec::new(); options.debug_level = 2; @@ -80,28 +80,28 @@ mod tests { d("test", 2, Dt::Log, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, " \u{1b}[33mtest\u{1b}[39m\n"); + assert_eq!(stdout, " \x1B[33mtest\x1B[39m\n"); output = Vec::new(); d("test", 1, Dt::Log, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, " \u{1b}[33mtest\u{1b}[39m\n"); + assert_eq!(stdout, " \x1B[33mtest\x1B[39m\n"); output = Vec::new(); options.debug_level = 3; d("test", 3, Dt::Log, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, " \u{1b}[33mtest\u{1b}[39m\n"); + assert_eq!(stdout, " \x1B[33mtest\x1B[39m\n"); output = Vec::new(); d("test", 2, Dt::Log, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, " \u{1b}[33mtest\u{1b}[39m\n"); + assert_eq!(stdout, " \x1B[33mtest\x1B[39m\n"); output = Vec::new(); d("test", 1, Dt::Log, &options, &mut output); stdout = String::from_utf8(output.clone()).expect("Input not UTF-8"); - assert_eq!(stdout, " \u{1b}[33mtest\u{1b}[39m\n"); + assert_eq!(stdout, " \x1B[33mtest\x1B[39m\n"); }); } } diff --git a/rust/tests/end-to-end_test.rs b/rust/tests/end-to-end_test.rs index 0fd8e151..cf98d751 100644 --- a/rust/tests/end-to-end_test.rs +++ b/rust/tests/end-to-end_test.rs @@ -263,6 +263,186 @@ fn get_all_tests() -> Vec { force_color: String::from("3"), no_color: true, }, + Test { + name: String::from("Color output for black with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "black,black".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[0mt\x1B[39m\x1B[0me\x1B[39m\x1B[0ms\x1B[39m\x1B[0mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for red with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "red,red".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[91mt\x1B[39m\x1B[94me\x1B[39m\x1B[92ms\x1B[39m\x1B[91mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for green with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "green,green".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[92mt\x1B[39m\x1B[91me\x1B[39m\x1B[94ms\x1B[39m\x1B[92mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for yellow with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "yellow,yellow".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[93mt\x1B[39m\x1B[95me\x1B[39m\x1B[96ms\x1B[39m\x1B[93mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for blue with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "blue,blue".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[94mt\x1B[39m\x1B[92me\x1B[39m\x1B[91ms\x1B[39m\x1B[94mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for magenta with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "magenta,magenta".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[95mt\x1B[39m\x1B[96me\x1B[39m\x1B[93ms\x1B[39m\x1B[95mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for cyan with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "cyan,cyan".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[96mt\x1B[39m\x1B[93me\x1B[39m\x1B[95ms\x1B[39m\x1B[96mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for white with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "white,white".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[97mt\x1B[39m\x1B[97me\x1B[39m\x1B[97ms\x1B[39m\x1B[97mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for gray with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "gray,gray".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[90mt\x1B[39m\x1B[90me\x1B[39m\x1B[90ms\x1B[39m\x1B[90mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, + Test { + name: String::from("Color output for grey with FORCE_COLOR 1"), + args: vec![ + "test".to_string(), + "-g".to_string(), + "grey,grey".to_string(), + "-f".to_string(), + "console".to_string(), + ], + fixture: concat!( + "\n\n", + "\x1B[90mt\x1B[39m\x1B[90me\x1B[39m\x1B[90ms\x1B[39m\x1B[90mt\x1B[39m\n", + "\n\n" + ) + .to_string(), + force_color: String::from("1"), + no_color: false, + }, Test { name: String::from("Align center"), args: vec!["test".to_string(), "-a".to_string() ,"center".to_string()], @@ -651,7 +831,7 @@ fn get_all_tests() -> Vec { name: String::from("Font test: \"console\" - with color"), args: vec![supported_characters.clone(), "--font".to_string(), "console".to_string(), "-c".to_string(), "red".to_string()], fixture: concat!("\n\n", - "\u{1b}[31ma\u{1b}[39m\u{1b}[31mb\u{1b}[39m\u{1b}[31mc\u{1b}[39m\u{1b}[31md\u{1b}[39m\u{1b}[31me\u{1b}[39m\u{1b}[31mf\u{1b}[39m\u{1b}[31mg\u{1b}[39m\u{1b}[31mh\u{1b}[39m\u{1b}[31mi\u{1b}[39m\u{1b}[31mj\u{1b}[39m\u{1b}[31mk\u{1b}[39m\u{1b}[31ml\u{1b}[39m\u{1b}[31mm\u{1b}[39m\u{1b}[31mn\u{1b}[39m\u{1b}[31mo\u{1b}[39m\u{1b}[31mp\u{1b}[39m\u{1b}[31mq\u{1b}[39m\u{1b}[31mr\u{1b}[39m\u{1b}[31ms\u{1b}[39m\u{1b}[31mt\u{1b}[39m\u{1b}[31mu\u{1b}[39m\u{1b}[31mv\u{1b}[39m\u{1b}[31mw\u{1b}[39m\u{1b}[31mx\u{1b}[39m\u{1b}[31my\u{1b}[39m\u{1b}[31mz\u{1b}[39m\u{1b}[31m0\u{1b}[39m\u{1b}[31m1\u{1b}[39m\u{1b}[31m2\u{1b}[39m\u{1b}[31m3\u{1b}[39m\u{1b}[31m4\u{1b}[39m\u{1b}[31m5\u{1b}[39m\u{1b}[31m6\u{1b}[39m\u{1b}[31m7\u{1b}[39m\u{1b}[31m8\u{1b}[39m\u{1b}[31m9\u{1b}[39m\u{1b}[31m!\u{1b}[39m\u{1b}[31m?\u{1b}[39m\u{1b}[31m.\u{1b}[39m\u{1b}[31m+\u{1b}[39m\u{1b}[31m-\u{1b}[39m\u{1b}[31m_\u{1b}[39m\u{1b}[31m=\u{1b}[39m\u{1b}[31m@\u{1b}[39m\u{1b}[31m#\u{1b}[39m\u{1b}[31m$\u{1b}[39m\u{1b}[31m%\u{1b}[39m\u{1b}[31m&\u{1b}[39m\u{1b}[31m(\u{1b}[39m\u{1b}[31m)\u{1b}[39m\u{1b}[31m/\u{1b}[39m\u{1b}[31m:\u{1b}[39m\u{1b}[31m;\u{1b}[39m\u{1b}[31m,\u{1b}[39m\u{1b}[31m'\u{1b}[39m\u{1b}[31m\"\u{1b}[39m\n", + "\x1B[31ma\x1B[39m\x1B[31mb\x1B[39m\x1B[31mc\x1B[39m\x1B[31md\x1B[39m\x1B[31me\x1B[39m\x1B[31mf\x1B[39m\x1B[31mg\x1B[39m\x1B[31mh\x1B[39m\x1B[31mi\x1B[39m\x1B[31mj\x1B[39m\x1B[31mk\x1B[39m\x1B[31ml\x1B[39m\x1B[31mm\x1B[39m\x1B[31mn\x1B[39m\x1B[31mo\x1B[39m\x1B[31mp\x1B[39m\x1B[31mq\x1B[39m\x1B[31mr\x1B[39m\x1B[31ms\x1B[39m\x1B[31mt\x1B[39m\x1B[31mu\x1B[39m\x1B[31mv\x1B[39m\x1B[31mw\x1B[39m\x1B[31mx\x1B[39m\x1B[31my\x1B[39m\x1B[31mz\x1B[39m\x1B[31m0\x1B[39m\x1B[31m1\x1B[39m\x1B[31m2\x1B[39m\x1B[31m3\x1B[39m\x1B[31m4\x1B[39m\x1B[31m5\x1B[39m\x1B[31m6\x1B[39m\x1B[31m7\x1B[39m\x1B[31m8\x1B[39m\x1B[31m9\x1B[39m\x1B[31m!\x1B[39m\x1B[31m?\x1B[39m\x1B[31m.\x1B[39m\x1B[31m+\x1B[39m\x1B[31m-\x1B[39m\x1B[31m_\x1B[39m\x1B[31m=\x1B[39m\x1B[31m@\x1B[39m\x1B[31m#\x1B[39m\x1B[31m$\x1B[39m\x1B[31m%\x1B[39m\x1B[31m&\x1B[39m\x1B[31m(\x1B[39m\x1B[31m)\x1B[39m\x1B[31m/\x1B[39m\x1B[31m:\x1B[39m\x1B[31m;\x1B[39m\x1B[31m,\x1B[39m\x1B[31m'\x1B[39m\x1B[31m\"\x1B[39m\n", "\n\n").to_string(), force_color: String::from(""), no_color: false,