use crate::commands::WholeStreamCommand; use crate::data::value::format_leaf; use crate::prelude::*; 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 { "to html" } 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 { "Convert table into simple HTML" } async fn run( &self, args: CommandArgs, registry: &CommandRegistry, ) -> Result { to_html(args, registry).await } } async fn to_html( args: CommandArgs, registry: &CommandRegistry, ) -> Result { let registry = registry.clone(); 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(); 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(""); } for row in input { match row.value { UntaggedValue::Primitive(Primitive::Binary(b)) => { // This might be a bit much, but it's fun :) match row.tag.anchor { Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => { let extension = f.split('.').last().map(String::from); match extension { Some(s) if ["png", "jpg", "bmp", "gif", "tiff", "jpeg"] .contains(&s.to_lowercase().as_str()) => { output_string.push_str(""); } _ => {} } } _ => {} } } UntaggedValue::Primitive(Primitive::String(ref b)) => { // This might be a bit much, but it's fun :) match row.tag.anchor { Some(AnchorLocation::Url(f)) | Some(AnchorLocation::File(f)) => { let extension = f.split('.').last().map(String::from); match extension { Some(s) if s.to_lowercase() == "svg" => { output_string.push_str(""); continue; } _ => {} } } _ => {} } output_string.push_str( &(htmlescape::encode_minimal(&format_leaf(&row.value).plain_string(100_000)) .replace("\n", "
")), ); } UntaggedValue::Row(row) => { output_string.push_str(""); for header in &headers { let data = row.get_data(header); output_string.push_str(""); } output_string.push_str(""); } p => { output_string.push_str( &(htmlescape::encode_minimal(&format_leaf(&p).plain_string(100_000)) .replace("\n", "
")), ); } } } if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") { output_string.push_str("
"); output_string.push_str(&htmlescape::encode_minimal(&header)); output_string.push_str("
"); output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000)); output_string.push_str("
"); } 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::*; use std::collections::HashMap; #[test] fn examples_work_as_expected() { use crate::examples::test as test_examples; 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)); } }