mirror of
https://github.com/nushell/nushell.git
synced 2024-11-28 19:33:47 +01:00
To html
and to md
(#453)
* MathEval Variance and Stddev * Fix tests and linting * Typo * Deal with streams when they are not tables * First draft of these commands * To MD * To md and to html * Fixed cargo and to_md * `into_abbreviated_string` instead of `into_string` * Changed how inner tables are displayed
This commit is contained in:
parent
865906e450
commit
7a892ec5d7
101
Cargo.lock
generated
101
Cargo.lock
generated
@ -342,6 +342,27 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70"
|
checksum = "6c58ec36aac5066d5ca17df51b3e70279f5670a72102f5752cb7e7c856adfc70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bzip2"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
|
||||||
|
dependencies = [
|
||||||
|
"bzip2-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bzip2-sys"
|
||||||
|
version = "0.1.11+1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calamine"
|
name = "calamine"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
@ -1064,6 +1085,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "htmlescape"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ical"
|
name = "ical"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -1578,6 +1605,7 @@ dependencies = [
|
|||||||
"dtparse",
|
"dtparse",
|
||||||
"eml-parser",
|
"eml-parser",
|
||||||
"glob",
|
"glob",
|
||||||
|
"htmlescape",
|
||||||
"ical",
|
"ical",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itertools",
|
"itertools",
|
||||||
@ -1594,10 +1622,12 @@ dependencies = [
|
|||||||
"nu-term-grid",
|
"nu-term-grid",
|
||||||
"num 0.4.0",
|
"num 0.4.0",
|
||||||
"polars",
|
"polars",
|
||||||
|
"pretty-hex",
|
||||||
"rand",
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_ini",
|
"serde_ini",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
@ -1612,6 +1642,7 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2196,6 +2227,12 @@ dependencies = [
|
|||||||
"termtree",
|
"termtree",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pretty-hex"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc5c99d529f0d30937f6f4b8a86d988047327bb88d04d2c4afc356de74722131"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@ -2439,6 +2476,39 @@ dependencies = [
|
|||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed"
|
||||||
|
version = "5.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fe1fe6aac5d6bb9e1ffd81002340363272a7648234ec7bdfac5ee202cb65523"
|
||||||
|
dependencies = [
|
||||||
|
"rust-embed-impl",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-impl"
|
||||||
|
version = "5.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ed91c41c42ef7bf687384439c312e75e0da9c149b0390889b94de3c7d9d9e66"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rust-embed-utils",
|
||||||
|
"syn",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-embed-utils"
|
||||||
|
version = "5.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a512219132473ab0a77b52077059f1c47ce4af7fbdc94503e9862a34422876d"
|
||||||
|
dependencies = [
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust_decimal"
|
name = "rust_decimal"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
@ -2463,6 +2533,15 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@ -3064,6 +3143,17 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.9.0+wasi-snapshot-preview1"
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
@ -3156,6 +3246,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -3190,9 +3289,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
|
checksum = "93ab48844d61251bb3835145c521d88aa4031d7139e8485990f60ca911fa0815"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"bzip2",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"flate2",
|
"flate2",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -78,6 +78,8 @@ example = ["nu_plugin_example"]
|
|||||||
# Extra
|
# Extra
|
||||||
gstat = ["nu_plugin_gstat"]
|
gstat = ["nu_plugin_gstat"]
|
||||||
|
|
||||||
|
zip-support = ["nu-command/zip"]
|
||||||
|
|
||||||
# Dataframe feature for nushell
|
# Dataframe feature for nushell
|
||||||
dataframe = ["nu-command/dataframe"]
|
dataframe = ["nu-command/dataframe"]
|
||||||
|
|
||||||
|
@ -46,9 +46,13 @@ ical = "0.7.0"
|
|||||||
calamine = "0.18.0"
|
calamine = "0.18.0"
|
||||||
roxmltree = "0.14.0"
|
roxmltree = "0.14.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
rust-embed = "5.9.0"
|
||||||
trash = { version = "1.3.0", optional = true }
|
trash = { version = "1.3.0", optional = true }
|
||||||
unicode-segmentation = "1.8.0"
|
unicode-segmentation = "1.8.0"
|
||||||
uuid = { version = "0.8.2", features = ["v4"] }
|
uuid = { version = "0.8.2", features = ["v4"] }
|
||||||
|
htmlescape = "0.3.1"
|
||||||
|
pretty-hex = "0.2.1"
|
||||||
|
zip = { version="0.5.9", optional=true }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
strip-ansi-escapes = "0.1.1"
|
strip-ansi-escapes = "0.1.1"
|
||||||
crossterm = "0.22.1"
|
crossterm = "0.22.1"
|
||||||
|
BIN
crates/nu-command/assets/228_themes.zip
Normal file
BIN
crates/nu-command/assets/228_themes.zip
Normal file
Binary file not shown.
@ -164,6 +164,8 @@ pub fn create_default_context() -> EngineState {
|
|||||||
ToToml,
|
ToToml,
|
||||||
ToTsv,
|
ToTsv,
|
||||||
ToCsv,
|
ToCsv,
|
||||||
|
ToHtml,
|
||||||
|
ToMd,
|
||||||
Touch,
|
Touch,
|
||||||
Uniq,
|
Uniq,
|
||||||
Use,
|
Use,
|
||||||
|
@ -22,7 +22,7 @@ mod uniq;
|
|||||||
mod update;
|
mod update;
|
||||||
mod where_;
|
mod where_;
|
||||||
mod wrap;
|
mod wrap;
|
||||||
mod zip;
|
mod zip_;
|
||||||
|
|
||||||
pub use all::All;
|
pub use all::All;
|
||||||
pub use any::Any;
|
pub use any::Any;
|
||||||
@ -48,4 +48,4 @@ pub use uniq::*;
|
|||||||
pub use update::Update;
|
pub use update::Update;
|
||||||
pub use where_::Where;
|
pub use where_::Where;
|
||||||
pub use wrap::Wrap;
|
pub use wrap::Wrap;
|
||||||
pub use zip::Zip;
|
pub use zip_::Zip;
|
||||||
|
@ -87,14 +87,11 @@ fn to_string_tagged_value(v: &Value, config: &Config) -> Result<String, ShellErr
|
|||||||
| Value::Error { .. }
|
| Value::Error { .. }
|
||||||
| Value::Filesize { .. }
|
| Value::Filesize { .. }
|
||||||
| Value::CellPath { .. }
|
| Value::CellPath { .. }
|
||||||
| Value::Float { .. } => Ok(v.clone().into_string("", config)),
|
| Value::List { .. }
|
||||||
|
| Value::Record { .. }
|
||||||
|
| Value::Float { .. } => Ok(v.clone().into_abbreviated_string(config)),
|
||||||
Value::Date { val, .. } => Ok(val.to_string()),
|
Value::Date { val, .. } => Ok(val.to_string()),
|
||||||
Value::Nothing { .. } => Ok(String::new()),
|
Value::Nothing { .. } => Ok(String::new()),
|
||||||
Value::List { ref vals, .. } => match &vals[..] {
|
|
||||||
[Value::Record { .. }, _end @ ..] => Ok(String::from("[Table]")),
|
|
||||||
_ => Ok(String::from("[List]")),
|
|
||||||
},
|
|
||||||
Value::Record { .. } => Ok(String::from("[Row]")),
|
|
||||||
_ => Err(ShellError::UnsupportedInput(
|
_ => Err(ShellError::UnsupportedInput(
|
||||||
"Unexpected value".to_string(),
|
"Unexpected value".to_string(),
|
||||||
v.span().unwrap_or_else(|_| Span::unknown()),
|
v.span().unwrap_or_else(|_| Span::unknown()),
|
||||||
@ -102,13 +99,13 @@ fn to_string_tagged_value(v: &Value, config: &Config) -> Result<String, ShellErr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_descriptors(values: &[Value]) -> Vec<String> {
|
pub fn merge_descriptors(values: &[Value]) -> Vec<String> {
|
||||||
let mut ret: Vec<String> = vec![];
|
let mut ret: Vec<String> = vec![];
|
||||||
let mut seen: IndexSet<String> = indexset! {};
|
let mut seen: IndexSet<String> = indexset! {};
|
||||||
for value in values {
|
for value in values {
|
||||||
let data_descriptors = match value {
|
let data_descriptors = match value {
|
||||||
Value::Record { cols, .. } => cols.to_owned(),
|
Value::Record { cols, .. } => cols.to_owned(),
|
||||||
_ => vec![],
|
_ => vec!["".to_string()],
|
||||||
};
|
};
|
||||||
for desc in data_descriptors {
|
for desc in data_descriptors {
|
||||||
if !seen.contains(&desc) {
|
if !seen.contains(&desc) {
|
||||||
|
727
crates/nu-command/src/formats/to/html.rs
Normal file
727
crates/nu-command/src/formats/to/html.rs
Normal file
@ -0,0 +1,727 @@
|
|||||||
|
use crate::formats::to::delimited::merge_descriptors;
|
||||||
|
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, Value,
|
||||||
|
};
|
||||||
|
use regex::Regex;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct HtmlThemes {
|
||||||
|
themes: Vec<HtmlTheme>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct HtmlTheme {
|
||||||
|
name: String,
|
||||||
|
black: String,
|
||||||
|
red: String,
|
||||||
|
green: String,
|
||||||
|
yellow: String,
|
||||||
|
blue: String,
|
||||||
|
purple: String,
|
||||||
|
cyan: String,
|
||||||
|
white: String,
|
||||||
|
brightBlack: String,
|
||||||
|
brightRed: String,
|
||||||
|
brightGreen: String,
|
||||||
|
brightYellow: String,
|
||||||
|
brightBlue: String,
|
||||||
|
brightPurple: String,
|
||||||
|
brightCyan: String,
|
||||||
|
brightWhite: String,
|
||||||
|
background: String,
|
||||||
|
foreground: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HtmlThemes {
|
||||||
|
fn default() -> Self {
|
||||||
|
HtmlThemes {
|
||||||
|
themes: vec![HtmlTheme::default()],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for HtmlTheme {
|
||||||
|
fn default() -> Self {
|
||||||
|
HtmlTheme {
|
||||||
|
name: "nu_default".to_string(),
|
||||||
|
black: "black".to_string(),
|
||||||
|
red: "red".to_string(),
|
||||||
|
green: "green".to_string(),
|
||||||
|
yellow: "#717100".to_string(),
|
||||||
|
blue: "blue".to_string(),
|
||||||
|
purple: "#c800c8".to_string(),
|
||||||
|
cyan: "#037979".to_string(),
|
||||||
|
white: "white".to_string(),
|
||||||
|
brightBlack: "black".to_string(),
|
||||||
|
brightRed: "red".to_string(),
|
||||||
|
brightGreen: "green".to_string(),
|
||||||
|
brightYellow: "#717100".to_string(),
|
||||||
|
brightBlue: "blue".to_string(),
|
||||||
|
brightPurple: "#c800c8".to_string(),
|
||||||
|
brightCyan: "#037979".to_string(),
|
||||||
|
brightWhite: "white".to_string(),
|
||||||
|
background: "white".to_string(),
|
||||||
|
foreground: "black".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "assets/"]
|
||||||
|
struct Assets;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ToHtml;
|
||||||
|
|
||||||
|
impl Command 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('c'))
|
||||||
|
.switch("no_color", "remove all ansi colors in output", Some('n'))
|
||||||
|
.switch(
|
||||||
|
"dark",
|
||||||
|
"indicate your background color is a darker color",
|
||||||
|
Some('d'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"partial",
|
||||||
|
"only output the html for the content itself",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
|
.named(
|
||||||
|
"theme",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the name of the theme to use (github, blulocolight, ...)",
|
||||||
|
Some('t'),
|
||||||
|
)
|
||||||
|
.switch("list", "list the names of all available themes", Some('l'))
|
||||||
|
.category(Category::Formats)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Outputs an HTML string representing the contents of this table",
|
||||||
|
example: "[[foo bar]; [1 2]] | to html",
|
||||||
|
result: Some(Value::test_string(
|
||||||
|
r#"<html><style>body { background-color:white;color:black; }</style><body><table><tr><th>foo</th><th>bar</th></tr><tr><td>1</td><td>2</td></tr></table></body></html>"#,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Optionally, only output the html for the content itself",
|
||||||
|
example: "[[foo bar]; [1 2]] | to html --partial",
|
||||||
|
result: Some(Value::test_string(
|
||||||
|
r#"<div style="background-color:white;color:black;"><table><tr><th>foo</th><th>bar</th></tr><tr><td>1</td><td>2</td></tr></table></div>"#,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Optionally, output the string with a dark background",
|
||||||
|
example: "[[foo bar]; [1 2]] | to html --dark",
|
||||||
|
result: Some(Value::test_string(
|
||||||
|
r#"<html><style>body { background-color:black;color:white; }</style><body><table><tr><th>foo</th><th>bar</th></tr><tr><td>1</td><td>2</td></tr></table></body></html>"#,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Convert table into simple HTML"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, ShellError> {
|
||||||
|
to_html(input, call, engine_state, stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_theme_from_asset_file(
|
||||||
|
is_dark: bool,
|
||||||
|
theme: &Option<Spanned<String>>,
|
||||||
|
) -> Result<HashMap<&'static str, String>, ShellError> {
|
||||||
|
let theme_name = match theme {
|
||||||
|
Some(s) => s.item.clone(),
|
||||||
|
None => "default".to_string(), // There is no theme named "default" so this will be HtmlTheme::default(), which is "nu_default".
|
||||||
|
};
|
||||||
|
|
||||||
|
// 228 themes come from
|
||||||
|
// https://github.com/mbadolato/iTerm2-Color-Schemes/tree/master/windowsterminal
|
||||||
|
// we should find a hit on any name in there
|
||||||
|
let asset = get_asset_by_name_as_html_themes("228_themes.zip", "228_themes.json");
|
||||||
|
|
||||||
|
// If asset doesn't work, make sure to return the default theme
|
||||||
|
let asset = match asset {
|
||||||
|
Ok(a) => a,
|
||||||
|
_ => HtmlThemes::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the theme by theme name
|
||||||
|
let th = asset
|
||||||
|
.themes
|
||||||
|
.iter()
|
||||||
|
.find(|&n| n.name.to_lowercase() == theme_name.to_lowercase()); // case insensitive search
|
||||||
|
|
||||||
|
// If no theme is found by the name provided, ensure we return the default theme
|
||||||
|
let default_theme = HtmlTheme::default();
|
||||||
|
let th = match th {
|
||||||
|
Some(t) => t,
|
||||||
|
None => &default_theme,
|
||||||
|
};
|
||||||
|
|
||||||
|
// this just means no theme was passed in
|
||||||
|
if th.name.to_lowercase().eq(&"nu_default".to_string())
|
||||||
|
// this means there was a theme passed in
|
||||||
|
&& theme.is_some()
|
||||||
|
{
|
||||||
|
return Err(ShellError::NotFound(
|
||||||
|
theme.as_ref().expect("this should never trigger").span,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(convert_html_theme_to_hash_map(is_dark, th))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn get_asset_by_name_as_html_themes(
|
||||||
|
zip_name: &str,
|
||||||
|
json_name: &str,
|
||||||
|
) -> Result<HtmlThemes, Box<dyn Error>> {
|
||||||
|
match Assets::get(zip_name) {
|
||||||
|
Some(content) => {
|
||||||
|
let asset: Vec<u8> = match content {
|
||||||
|
Cow::Borrowed(bytes) => bytes.into(),
|
||||||
|
Cow::Owned(bytes) => bytes,
|
||||||
|
};
|
||||||
|
let reader = std::io::Cursor::new(asset);
|
||||||
|
#[cfg(feature = "zip")]
|
||||||
|
{
|
||||||
|
use std::io::Read;
|
||||||
|
let mut archive = zip::ZipArchive::new(reader)?;
|
||||||
|
let mut zip_file = archive.by_name(json_name)?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
zip_file.read_to_string(&mut contents)?;
|
||||||
|
Ok(nu_json::from_str(&contents)?)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "zip"))]
|
||||||
|
{
|
||||||
|
let th = HtmlThemes::default();
|
||||||
|
Ok(th)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let th = HtmlThemes::default();
|
||||||
|
Ok(th)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_html_theme_to_hash_map(
|
||||||
|
is_dark: bool,
|
||||||
|
theme: &HtmlTheme,
|
||||||
|
) -> HashMap<&'static str, String> {
|
||||||
|
let mut hm: HashMap<&str, String> = HashMap::new();
|
||||||
|
|
||||||
|
hm.insert("bold_black", theme.brightBlack[..].to_string());
|
||||||
|
hm.insert("bold_red", theme.brightRed[..].to_string());
|
||||||
|
hm.insert("bold_green", theme.brightGreen[..].to_string());
|
||||||
|
hm.insert("bold_yellow", theme.brightYellow[..].to_string());
|
||||||
|
hm.insert("bold_blue", theme.brightBlue[..].to_string());
|
||||||
|
hm.insert("bold_magenta", theme.brightPurple[..].to_string());
|
||||||
|
hm.insert("bold_cyan", theme.brightCyan[..].to_string());
|
||||||
|
hm.insert("bold_white", theme.brightWhite[..].to_string());
|
||||||
|
|
||||||
|
hm.insert("black", theme.black[..].to_string());
|
||||||
|
hm.insert("red", theme.red[..].to_string());
|
||||||
|
hm.insert("green", theme.green[..].to_string());
|
||||||
|
hm.insert("yellow", theme.yellow[..].to_string());
|
||||||
|
hm.insert("blue", theme.blue[..].to_string());
|
||||||
|
hm.insert("magenta", theme.purple[..].to_string());
|
||||||
|
hm.insert("cyan", theme.cyan[..].to_string());
|
||||||
|
hm.insert("white", theme.white[..].to_string());
|
||||||
|
|
||||||
|
// Try to make theme work with light or dark but
|
||||||
|
// flipping the foreground and background but leave
|
||||||
|
// the other colors the same.
|
||||||
|
if is_dark {
|
||||||
|
hm.insert("background", theme.black[..].to_string());
|
||||||
|
hm.insert("foreground", theme.white[..].to_string());
|
||||||
|
} else {
|
||||||
|
hm.insert("background", theme.white[..].to_string());
|
||||||
|
hm.insert("foreground", theme.black[..].to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
hm
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_list_of_theme_names() -> Vec<String> {
|
||||||
|
let asset = get_asset_by_name_as_html_themes("228_themes.zip", "228_themes.json");
|
||||||
|
|
||||||
|
// If asset doesn't work, make sure to return the default theme
|
||||||
|
let html_themes = match asset {
|
||||||
|
Ok(a) => a,
|
||||||
|
_ => HtmlThemes::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let theme_names: Vec<String> = html_themes.themes.iter().map(|n| n.name.clone()).collect();
|
||||||
|
|
||||||
|
theme_names
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_html(
|
||||||
|
input: PipelineData,
|
||||||
|
call: &Call,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let html_color = call.has_flag("html_color");
|
||||||
|
let no_color = call.has_flag("no_color");
|
||||||
|
let dark = call.has_flag("dark");
|
||||||
|
let partial = call.has_flag("partial");
|
||||||
|
let list = call.has_flag("list");
|
||||||
|
let theme: Option<Spanned<String>> = call.get_flag(engine_state, stack, "theme")?;
|
||||||
|
let config = stack.get_config()?;
|
||||||
|
|
||||||
|
let vec_of_values = input.into_iter().collect::<Vec<Value>>();
|
||||||
|
let headers = merge_descriptors(&vec_of_values);
|
||||||
|
let headers = Some(headers)
|
||||||
|
.filter(|headers| !headers.is_empty() && (headers.len() > 1 || !headers[0].is_empty()));
|
||||||
|
let mut output_string = String::new();
|
||||||
|
let mut regex_hm: HashMap<u32, (&str, String)> = HashMap::new();
|
||||||
|
|
||||||
|
if list {
|
||||||
|
// Get the list of theme names
|
||||||
|
let theme_names = get_list_of_theme_names();
|
||||||
|
|
||||||
|
// Put that list into the output string
|
||||||
|
for s in &theme_names {
|
||||||
|
writeln!(&mut output_string, "{}", s).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
output_string.push_str("\nScreenshots of themes can be found here:\n");
|
||||||
|
output_string.push_str("https://github.com/mbadolato/iTerm2-Color-Schemes\n");
|
||||||
|
} else {
|
||||||
|
let theme_span = match &theme {
|
||||||
|
Some(v) => v.span,
|
||||||
|
None => head,
|
||||||
|
};
|
||||||
|
|
||||||
|
let color_hm = get_theme_from_asset_file(dark, &theme);
|
||||||
|
let color_hm = match color_hm {
|
||||||
|
Ok(c) => c,
|
||||||
|
_ => {
|
||||||
|
return Err(ShellError::SpannedLabeledError(
|
||||||
|
"Error finding theme name".to_string(),
|
||||||
|
"Error finding theme name".to_string(),
|
||||||
|
theme_span,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// change the color of the page
|
||||||
|
if !partial {
|
||||||
|
write!(
|
||||||
|
&mut output_string,
|
||||||
|
r"<html><style>body {{ background-color:{};color:{}; }}</style><body>",
|
||||||
|
color_hm
|
||||||
|
.get("background")
|
||||||
|
.expect("Error getting background color"),
|
||||||
|
color_hm
|
||||||
|
.get("foreground")
|
||||||
|
.expect("Error getting foreground color")
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
&mut output_string,
|
||||||
|
"<div style=\"background-color:{};color:{};\">",
|
||||||
|
color_hm
|
||||||
|
.get("background")
|
||||||
|
.expect("Error getting background color"),
|
||||||
|
color_hm
|
||||||
|
.get("foreground")
|
||||||
|
.expect("Error getting foreground color")
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let inner_value = match vec_of_values.len() {
|
||||||
|
0 => String::default(),
|
||||||
|
1 => match headers {
|
||||||
|
Some(headers) => html_table(vec_of_values, headers, &config),
|
||||||
|
None => {
|
||||||
|
let value = &vec_of_values[0];
|
||||||
|
html_value(value.clone(), &config)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => match headers {
|
||||||
|
Some(headers) => html_table(vec_of_values, headers, &config),
|
||||||
|
None => html_list(vec_of_values, &config),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
output_string.push_str(&inner_value);
|
||||||
|
|
||||||
|
if !partial {
|
||||||
|
output_string.push_str("</body></html>");
|
||||||
|
} else {
|
||||||
|
output_string.push_str("</div>")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if we want to remove all color or change ansi to html colors
|
||||||
|
if html_color {
|
||||||
|
setup_html_color_regexes(&mut regex_hm, &color_hm);
|
||||||
|
output_string = run_regexes(®ex_hm, &output_string);
|
||||||
|
} else if no_color {
|
||||||
|
setup_no_color_regexes(&mut regex_hm);
|
||||||
|
output_string = run_regexes(®ex_hm, &output_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Value::string(output_string, head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn html_list(list: Vec<Value>, config: &Config) -> String {
|
||||||
|
let mut output_string = String::new();
|
||||||
|
output_string.push_str("<ol>");
|
||||||
|
for value in list {
|
||||||
|
output_string.push_str("<li>");
|
||||||
|
output_string.push_str(&html_value(value, config));
|
||||||
|
output_string.push_str("</li>");
|
||||||
|
}
|
||||||
|
output_string.push_str("</ol>");
|
||||||
|
output_string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn html_table(table: Vec<Value>, headers: Vec<String>, config: &Config) -> String {
|
||||||
|
let mut output_string = String::new();
|
||||||
|
|
||||||
|
output_string.push_str("<table>");
|
||||||
|
|
||||||
|
output_string.push_str("<tr>");
|
||||||
|
for header in &headers {
|
||||||
|
output_string.push_str("<th>");
|
||||||
|
output_string.push_str(&htmlescape::encode_minimal(header));
|
||||||
|
output_string.push_str("</th>");
|
||||||
|
}
|
||||||
|
output_string.push_str("</tr>");
|
||||||
|
|
||||||
|
for row in table {
|
||||||
|
if let Value::Record { span, .. } = row {
|
||||||
|
output_string.push_str("<tr>");
|
||||||
|
for header in &headers {
|
||||||
|
let data = row.get_data_by_key(header);
|
||||||
|
output_string.push_str("<td>");
|
||||||
|
output_string.push_str(&html_value(
|
||||||
|
data.unwrap_or_else(|| Value::nothing(span)),
|
||||||
|
config,
|
||||||
|
));
|
||||||
|
output_string.push_str("</td>");
|
||||||
|
}
|
||||||
|
output_string.push_str("</tr>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output_string.push_str("</table>");
|
||||||
|
|
||||||
|
output_string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn html_value(value: Value, config: &Config) -> String {
|
||||||
|
let mut output_string = String::new();
|
||||||
|
match value {
|
||||||
|
Value::Binary { val, .. } => {
|
||||||
|
let output = pretty_hex::pretty_hex(&val);
|
||||||
|
output_string.push_str("<pre>");
|
||||||
|
output_string.push_str(&output);
|
||||||
|
output_string.push_str("</pre>");
|
||||||
|
}
|
||||||
|
other => output_string.push_str(
|
||||||
|
&htmlescape::encode_minimal(&other.into_abbreviated_string(config))
|
||||||
|
.replace("\n", "<br>"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
output_string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_html_color_regexes(
|
||||||
|
hash: &mut HashMap<u32, (&'static str, String)>,
|
||||||
|
color_hm: &HashMap<&str, String>,
|
||||||
|
) {
|
||||||
|
// All the bold colors
|
||||||
|
hash.insert(
|
||||||
|
0,
|
||||||
|
(
|
||||||
|
r"(?P<reset>\[0m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
// Reset the text color, normal weight font
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:normal;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("foreground")
|
||||||
|
.expect("Error getting reset text color")
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
1,
|
||||||
|
(
|
||||||
|
// Bold Black
|
||||||
|
r"(?P<bb>\[1;30m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("foreground")
|
||||||
|
.expect("Error getting bold black text color")
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
2,
|
||||||
|
(
|
||||||
|
// Bold Red
|
||||||
|
r"(?P<br>\[1;31m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("bold_red")
|
||||||
|
.expect("Error getting bold red text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
3,
|
||||||
|
(
|
||||||
|
// Bold Green
|
||||||
|
r"(?P<bg>\[1;32m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("bold_green")
|
||||||
|
.expect("Error getting bold green text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
4,
|
||||||
|
(
|
||||||
|
// Bold Yellow
|
||||||
|
r"(?P<by>\[1;33m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("bold_yellow")
|
||||||
|
.expect("Error getting bold yellow text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
5,
|
||||||
|
(
|
||||||
|
// Bold Blue
|
||||||
|
r"(?P<bu>\[1;34m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("bold_blue")
|
||||||
|
.expect("Error getting bold blue text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
6,
|
||||||
|
(
|
||||||
|
// Bold Magenta
|
||||||
|
r"(?P<bm>\[1;35m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("bold_magenta")
|
||||||
|
.expect("Error getting bold magenta text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
7,
|
||||||
|
(
|
||||||
|
// Bold Cyan
|
||||||
|
r"(?P<bc>\[1;36m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("bold_cyan")
|
||||||
|
.expect("Error getting bold cyan text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
8,
|
||||||
|
(
|
||||||
|
// Bold White
|
||||||
|
// Let's change this to black since the html background
|
||||||
|
// is white. White on white = no bueno.
|
||||||
|
r"(?P<bw>\[1;37m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};font-weight:bold;'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("foreground")
|
||||||
|
.expect("Error getting bold bold white text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// All the normal colors
|
||||||
|
hash.insert(
|
||||||
|
9,
|
||||||
|
(
|
||||||
|
// Black
|
||||||
|
r"(?P<b>\[30m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("foreground")
|
||||||
|
.expect("Error getting black text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
10,
|
||||||
|
(
|
||||||
|
// Red
|
||||||
|
r"(?P<r>\[31m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm.get("red").expect("Error getting red text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
11,
|
||||||
|
(
|
||||||
|
// Green
|
||||||
|
r"(?P<g>\[32m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("green")
|
||||||
|
.expect("Error getting green text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
12,
|
||||||
|
(
|
||||||
|
// Yellow
|
||||||
|
r"(?P<y>\[33m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("yellow")
|
||||||
|
.expect("Error getting yellow text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
13,
|
||||||
|
(
|
||||||
|
// Blue
|
||||||
|
r"(?P<u>\[34m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm.get("blue").expect("Error getting blue text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
14,
|
||||||
|
(
|
||||||
|
// Magenta
|
||||||
|
r"(?P<m>\[35m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("magenta")
|
||||||
|
.expect("Error getting magenta text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
15,
|
||||||
|
(
|
||||||
|
// Cyan
|
||||||
|
r"(?P<c>\[36m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm.get("cyan").expect("Error getting cyan text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
hash.insert(
|
||||||
|
16,
|
||||||
|
(
|
||||||
|
// White
|
||||||
|
// Let's change this to black since the html background
|
||||||
|
// is white. White on white = no bueno.
|
||||||
|
r"(?P<w>\[37m)(?P<word>[[:alnum:][:space:][:punct:]]*)",
|
||||||
|
format!(
|
||||||
|
r"<span style='color:{};'>$word</span>",
|
||||||
|
color_hm
|
||||||
|
.get("foreground")
|
||||||
|
.expect("Error getting white text color"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_no_color_regexes(hash: &mut HashMap<u32, (&'static str, String)>) {
|
||||||
|
// 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".to_string(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_regexes(hash: &HashMap<u32, (&'static str, String)>, 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::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(ToHtml {})
|
||||||
|
}
|
||||||
|
}
|
443
crates/nu-command/src/formats/to/md.rs
Normal file
443
crates/nu-command/src/formats/to/md.rs
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
use crate::formats::to::delimited::merge_descriptors;
|
||||||
|
use indexmap::map::IndexMap;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ToMd;
|
||||||
|
|
||||||
|
impl Command for ToMd {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"to md"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("to md")
|
||||||
|
.switch(
|
||||||
|
"pretty",
|
||||||
|
"Formats the Markdown table to vertically align items",
|
||||||
|
Some('p'),
|
||||||
|
)
|
||||||
|
.switch(
|
||||||
|
"per-element",
|
||||||
|
"treat each row as markdown syntax element",
|
||||||
|
Some('e'),
|
||||||
|
)
|
||||||
|
.category(Category::Formats)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Convert table into simple Markdown"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Outputs an MD string representing the contents of this table",
|
||||||
|
example: "[[foo bar]; [1 2]] | to md",
|
||||||
|
result: Some(Value::test_string("|foo|bar|\n|-|-|\n|1|2|\n")),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Optionally, output a formatted markdown string",
|
||||||
|
example: "[[foo bar]; [1 2]] | to md --pretty",
|
||||||
|
result: Some(Value::test_string(
|
||||||
|
"| foo | bar |\n| --- | --- |\n| 1 | 2 |\n",
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Treat each row as a markdown element",
|
||||||
|
example: r#"[{"H1": "Welcome to Nushell" } [[foo bar]; [1 2]]] | to md --per-element --pretty"#,
|
||||||
|
result: Some(Value::test_string(
|
||||||
|
"# Welcome to Nushell\n| foo | bar |\n| --- | --- |\n| 1 | 2 |",
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, ShellError> {
|
||||||
|
let head = call.head;
|
||||||
|
let pretty = call.has_flag("pretty");
|
||||||
|
let per_element = call.has_flag("per-element");
|
||||||
|
let config = stack.get_config()?;
|
||||||
|
to_md(input, pretty, per_element, config, head)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_md(
|
||||||
|
input: PipelineData,
|
||||||
|
pretty: bool,
|
||||||
|
per_element: bool,
|
||||||
|
config: Config,
|
||||||
|
head: Span,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let (grouped_input, single_list) = group_by(input, head, &config);
|
||||||
|
if per_element || single_list {
|
||||||
|
return Ok(Value::string(
|
||||||
|
grouped_input
|
||||||
|
.into_iter()
|
||||||
|
.map(move |val| match val {
|
||||||
|
Value::List { .. } => table(val.into_pipeline_data(), pretty, &config),
|
||||||
|
other => fragment(other, pretty, &config),
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(""),
|
||||||
|
head,
|
||||||
|
)
|
||||||
|
.into_pipeline_data());
|
||||||
|
}
|
||||||
|
Ok(Value::string(table(grouped_input, pretty, &config), head).into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fragment(input: Value, pretty: bool, config: &Config) -> String {
|
||||||
|
let headers = match input {
|
||||||
|
Value::Record { ref cols, .. } => cols.to_owned(),
|
||||||
|
_ => vec![],
|
||||||
|
};
|
||||||
|
let mut out = String::new();
|
||||||
|
|
||||||
|
if headers.len() == 1 {
|
||||||
|
let markup = match (&headers[0]).to_ascii_lowercase().as_ref() {
|
||||||
|
"h1" => "# ".to_string(),
|
||||||
|
"h2" => "## ".to_string(),
|
||||||
|
"h3" => "### ".to_string(),
|
||||||
|
"blockquote" => "> ".to_string(),
|
||||||
|
|
||||||
|
_ => return table(input.into_pipeline_data(), pretty, config),
|
||||||
|
};
|
||||||
|
|
||||||
|
out.push_str(&markup);
|
||||||
|
let data = match input.get_data_by_key(&headers[0]) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => input,
|
||||||
|
};
|
||||||
|
out.push_str(&data.into_string("|", config));
|
||||||
|
} else if let Value::Record { .. } = input {
|
||||||
|
out = table(input.into_pipeline_data(), pretty, config)
|
||||||
|
} else {
|
||||||
|
out = input.into_string("|", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push('\n');
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_headers(headers: &[String]) -> (Vec<String>, Vec<usize>) {
|
||||||
|
let mut escaped_headers: Vec<String> = Vec::new();
|
||||||
|
let mut column_widths: Vec<usize> = Vec::new();
|
||||||
|
|
||||||
|
if !headers.is_empty() && (headers.len() > 1 || !headers[0].is_empty()) {
|
||||||
|
for header in headers {
|
||||||
|
let escaped_header_string = htmlescape::encode_minimal(header);
|
||||||
|
column_widths.push(escaped_header_string.len());
|
||||||
|
escaped_headers.push(escaped_header_string);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
column_widths = vec![0; headers.len()]
|
||||||
|
}
|
||||||
|
|
||||||
|
(escaped_headers, column_widths)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table(input: PipelineData, pretty: bool, config: &Config) -> String {
|
||||||
|
let vec_of_values = input.into_iter().collect::<Vec<Value>>();
|
||||||
|
let headers = merge_descriptors(&vec_of_values);
|
||||||
|
|
||||||
|
let (escaped_headers, mut column_widths) = collect_headers(&headers);
|
||||||
|
|
||||||
|
let mut escaped_rows: Vec<Vec<String>> = Vec::new();
|
||||||
|
|
||||||
|
for row in vec_of_values {
|
||||||
|
let mut escaped_row: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
match row.to_owned() {
|
||||||
|
Value::Record { span, .. } => {
|
||||||
|
for i in 0..headers.len() {
|
||||||
|
let data = row.get_data_by_key(&headers[i]);
|
||||||
|
let value_string = data
|
||||||
|
.unwrap_or_else(|| Value::nothing(span))
|
||||||
|
.into_string("|", config);
|
||||||
|
let new_column_width = value_string.len();
|
||||||
|
|
||||||
|
escaped_row.push(value_string);
|
||||||
|
|
||||||
|
if column_widths[i] < new_column_width {
|
||||||
|
column_widths[i] = new_column_width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p => {
|
||||||
|
let value_string = htmlescape::encode_minimal(&p.into_abbreviated_string(config));
|
||||||
|
escaped_row.push(value_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
escaped_rows.push(escaped_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output_string = if (column_widths.is_empty() || column_widths.iter().all(|x| *x == 0))
|
||||||
|
&& escaped_rows.is_empty()
|
||||||
|
{
|
||||||
|
String::from("")
|
||||||
|
} else {
|
||||||
|
get_output_string(&escaped_headers, &escaped_rows, &column_widths, pretty)
|
||||||
|
.trim()
|
||||||
|
.to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
output_string
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineData, bool) {
|
||||||
|
let mut lists = IndexMap::new();
|
||||||
|
let mut single_list = false;
|
||||||
|
for val in values {
|
||||||
|
if let Value::Record { ref cols, .. } = val {
|
||||||
|
lists
|
||||||
|
.entry(cols.concat())
|
||||||
|
.and_modify(|v: &mut Vec<Value>| v.push(val.clone()))
|
||||||
|
.or_insert_with(|| vec![val.clone()]);
|
||||||
|
} else {
|
||||||
|
lists
|
||||||
|
.entry(val.clone().into_string(",", config))
|
||||||
|
.and_modify(|v: &mut Vec<Value>| v.push(val.clone()))
|
||||||
|
.or_insert_with(|| vec![val.clone()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut output = vec![];
|
||||||
|
for (_, mut value) in lists {
|
||||||
|
if value.len() == 1 {
|
||||||
|
output.push(value.pop().unwrap_or_else(|| Value::nothing(head)))
|
||||||
|
} else {
|
||||||
|
output.push(Value::List {
|
||||||
|
vals: value.to_vec(),
|
||||||
|
span: head,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if output.len() == 1 {
|
||||||
|
single_list = true;
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Value::List {
|
||||||
|
vals: output,
|
||||||
|
span: head,
|
||||||
|
}
|
||||||
|
.into_pipeline_data(),
|
||||||
|
single_list,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_output_string(
|
||||||
|
headers: &[String],
|
||||||
|
rows: &[Vec<String>],
|
||||||
|
column_widths: &[usize],
|
||||||
|
pretty: bool,
|
||||||
|
) -> String {
|
||||||
|
let mut output_string = String::new();
|
||||||
|
|
||||||
|
if !headers.is_empty() {
|
||||||
|
output_string.push('|');
|
||||||
|
|
||||||
|
for i in 0..headers.len() {
|
||||||
|
if pretty {
|
||||||
|
output_string.push(' ');
|
||||||
|
output_string.push_str(&get_padded_string(
|
||||||
|
headers[i].clone(),
|
||||||
|
column_widths[i],
|
||||||
|
' ',
|
||||||
|
));
|
||||||
|
output_string.push(' ');
|
||||||
|
} else {
|
||||||
|
output_string.push_str(&headers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
output_string.push('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
output_string.push_str("\n|");
|
||||||
|
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
for i in 0..headers.len() {
|
||||||
|
if pretty {
|
||||||
|
output_string.push(' ');
|
||||||
|
output_string.push_str(&get_padded_string(
|
||||||
|
String::from("-"),
|
||||||
|
column_widths[i],
|
||||||
|
'-',
|
||||||
|
));
|
||||||
|
output_string.push(' ');
|
||||||
|
} else {
|
||||||
|
output_string.push('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
output_string.push('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
output_string.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
for row in rows {
|
||||||
|
if !headers.is_empty() {
|
||||||
|
output_string.push('|');
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..row.len() {
|
||||||
|
if pretty {
|
||||||
|
output_string.push(' ');
|
||||||
|
output_string.push_str(&get_padded_string(row[i].clone(), column_widths[i], ' '));
|
||||||
|
output_string.push(' ');
|
||||||
|
} else {
|
||||||
|
output_string.push_str(&row[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !headers.is_empty() {
|
||||||
|
output_string.push('|');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output_string.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
output_string
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_padded_string(text: String, desired_length: usize, padding_character: char) -> String {
|
||||||
|
let repeat_length = if text.len() > desired_length {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
desired_length - text.len()
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{}{}",
|
||||||
|
text,
|
||||||
|
padding_character.to_string().repeat(repeat_length)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use nu_protocol::{Config, IntoPipelineData, Span, Value};
|
||||||
|
|
||||||
|
fn one(string: &str) -> String {
|
||||||
|
string
|
||||||
|
.lines()
|
||||||
|
.skip(1)
|
||||||
|
.map(|line| line.trim())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join("\n")
|
||||||
|
.trim_end()
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(ToMd {})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_h1() {
|
||||||
|
let value = Value::Record {
|
||||||
|
cols: vec!["H1".to_string()],
|
||||||
|
vals: vec![Value::test_string("Ecuador")],
|
||||||
|
span: Span::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(fragment(value, false, &Config::default()), "# Ecuador\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_h2() {
|
||||||
|
let value = Value::Record {
|
||||||
|
cols: vec!["H2".to_string()],
|
||||||
|
vals: vec![Value::test_string("Ecuador")],
|
||||||
|
span: Span::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(fragment(value, false, &Config::default()), "## Ecuador\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_h3() {
|
||||||
|
let value = Value::Record {
|
||||||
|
cols: vec!["H3".to_string()],
|
||||||
|
vals: vec![Value::test_string("Ecuador")],
|
||||||
|
span: Span::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(fragment(value, false, &Config::default()), "### Ecuador\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_blockquote() {
|
||||||
|
let value = Value::Record {
|
||||||
|
cols: vec!["BLOCKQUOTE".to_string()],
|
||||||
|
vals: vec![Value::test_string("Ecuador")],
|
||||||
|
span: Span::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(fragment(value, false, &Config::default()), "> Ecuador\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn render_table() {
|
||||||
|
let value = Value::List {
|
||||||
|
vals: vec![
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["country".to_string()],
|
||||||
|
vals: vec![Value::test_string("Ecuador")],
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["country".to_string()],
|
||||||
|
vals: vec![Value::test_string("New Zealand")],
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["country".to_string()],
|
||||||
|
vals: vec![Value::test_string("USA")],
|
||||||
|
span: Span::unknown(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: Span::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table(
|
||||||
|
value.clone().into_pipeline_data(),
|
||||||
|
false,
|
||||||
|
&Config::default()
|
||||||
|
),
|
||||||
|
one(r#"
|
||||||
|
|country|
|
||||||
|
|-|
|
||||||
|
|Ecuador|
|
||||||
|
|New Zealand|
|
||||||
|
|USA|
|
||||||
|
"#)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
table(value.clone().into_pipeline_data(), true, &Config::default()),
|
||||||
|
one(r#"
|
||||||
|
| country |
|
||||||
|
| ----------- |
|
||||||
|
| Ecuador |
|
||||||
|
| New Zealand |
|
||||||
|
| USA |
|
||||||
|
"#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,9 @@
|
|||||||
mod command;
|
mod command;
|
||||||
mod csv;
|
mod csv;
|
||||||
mod delimited;
|
mod delimited;
|
||||||
|
mod html;
|
||||||
mod json;
|
mod json;
|
||||||
|
mod md;
|
||||||
mod toml;
|
mod toml;
|
||||||
mod tsv;
|
mod tsv;
|
||||||
mod url;
|
mod url;
|
||||||
@ -10,5 +12,7 @@ pub use self::csv::ToCsv;
|
|||||||
pub use self::toml::ToToml;
|
pub use self::toml::ToToml;
|
||||||
pub use self::url::ToUrl;
|
pub use self::url::ToUrl;
|
||||||
pub use command::To;
|
pub use command::To;
|
||||||
|
pub use html::ToHtml;
|
||||||
pub use json::ToJson;
|
pub use json::ToJson;
|
||||||
|
pub use md::ToMd;
|
||||||
pub use tsv::ToTsv;
|
pub use tsv::ToTsv;
|
||||||
|
@ -391,11 +391,18 @@ impl Value {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
Value::String { val, .. } => val,
|
Value::String { val, .. } => val,
|
||||||
Value::List { vals: val, .. } => format!(
|
Value::List { ref vals, .. } => match &vals[..] {
|
||||||
"[list {} item{}]",
|
[Value::Record { .. }, _end @ ..] => format!(
|
||||||
val.len(),
|
"[table {} row{}]",
|
||||||
if val.len() == 1 { "" } else { "s" }
|
vals.len(),
|
||||||
|
if vals.len() == 1 { "" } else { "s" }
|
||||||
),
|
),
|
||||||
|
_ => format!(
|
||||||
|
"[list {} item{}]",
|
||||||
|
vals.len(),
|
||||||
|
if vals.len() == 1 { "" } else { "s" }
|
||||||
|
),
|
||||||
|
},
|
||||||
Value::Record { cols, .. } => format!(
|
Value::Record { cols, .. } => format!(
|
||||||
"{{record {} field{}}}",
|
"{{record {} field{}}}",
|
||||||
cols.len(),
|
cols.len(),
|
||||||
|
Loading…
Reference in New Issue
Block a user