diff --git a/crates/nu-cli/src/commands/to_html.rs b/crates/nu-cli/src/commands/to_html.rs index c6de43506d..41b3a96632 100644 --- a/crates/nu-cli/src/commands/to_html.rs +++ b/crates/nu-cli/src/commands/to_html.rs @@ -5,9 +5,17 @@ use futures::StreamExt; use nu_errors::ShellError; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value}; use nu_source::AnchorLocation; +use regex::Regex; +use std::collections::HashMap; pub struct ToHTML; +#[derive(Deserialize)] +pub struct ToHTMLArgs { + html_color: bool, + no_color: bool, +} + #[async_trait] impl WholeStreamCommand for ToHTML { fn name(&self) -> &str { @@ -16,6 +24,8 @@ impl WholeStreamCommand for ToHTML { fn signature(&self) -> Signature { Signature::build("to html") + .switch("html_color", "change ansi colors to html colors", Some('t')) + .switch("no_color", "remove all ansi colors in output", Some('n')) } fn usage(&self) -> &str { @@ -36,16 +46,34 @@ async fn to_html( registry: &CommandRegistry, ) -> Result { let registry = registry.clone(); - let args = args.evaluate_once(®istry).await?; - let name_tag = args.name_tag(); - let input: Vec = args.input.collect().await; + let name_tag = args.call_info.name_tag.clone(); + let ( + ToHTMLArgs { + html_color, + no_color, + }, + input, + ) = args.process(®istry).await?; + let input: Vec = input.collect().await; let headers = nu_protocol::merge_descriptors(&input); - let mut output_string = "".to_string(); + let mut output_string = "".to_string(); + output_string.push_str(""); + // change the body background color + // output_string.push_str(""); + let mut hm = HashMap::new(); + + // Add grid lines to html + // let mut output_string = ""); if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") { output_string.push_str(""); output_string.push_str(""); + // change the background of tables + // output_string.push_str(""); + for header in &headers { output_string.push_str("
"); output_string.push_str(&htmlescape::encode_minimal(&header)); @@ -124,14 +152,211 @@ async fn to_html( } output_string.push_str(""); + // Check to see if we want to remove all color or change ansi to html colors + if html_color { + setup_html_color_regexes(&mut hm); + output_string = run_regexes(&hm, &output_string); + } else if no_color { + setup_no_color_regexes(&mut hm); + output_string = run_regexes(&hm, &output_string); + } + Ok(OutputStream::one(ReturnSuccess::value( UntaggedValue::string(output_string).into_value(name_tag), ))) } +fn setup_html_color_regexes(hash: &mut HashMap) { + // All the bold colors + hash.insert( + 0, + ( + r"(?P\[0m)(?P[[:alnum:][:space:][:punct:]]*)", + // Since this is a reset, reset to black, normal weight font + r"$word", + ), + ); + hash.insert( + 1, + ( + // Bold Black + // r"(?P\[1;30m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[1;30m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 2, + ( + // Bold Red + // r"(?P
\[1;31m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P
\[1;31m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 3, + ( + // Bold Green + // r"(?P\[1;32m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[1;32m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 4, + ( + // Bold Yellow + // r"(?P\[1;33m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[1;33m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 5, + ( + // Bold Blue + // r"(?P\[1;34m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[1;34m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 6, + ( + // Bold Magenta + // r"(?P\[1;35m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[1;35m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 7, + ( + // Bold Cyan + // r"(?P\[1;36m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[1;36m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 8, + ( + // Bold White + // Let's change this to black since the html background + // is white. White on white = no bueno. + // r"(?P\[1;37m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[1;37m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + // All the normal colors + hash.insert( + 9, + ( + // Black + // r"(?P\[30m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[30m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 10, + ( + // Red + // r"(?P\[31m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[31m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 11, + ( + // Green + // r"(?P\[32m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[32m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 12, + ( + // Yellow + // r"(?P\[33m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[33m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 13, + ( + // Blue + // r"(?P\[34m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[34m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 14, + ( + // Magenta + // r"(?P\[35m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[35m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 15, + ( + // Cyan + // r"(?P\[36m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[36m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); + hash.insert( + 16, + ( + // White + // Let's change this to black since the html background + // is white. White on white = no bueno. + // r"(?P\[37m)(?P[A-Za-z0-9\-'!/_~ &;|=\+\*\.#%:\]$`\(\)]+)", + r"(?P\[37m)(?P[[:alnum:][:space:][:punct:]]*)", + r"$word", + ), + ); +} + +fn setup_no_color_regexes(hash: &mut HashMap) { + // We can just use one regex here because we're just removing ansi sequences + // and not replacing them with html colors. + // attribution: https://stackoverflow.com/questions/14693701/how-can-i-remove-the-ansi-escape-sequences-from-a-string-in-python + hash.insert( + 0, + ( + r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])", + r"$name_group_doesnt_exist", + ), + ); +} + +fn run_regexes(hash: &HashMap, contents: &str) -> String { + let mut working_string = contents.to_owned(); + let hash_count: u32 = hash.len() as u32; + for n in 0..hash_count { + let value = hash.get(&n).expect("error getting hash at index"); + //println!("{},{}", value.0, value.1); + let re = Regex::new(value.0).expect("problem with color regex"); + let after = re.replace_all(&working_string, value.1).to_string(); + working_string = after.clone(); + } + working_string +} + #[cfg(test)] mod tests { - use super::ToHTML; + use super::*; + use std::collections::HashMap; #[test] fn examples_work_as_expected() { @@ -139,4 +364,31 @@ mod tests { test_examples(ToHTML {}) } + + #[test] + fn test_html_color_flag() { + let mut hm = HashMap::new(); + let cd_help = r"Change to a new path.

Usage:
> cd (directory) {flags}

Parameters:
(directory) the directory to change to

Flags:
-h, --help: Display this help message

Examples:
Change to a new directory called 'dirname'
> cd dirname

Change to your home directory
> cd

Change to your home directory (alternate version)
> cd ~

Change to the previous directory
> cd -

".to_string(); + let cd_help_expected_result = r"Change to a new path.

Usage:
> cd (directory) {flags}

Parameters:
(directory) the directory to change to

Flags:
-h, --help: Display this help message

Examples:
Change to a new directory called 'dirname'
> cd dirname

Change to your home directory
>
cd

Change to your home directory (alternate version)
>
cd
~

Change to the previous directory
>
cd
-

".to_string(); + setup_html_color_regexes(&mut hm); + assert_eq!(cd_help_expected_result, run_regexes(&hm, &cd_help)); + } + + #[test] + fn test_no_color_flag() { + let mut hm = HashMap::new(); + let cd_help = r"Change to a new path.

Usage:
> cd (directory) {flags}

Parameters:
(directory) the directory to change to

Flags:
-h, --help: Display this help message

Examples:
Change to a new directory called 'dirname'
> cd dirname

Change to your home directory
> cd

Change to your home directory (alternate version)
> cd ~

Change to the previous directory
> cd -

".to_string(); + let cd_help_expected_result = r"Change to a new path.

Usage:
> cd (directory) {flags}

Parameters:
(directory) the directory to change to

Flags:
-h, --help: Display this help message

Examples:
Change to a new directory called 'dirname'
> cd dirname

Change to your home directory
> cd

Change to your home directory (alternate version)
> cd ~

Change to the previous directory
> cd -

".to_string(); + setup_no_color_regexes(&mut hm); + assert_eq!(cd_help_expected_result, run_regexes(&hm, &cd_help)); + } + + #[test] + fn test_html_color_where_flag() { + let mut hm = HashMap::new(); + let where_help = r"Filter table to match the condition.

Usage:
> where <condition> {flags}

Parameters:
<condition> the condition that must match

Flags:
-h, --help: Display this help message

Examples:
List all files in the current directory with sizes greater than 2kb
> ls | where size > 2kb

List only the files in the current directory
> ls | where type == File

List all files with names that contain "Car"
> ls | where name =~ "Car"

List all files that were modified in the last two months
> ls | where modified <= 2M

".to_string(); + let where_help_exptected_results = r"Filter table to match the condition.

Usage:
> where <condition> {flags}

Parameters:
<condition> the condition that must match

Flags:
-h, --help: Display this help message

Examples:
List all files in the current directory with sizes greater than 2kb
> ls | where size > 2kb

List only the files in the current directory
>
ls
| where type == File

List all files with names that contain "Car"
>
ls
| where name =~ "Car"

List all files that were modified in the last two months
>
ls
| where modified <= 2M

".to_string(); + setup_html_color_regexes(&mut hm); + assert_eq!(where_help_exptected_results, run_regexes(&hm, &where_help)); + } }