diff --git a/crates/nu-cli/src/commands/to_md.rs b/crates/nu-cli/src/commands/to_md.rs index 8ea36f14e..e419a1400 100644 --- a/crates/nu-cli/src/commands/to_md.rs +++ b/crates/nu-cli/src/commands/to_md.rs @@ -7,6 +7,11 @@ use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value}; pub struct ToMarkdown; +#[derive(Deserialize)] +pub struct ToMarkdownArgs { + pretty: bool, +} + #[async_trait] impl WholeStreamCommand for ToMarkdown { fn name(&self) -> &str { @@ -14,7 +19,11 @@ impl WholeStreamCommand for ToMarkdown { } fn signature(&self) -> Signature { - Signature::build("to md") + Signature::build("to md").switch( + "pretty", + "Formats the Markdown table to vertically align items", + Some('p'), + ) } fn usage(&self) -> &str { @@ -28,27 +37,84 @@ impl WholeStreamCommand for ToMarkdown { ) -> Result { to_md(args, registry).await } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Outputs an unformatted md string representing the contents of ls", + example: "ls | to md", + result: None, + }, + Example { + description: "Outputs a formatted md string representing the contents of ls", + example: "ls | to md -p", + result: None, + }, + ] + } } async fn to_md(args: CommandArgs, 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 (ToMarkdownArgs { pretty }, input) = args.process(®istry).await?; + let input: Vec = input.collect().await; let headers = nu_protocol::merge_descriptors(&input); let mut output_string = String::new(); + let mut column_length_vector: Vec = Vec::new(); + + if pretty { + if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") { + for header in &headers { + let htmlescape_header_string = &htmlescape::encode_minimal(&header); + column_length_vector.push(htmlescape_header_string.len()); + } + } + + for row in &input { + if let UntaggedValue::Row(row) = row.value.clone() { + for i in 0..headers.len() { + let data = row.get_data(&headers[i]); + let new_column_length = format_leaf(data.borrow()).plain_string(100_000).len(); + + if column_length_vector[i] < new_column_length { + column_length_vector[i] = new_column_length; + } + } + } + } + } + if !headers.is_empty() && (headers.len() > 1 || headers[0] != "") { output_string.push_str("|"); - for header in &headers { - output_string.push_str(&htmlescape::encode_minimal(&header)); + + for i in 0..headers.len() { + let htmlescape_string = htmlescape::encode_minimal(&headers[i]); + let final_string = if pretty { + get_padded_string(htmlescape_string, column_length_vector[i], ' ') + } else { + htmlescape_string + }; + + output_string.push_str(&final_string); output_string.push_str("|"); } + output_string.push_str("\n|"); - for _ in &headers { - output_string.push_str("-"); + + #[allow(clippy::needless_range_loop)] + for i in 0..headers.len() { + let final_string = if pretty { + "-".repeat(column_length_vector[i]) + } else { + String::from("-") + }; + + output_string.push_str(final_string.as_str()); output_string.push_str("|"); } + output_string.push_str("\n"); } @@ -56,11 +122,20 @@ async fn to_md(args: CommandArgs, registry: &CommandRegistry) -> Result { output_string.push_str("|"); - for header in &headers { - let data = row.get_data(header); - output_string.push_str(&format_leaf(data.borrow()).plain_string(100_000)); + + for i in 0..headers.len() { + let data = row.get_data(&headers[i]); + let leaf_string = format_leaf(data.borrow()).plain_string(100_000); + let final_string = if pretty { + get_padded_string(leaf_string, column_length_vector[i], ' ') + } else { + leaf_string + }; + + output_string.push_str(&final_string); output_string.push_str("|"); } + output_string.push_str("\n"); } p => { @@ -77,6 +152,11 @@ async fn to_md(args: CommandArgs, registry: &CommandRegistry) -> Result String { + let padding_length = desired_length - text.len(); + return format!("{}{}", text, character.to_string().repeat(padding_length)); +} + #[cfg(test)] mod tests { use super::ShellError; diff --git a/crates/nu-cli/tests/format_conversions/markdown.rs b/crates/nu-cli/tests/format_conversions/markdown.rs index cdcf4f4f4..fa2c92695 100644 --- a/crates/nu-cli/tests/format_conversions/markdown.rs +++ b/crates/nu-cli/tests/format_conversions/markdown.rs @@ -23,3 +23,15 @@ fn out_md_table() { assert_eq!(actual.out, "|name||-||jason|"); } + +#[test] +fn out_md_table_pretty() { + let actual = nu!( + cwd: ".", pipeline( + r#" + echo '{"name": "joseph"}' | from json | to md -p + "# + )); + + assert_eq!(actual.out, "|name ||------||joseph|"); +} diff --git a/docs/commands/to-md.md b/docs/commands/to-md.md new file mode 100644 index 000000000..c3fe24978 --- /dev/null +++ b/docs/commands/to-md.md @@ -0,0 +1,67 @@ +# to md + +Convert table into simple Markdown. + +## Flags + +* `-p`, `--pretty`: Formats the Markdown table to vertically align items + +## Example + +```shell +> ls | to md +|name|type|size|modified| +|-|-|-|-| +|CODE_OF_CONDUCT.md|File|3.4 KB|2 months ago| +|CONTRIBUTING.md|File|1.4 KB|1 month ago| +|Cargo.lock|File|144.4 KB|2 days ago| +|Cargo.toml|File|6.0 KB|2 days ago| +|LICENSE|File|1.1 KB|2 months ago| +|Makefile.toml|File|449 B|2 months ago| +|README.build.txt|File|192 B|2 months ago| +|README.md|File|15.9 KB|1 month ago| +|TODO.md|File|0 B|2 months ago| +|crates|Dir|896 B|2 days ago| +|debian|Dir|352 B|2 months ago| +|docker|Dir|288 B|1 month ago| +|docs|Dir|256 B|1 month ago| +|features.toml|File|632 B|2 months ago| +|images|Dir|160 B|2 months ago| +|pkg_mgrs|Dir|96 B|1 month ago| +|rustfmt.toml|File|16 B|9 months ago| +|samples|Dir|96 B|1 month ago| +|src|Dir|128 B|2 days ago| +|target|Dir|160 B|1 month ago| +|tests|Dir|192 B|2 months ago| +|wix|Dir|128 B|23 hours ago| +``` + +If we provide the `-p` flag, we can obtain a formatted version of the Markdown table + +```shell +> ls | to md -p +|name |type|size |modified | +|------------------|----|--------|------------| +|CODE_OF_CONDUCT.md|File|3.4 KB |2 months ago| +|CONTRIBUTING.md |File|1.4 KB |1 month ago | +|Cargo.lock |File|144.4 KB|2 days ago | +|Cargo.toml |File|6.0 KB |2 days ago | +|LICENSE |File|1.1 KB |2 months ago| +|Makefile.toml |File|449 B |2 months ago| +|README.build.txt |File|192 B |2 months ago| +|README.md |File|15.9 KB |1 month ago | +|TODO.md |File|0 B |2 months ago| +|crates |Dir |896 B |2 days ago | +|debian |Dir |352 B |2 months ago| +|docker |Dir |288 B |1 month ago | +|docs |Dir |256 B |1 month ago | +|features.toml |File|632 B |2 months ago| +|images |Dir |160 B |2 months ago| +|pkg_mgrs |Dir |96 B |1 month ago | +|rustfmt.toml |File|16 B |9 months ago| +|samples |Dir |96 B |1 month ago | +|src |Dir |128 B |2 days ago | +|target |Dir |160 B |1 month ago | +|tests |Dir |192 B |2 months ago| +|wix |Dir |128 B |23 hours ago| +``` diff --git a/docs/commands/to.md b/docs/commands/to.md index 442a2a670..64639afce 100644 --- a/docs/commands/to.md +++ b/docs/commands/to.md @@ -8,7 +8,7 @@ Converts table data into a string or binary. The target format is specified as a * [to csv](to-csv.md) * to html * [to json](to-json.md) -* to md +* [to md](to-md.md) * to sqlite * [to toml](to-toml.md) * [to tsv](to-tsv.md)