diff --git a/crates/nu-command/src/core_commands/metadata.rs b/crates/nu-command/src/core_commands/metadata.rs index 04fcd2d46..42a8aa48d 100644 --- a/crates/nu-command/src/core_commands/metadata.rs +++ b/crates/nu-command/src/core_commands/metadata.rs @@ -86,6 +86,15 @@ impl Command for Metadata { span: head, }) } + PipelineMetadata { + data_source: DataSource::HtmlThemes, + } => { + cols.push("source".into()); + vals.push(Value::String { + val: "into html --list".into(), + span: head, + }) + } } } @@ -148,6 +157,15 @@ fn build_metadata_record(arg: &Value, metadata: &Option, head: span: head, }) } + PipelineMetadata { + data_source: DataSource::HtmlThemes, + } => { + cols.push("source".into()); + vals.push(Value::String { + val: "into html --list".into(), + span: head, + }) + } } } diff --git a/crates/nu-command/src/formats/to/html.rs b/crates/nu-command/src/formats/to/html.rs index ca689b799..5226d0186 100644 --- a/crates/nu-command/src/formats/to/html.rs +++ b/crates/nu-command/src/formats/to/html.rs @@ -4,8 +4,8 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, - SyntaxShape, Type, Value, + Category, Config, DataSource, Example, IntoPipelineData, PipelineData, PipelineMetadata, + ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use rust_embed::RustEmbed; use serde::{Deserialize, Serialize}; @@ -109,7 +109,11 @@ impl Command for ToHtml { "the name of the theme to use (github, blulocolight, ...)", Some('t'), ) - .switch("list", "list the names of all available themes", Some('l')) + .switch( + "list", + "produce a color table of all available themes", + Some('l'), + ) .category(Category::Formats) } @@ -143,6 +147,10 @@ impl Command for ToHtml { "Convert table into simple HTML" } + fn extra_usage(&self) -> &str { + "Screenshots of the themes can be browsed here: https://github.com/mbadolato/iTerm2-Color-Schemes" + } + fn run( &self, engine_state: &EngineState, @@ -223,12 +231,6 @@ fn get_html_themes(json_name: &str) -> Result> { } } -fn get_list_of_theme_names() -> Vec { - // If asset doesn't work, make sure to return the default theme - let html_themes = get_html_themes("228_themes.json").unwrap_or_default(); - html_themes.themes.into_iter().map(|n| n.name).collect() -} - fn to_html( input: PipelineData, call: &Call, @@ -251,17 +253,76 @@ fn to_html( let mut output_string = String::new(); let mut regex_hm: HashMap = HashMap::with_capacity(17); + // Being essentially a 'help' option, this can afford to be relatively unoptimised if list { - // Get the list of theme names - let theme_names = get_list_of_theme_names(); + // If asset doesn't work, make sure to return the default theme + let html_themes = get_html_themes("228_themes.json").unwrap_or_default(); - // Put that list into the output string - for s in &theme_names { - writeln!(&mut output_string, "{}", s).unwrap(); + let cols = vec![ + "name".into(), + "black".into(), + "red".into(), + "green".into(), + "yellow".into(), + "blue".into(), + "purple".into(), + "cyan".into(), + "white".into(), + "brightBlack".into(), + "brightRed".into(), + "brightGreen".into(), + "brightYellow".into(), + "brightBlue".into(), + "brightPurple".into(), + "brightCyan".into(), + "brightWhite".into(), + "background".into(), + "foreground".into(), + ]; + + let result: Vec = html_themes + .themes + .into_iter() + .map(|n| { + let vals = vec![ + n.name, + n.black, + n.red, + n.green, + n.yellow, + n.blue, + n.purple, + n.cyan, + n.white, + n.brightBlack, + n.brightRed, + n.brightGreen, + n.brightYellow, + n.brightBlue, + n.brightPurple, + n.brightCyan, + n.brightWhite, + n.background, + n.foreground, + ] + .into_iter() + .map(|val| Value::String { val, span: head }) + .collect(); + + Value::Record { + cols: cols.clone(), + vals, + span: head, + } + }) + .collect(); + return Ok(Value::List { + vals: result, + span: head, } - - output_string.push_str("\nScreenshots of themes can be found here:\n"); - output_string.push_str("https://github.com/mbadolato/iTerm2-Color-Schemes\n"); + .into_pipeline_data_with_metadata(PipelineMetadata { + data_source: DataSource::HtmlThemes, + })); } else { let theme_span = match &theme { Some(v) => v.span, diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 9ba56d664..65f3cfa04 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1,5 +1,5 @@ use lscolors::{LsColors, Style}; -use nu_color_config::{get_color_config, style_primitive}; +use nu_color_config::{color_from_hex, get_color_config, style_primitive}; use nu_engine::{column::get_columns, env_to_string, CallExt}; use nu_protocol::{ ast::{Call, PathMember}, @@ -558,6 +558,7 @@ fn handle_row_stream( metadata: Option, ) -> Result { let stream = match metadata { + // First, `ls` sources: Some(PipelineMetadata { data_source: DataSource::Ls, }) => { @@ -575,6 +576,7 @@ fn handle_row_stream( let mut idx = 0; while idx < cols.len() { + // Only the name column gets special colors, for now if cols[idx] == "name" { if let Some(Value::String { val, span }) = vals.get(idx) { let val = render_path_name(val, &config, &ls_colors, *span); @@ -594,6 +596,46 @@ fn handle_row_stream( ctrlc, ) } + // Next, `into html -l` sources: + Some(PipelineMetadata { + data_source: DataSource::HtmlThemes, + }) => { + let ctrlc = ctrlc.clone(); + + ListStream::from_stream( + stream.map(move |mut x| match &mut x { + Value::Record { cols, vals, .. } => { + let mut idx = 0; + // Every column in the HTML theme table except 'name' is colored + while idx < cols.len() { + if cols[idx] != "name" { + // Simple routine to grab the hex code, convert to a style, + // then place it in a new Value::String. + if let Some(Value::String { val, span }) = vals.get(idx) { + let s = match color_from_hex(val) { + Ok(c) => match c { + // .normal() just sets the text foreground color. + Some(c) => c.normal(), + None => nu_ansi_term::Style::default(), + }, + Err(_) => nu_ansi_term::Style::default(), + }; + vals[idx] = Value::String { + // Apply the style (ANSI codes) to the string + val: s.paint(val).to_string(), + span: *span, + }; + } + } + idx += 1; + } + x + } + _ => x, + }), + ctrlc, + ) + } _ => stream, }; diff --git a/crates/nu-command/tests/format_conversions/html.rs b/crates/nu-command/tests/format_conversions/html.rs index 6b26d1a4c..87b92a276 100644 --- a/crates/nu-command/tests/format_conversions/html.rs +++ b/crates/nu-command/tests/format_conversions/html.rs @@ -75,3 +75,15 @@ fn test_no_color_flag() { r"Change directory.

Usage:
> cd (path)

Flags:
-h, --help - Display the help message for this command

Parameters:
(optional) path <Directory>: the path to change to

Examples:
Change to your home directory
> cd ~

Change to a directory via abbreviations
> cd d/s/9

Change to the previous working directory ($OLDPWD)
> cd -

" ); } + +#[test] +fn test_list() { + let actual = nu!( + cwd: ".", + r#"to html --list | where name == C64 | get 0 | to nuon"# + ); + assert_eq!( + actual.out, + r##"{name: C64, black: "#090300", red: "#883932", green: "#55a049", yellow: "#bfce72", blue: "#40318d", purple: "#8b3f96", cyan: "#67b6bd", white: "#ffffff", brightBlack: "#000000", brightRed: "#883932", brightGreen: "#55a049", brightYellow: "#bfce72", brightBlue: "#40318d", brightPurple: "#8b3f96", brightCyan: "#67b6bd", brightWhite: "#f7f7f7", background: "#40318d", foreground: "#7869c4"}"## + ); +} diff --git a/crates/nu-protocol/src/pipeline_data.rs b/crates/nu-protocol/src/pipeline_data.rs index 2f266118b..de79a52e1 100644 --- a/crates/nu-protocol/src/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline_data.rs @@ -56,6 +56,7 @@ pub struct PipelineMetadata { #[derive(Debug, Clone)] pub enum DataSource { Ls, + HtmlThemes, } impl PipelineData { @@ -595,6 +596,7 @@ impl Iterator for PipelineIterator { pub trait IntoPipelineData { fn into_pipeline_data(self) -> PipelineData; + fn into_pipeline_data_with_metadata(self, metadata: PipelineMetadata) -> PipelineData; } impl IntoPipelineData for V @@ -604,6 +606,9 @@ where fn into_pipeline_data(self) -> PipelineData { PipelineData::Value(self.into(), None) } + fn into_pipeline_data_with_metadata(self, metadata: PipelineMetadata) -> PipelineData { + PipelineData::Value(self.into(), Some(metadata)) + } } pub trait IntoInterruptiblePipelineData {