From b6ce907928d872957e3b47f95a9ccda80f1eed9c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Wed, 20 Nov 2024 00:31:28 +0300 Subject: [PATCH 1/8] =?UTF-8?q?nu-table/=20Do=20footer=5Finheritance=20by?= =?UTF-8?q?=20accouting=20for=20rows=20rather=20then=20a=20f=E2=80=A6=20(#?= =?UTF-8?q?14380)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit So it's my take on the comments in #14060 The change could be seen in this test. Looks like it works :) but I haven't done a lot of testing. https://github.com/zhiburt/nushell/blob/0b1af774157c182675e471e67c184f6080721794/crates/nu-command/tests/commands/table.rs#L3032-L3062 ```nushell $env.config.table.footer_inheritance = true; $env.config.footer_mode = 7; [[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4] [5]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80 ``` ```text ╭───┬──────┬───────────────────────╮ │ # │ a │ b │ ├───┼──────┼───────────────────────┤ │ 0 │ kv │ ╭───┬───────────────╮ │ │ │ │ │ │ ╭───┬───────╮ │ │ │ │ │ │ 0 │ │ # │ field │ │ │ │ │ │ │ │ ├───┼───────┤ │ │ │ │ │ │ │ │ 0 │ 0 │ │ │ │ │ │ │ │ │ 1 │ 1 │ │ │ │ │ │ │ │ │ 2 │ 2 │ │ │ │ │ │ │ │ │ 3 │ 3 │ │ │ │ │ │ │ │ │ 4 │ 4 │ │ │ │ │ │ │ │ │ 5 │ 5 │ │ │ │ │ │ │ │ ╰───┴───────╯ │ │ │ │ │ ╰───┴───────────────╯ │ │ 1 │ data │ 0 │ │ 2 │ data │ 0 │ ├───┼──────┼───────────────────────┤ │ # │ a │ b │ ╰───┴──────┴───────────────────────╯ ``` Maybe it will also solve the issue you @fdncred encountered. close #14060 cc: @NotTheDr01ds --- crates/nu-command/src/viewers/table.rs | 2 +- crates/nu-command/tests/commands/table.rs | 120 ++++++++++++++++++++++ crates/nu-table/src/common.rs | 8 +- crates/nu-table/src/table.rs | 4 + crates/nu-table/src/types/expanded.rs | 57 +++++----- crates/nu-table/src/types/general.rs | 13 ++- crates/nu-table/src/types/mod.rs | 31 +----- 7 files changed, 170 insertions(+), 65 deletions(-) diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index a9e7361a9e..50db949ced 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -1088,7 +1088,7 @@ fn create_empty_placeholder( let data = vec![vec![cell]]; let mut table = NuTable::from(data); table.set_data_style(TextStyle::default().dimmed()); - let out = TableOutput::new(table, false, false, false); + let out = TableOutput::new(table, false, false, 1); let style_computer = &StyleComputer::from_config(engine_state, stack); let config = create_nu_table_config(&config, style_computer, &out, false, TableMode::default()); diff --git a/crates/nu-command/tests/commands/table.rs b/crates/nu-command/tests/commands/table.rs index 96ae395e47..b9b80b9e87 100644 --- a/crates/nu-command/tests/commands/table.rs +++ b/crates/nu-command/tests/commands/table.rs @@ -2941,3 +2941,123 @@ fn table_footer_inheritance() { assert_eq!(actual.out.match_indices("x2").count(), 1); assert_eq!(actual.out.match_indices("x3").count(), 1); } + +#[test] +fn table_footer_inheritance_kv_rows() { + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────┤\ + │ 0 │ kv │ ╭───┬───╮ │\ + │ │ │ │ 0 │ 0 │ │\ + │ │ │ │ 1 │ 1 │ │\ + │ │ │ │ 2 │ 2 │ │\ + │ │ │ │ 3 │ 3 │ │\ + │ │ │ │ 4 │ 4 │ │\ + │ │ │ ╰───┴───╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ╰───┴──────┴───────────╯" + ); + + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────┤\ + │ 0 │ kv │ ╭───┬───╮ │\ + │ │ │ │ 0 │ 0 │ │\ + │ │ │ │ 1 │ 1 │ │\ + │ │ │ │ 2 │ 2 │ │\ + │ │ │ │ 3 │ 3 │ │\ + │ │ │ │ 4 │ 4 │ │\ + │ │ │ │ 5 │ 5 │ │\ + │ │ │ ╰───┴───╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ├───┼──────┼───────────┤\ + │ # │ a │ b │\ + ╰───┴──────┴───────────╯" + ); +} + +#[test] +fn table_footer_inheritance_list_rows() { + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────────────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────────────────┤\ + │ 0 │ kv │ ╭───┬───────────────╮ │\ + │ │ │ │ │ ╭───┬───────╮ │ │\ + │ │ │ │ 0 │ │ # │ field │ │ │\ + │ │ │ │ │ ├───┼───────┤ │ │\ + │ │ │ │ │ │ 0 │ 0 │ │ │\ + │ │ │ │ │ │ 1 │ 1 │ │ │\ + │ │ │ │ │ │ 2 │ 2 │ │ │\ + │ │ │ │ │ │ 3 │ 3 │ │ │\ + │ │ │ │ │ │ 4 │ 4 │ │ │\ + │ │ │ │ │ ╰───┴───────╯ │ │\ + │ │ │ ╰───┴───────────────╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ╰───┴──────┴───────────────────────╯" + ); + + let actual = nu!( + concat!( + "$env.config.table.footer_inheritance = true;", + "$env.config.footer_mode = 7;", + "[[a b]; ['kv' {0: [[field]; [0] [1] [2] [3] [4] [5]]} ], ['data' 0], ['data' 0] ] | table --expand --width=80", + ) + ); + + assert_eq!( + actual.out, + "╭───┬──────┬───────────────────────╮\ + │ # │ a │ b │\ + ├───┼──────┼───────────────────────┤\ + │ 0 │ kv │ ╭───┬───────────────╮ │\ + │ │ │ │ │ ╭───┬───────╮ │ │\ + │ │ │ │ 0 │ │ # │ field │ │ │\ + │ │ │ │ │ ├───┼───────┤ │ │\ + │ │ │ │ │ │ 0 │ 0 │ │ │\ + │ │ │ │ │ │ 1 │ 1 │ │ │\ + │ │ │ │ │ │ 2 │ 2 │ │ │\ + │ │ │ │ │ │ 3 │ 3 │ │ │\ + │ │ │ │ │ │ 4 │ 4 │ │ │\ + │ │ │ │ │ │ 5 │ 5 │ │ │\ + │ │ │ │ │ ╰───┴───────╯ │ │\ + │ │ │ ╰───┴───────────────╯ │\ + │ 1 │ data │ 0 │\ + │ 2 │ data │ 0 │\ + ├───┼──────┼───────────────────────┤\ + │ # │ a │ b │\ + ╰───┴──────┴───────────────────────╯" + ); +} diff --git a/crates/nu-table/src/common.rs b/crates/nu-table/src/common.rs index d18e053a1c..13c8f84fbc 100644 --- a/crates/nu-table/src/common.rs +++ b/crates/nu-table/src/common.rs @@ -18,8 +18,12 @@ pub fn create_nu_table_config( expand: bool, mode: TableMode, ) -> NuTableConfig { - let with_footer = (config.table.footer_inheritance && out.with_footer) - || with_footer(config, out.with_header, out.table.count_rows()); + let mut count_rows = out.table.count_rows(); + if config.table.footer_inheritance { + count_rows = out.count_rows; + } + + let with_footer = with_footer(config, out.with_header, count_rows); NuTableConfig { theme: load_theme(mode), diff --git a/crates/nu-table/src/table.rs b/crates/nu-table/src/table.rs index 971e5b1f91..bf75e14380 100644 --- a/crates/nu-table/src/table.rs +++ b/crates/nu-table/src/table.rs @@ -615,12 +615,15 @@ fn load_theme( if let Some(style) = sep_color { let color = convert_style(style); let color = ANSIBuf::from(color); + // todo: use .modify(Segment::all(), color) --> it has this optimization table.get_config_mut().set_border_color_default(color); } if !with_header { + // todo: remove and use theme.remove_horizontal_lines(); table.with(RemoveHorizontalLine); } else if with_footer { + // todo: remove and set it on theme rather then here... table.with(CopyFirstHorizontalLineAtLast); } } @@ -1257,6 +1260,7 @@ fn remove_row(recs: &mut NuRecords, row: usize) -> Vec { columns } +// todo; use Format? struct StripColorFromRow(usize); impl TableOption> for StripColorFromRow { diff --git a/crates/nu-table/src/types/expanded.rs b/crates/nu-table/src/types/expanded.rs index a0b6cd0ff2..d87af74522 100644 --- a/crates/nu-table/src/types/expanded.rs +++ b/crates/nu-table/src/types/expanded.rs @@ -5,7 +5,7 @@ use crate::{ NuText, StringResult, TableResult, INDEX_COLUMN_NAME, }, string_width, - types::{has_footer, has_index}, + types::has_index, NuTable, NuTableCell, TableOpts, TableOutput, }; use nu_color_config::{Alignment, StyleComputer, TextStyle}; @@ -63,22 +63,22 @@ struct Cfg<'a> { struct CellOutput { text: String, style: TextStyle, - is_big: bool, + size: usize, is_expanded: bool, } impl CellOutput { - fn new(text: String, style: TextStyle, is_big: bool, is_expanded: bool) -> Self { + fn new(text: String, style: TextStyle, size: usize, is_expanded: bool) -> Self { Self { text, style, - is_big, + size, is_expanded, } } - fn clean(text: String, is_big: bool, is_expanded: bool) -> Self { - Self::new(text, Default::default(), is_big, is_expanded) + fn clean(text: String, size: usize, is_expanded: bool) -> Self { + Self::new(text, Default::default(), size, is_expanded) } fn text(text: String) -> Self { @@ -86,7 +86,7 @@ impl CellOutput { } fn styled(text: NuText) -> Self { - Self::new(text.0, text.1, false, false) + Self::new(text.0, text.1, 1, false) } } @@ -117,7 +117,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { let with_index = has_index(&cfg.opts, &headers); let row_offset = cfg.opts.index_offset; - let mut is_footer_used = false; + let mut rows_count = 0usize; // The header with the INDEX is removed from the table headers since // it is added to the natural table index @@ -199,9 +199,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { data[row].push(value); data_styles.insert((row, with_index as usize), cell.style); - if cell.is_big { - is_footer_used = cell.is_big; - } + rows_count = rows_count.saturating_add(cell.size); } let mut table = NuTable::from(data); @@ -209,12 +207,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { table.set_index_style(get_index_style(cfg.opts.style_computer)); set_data_styles(&mut table, data_styles); - return Ok(Some(TableOutput::new( - table, - false, - with_index, - is_footer_used, - ))); + return Ok(Some(TableOutput::new(table, false, with_index, rows_count))); } if !headers.is_empty() { @@ -269,6 +262,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { } } + let mut column_rows = 0usize; + for (row, item) in input.iter().enumerate() { cfg.opts.signals.check(cfg.opts.span)?; @@ -294,9 +289,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { data[row + 1].push(value); data_styles.insert((row + 1, col + with_index as usize), cell.style); - if cell.is_big { - is_footer_used = cell.is_big; - } + column_rows = column_rows.saturating_add(cell.size); } let head_cell = NuTableCell::new(header); @@ -316,6 +309,8 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { available_width -= pad_space + column_width; rendered_column += 1; + + rows_count = std::cmp::max(rows_count, column_rows); } if truncate && rendered_column == 0 { @@ -374,9 +369,7 @@ fn expanded_table_list(input: &[Value], cfg: Cfg<'_>) -> TableResult { table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1); set_data_styles(&mut table, data_styles); - let has_footer = is_footer_used || has_footer(&cfg.opts, table.count_rows() as u64); - - Ok(Some(TableOutput::new(table, true, with_index, has_footer))) + Ok(Some(TableOutput::new(table, true, with_index, rows_count))) } fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { @@ -395,7 +388,7 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { let value_width = cfg.opts.width - key_width - count_borders - padding - padding; - let mut with_footer = false; + let mut count_rows = 0usize; let mut data = Vec::with_capacity(record.len()); for (key, value) in record { @@ -420,19 +413,17 @@ fn expanded_table_kv(record: &Record, cfg: Cfg<'_>) -> CellResult { data.push(row); - if cell.is_big { - with_footer = cell.is_big; - } + count_rows = count_rows.saturating_add(cell.size); } let mut table = NuTable::from(data); table.set_index_style(get_key_style(&cfg)); table.set_indent(cfg.opts.indent.0, cfg.opts.indent.1); - let out = TableOutput::new(table, false, true, with_footer); + let out = TableOutput::new(table, false, true, count_rows); maybe_expand_table(out, cfg.opts.width, &cfg.opts) - .map(|value| value.map(|value| CellOutput::clean(value, with_footer, false))) + .map(|value| value.map(|value| CellOutput::clean(value, count_rows, false))) } // the flag is used as an optimization to not do `value.lines().count()` search. @@ -441,7 +432,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR if is_limited { return Ok(Some(CellOutput::clean( value_to_string_clean(value, cfg), - false, + 1, false, ))); } @@ -457,7 +448,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR let cfg = create_table_cfg(cfg, &out); let value = out.table.draw(cfg, value_width); match value { - Some(value) => Ok(Some(CellOutput::clean(value, out.with_footer, true))), + Some(value) => Ok(Some(CellOutput::clean(value, out.count_rows, true))), None => Ok(None), } } @@ -484,7 +475,7 @@ fn expand_table_value(value: &Value, value_width: usize, cfg: &Cfg<'_>) -> CellR let inner_cfg = update_config(dive_options(cfg, span), value_width); let result = expanded_table_kv(record, inner_cfg)?; match result { - Some(result) => Ok(Some(CellOutput::clean(result.text, result.is_big, true))), + Some(result) => Ok(Some(CellOutput::clean(result.text, result.size, true))), None => Ok(Some(CellOutput::text(value_to_wrapped_string( value, cfg, @@ -575,7 +566,7 @@ fn expanded_table_entry2(item: &Value, cfg: Cfg<'_>) -> CellOutput { let table_config = create_table_cfg(&cfg, &out); let table = out.table.draw(table_config, usize::MAX); match table { - Some(table) => CellOutput::clean(table, out.with_footer, false), + Some(table) => CellOutput::clean(table, out.count_rows, false), None => CellOutput::styled(nu_value_to_string( item, cfg.opts.config, diff --git a/crates/nu-table/src/types/general.rs b/crates/nu-table/src/types/general.rs index ba0a1ceefa..e748b6eff2 100644 --- a/crates/nu-table/src/types/general.rs +++ b/crates/nu-table/src/types/general.rs @@ -56,8 +56,9 @@ fn kv_table(record: &Record, opts: TableOpts<'_>) -> StringResult { let mut table = NuTable::from(data); table.set_index_style(TextStyle::default_field()); + let count_rows = table.count_rows(); - let mut out = TableOutput::new(table, false, true, false); + let mut out = TableOutput::new(table, false, true, count_rows); let left = opts.config.table.padding.left; let right = opts.config.table.padding.right; @@ -82,7 +83,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult { let with_header = !headers.is_empty(); if !with_header { let table = to_table_with_no_header(input, with_index, row_offset, opts)?; - let table = table.map(|table| TableOutput::new(table, false, with_index, false)); + let table = table.map(|table| { + let count_rows = table.count_rows(); + TableOutput::new(table, false, with_index, count_rows) + }); return Ok(table); } @@ -98,7 +102,10 @@ fn table(input: &[Value], opts: &TableOpts<'_>) -> TableResult { .collect(); let table = to_table_with_header(input, &headers, with_index, row_offset, opts)?; - let table = table.map(|table| TableOutput::new(table, true, with_index, false)); + let table = table.map(|table| { + let count_rows = table.count_rows(); + TableOutput::new(table, true, with_index, count_rows) + }); Ok(table) } diff --git a/crates/nu-table/src/types/mod.rs b/crates/nu-table/src/types/mod.rs index adbe56bf27..829c87ed9e 100644 --- a/crates/nu-table/src/types/mod.rs +++ b/crates/nu-table/src/types/mod.rs @@ -1,8 +1,7 @@ -use terminal_size::{terminal_size, Height, Width}; +use nu_color_config::StyleComputer; +use nu_protocol::{Config, Signals, Span, TableIndexMode, TableMode}; use crate::{common::INDEX_COLUMN_NAME, NuTable}; -use nu_color_config::StyleComputer; -use nu_protocol::{Config, FooterMode, Signals, Span, TableIndexMode, TableMode}; mod collapse; mod expanded; @@ -16,16 +15,16 @@ pub struct TableOutput { pub table: NuTable, pub with_header: bool, pub with_index: bool, - pub with_footer: bool, + pub count_rows: usize, } impl TableOutput { - pub fn new(table: NuTable, with_header: bool, with_index: bool, with_footer: bool) -> Self { + pub fn new(table: NuTable, with_header: bool, with_index: bool, count_rows: usize) -> Self { Self { table, with_header, with_index, - with_footer, + count_rows, } } } @@ -79,23 +78,3 @@ fn has_index(opts: &TableOpts<'_>, headers: &[String]) -> bool { with_index && !opts.index_remove } - -fn has_footer(opts: &TableOpts<'_>, count_records: u64) -> bool { - match opts.config.footer_mode { - // Only show the footer if there are more than RowCount rows - FooterMode::RowCount(limit) => count_records > limit, - // Always show the footer - FooterMode::Always => true, - // Never show the footer - FooterMode::Never => false, - // Calculate the screen height and row count, if screen height is larger than row count, don't show footer - FooterMode::Auto => { - let (_width, height) = match terminal_size() { - Some((w, h)) => (Width(w.0).0 as u64, Height(h.0).0 as u64), - None => (Width(0).0 as u64, Height(0).0 as u64), - }; - - height <= count_records - } - } -} From eb0b6c87d610d222903d8c1b9a0523474e9176ff Mon Sep 17 00:00:00 2001 From: Ryan Faulhaber Date: Tue, 19 Nov 2024 17:20:52 -0500 Subject: [PATCH 2/8] Add mac and IP address entries to `sys net` (#14389) # Description What it says on the tin, this change adds the `mac` and `ip` columns to the `sys net` command, where `mac` is the interface mac address and `ip` is a record containing ipv4 and ipv6 addresses as well as whether or not the address is loopback and multicast. I thought it might be useful to have this information available in Nushell. This change basically just pulls extra information out of the underlying structs in the `sysinfo::Networks` struct. Here's a screenshot from my system: ![Screenshot from 2024-11-19 11-59-54](https://github.com/user-attachments/assets/92c2d72c-b0d0-49c0-8167-9e1ce853acf1) # User-Facing Changes - Adds `mac` and `ip` columns to the `sys net` command, where `mac` contains the interface's mac address and `ip` contains information extracted from the `std::net::IpAddr` struct, including address, protocol, whether or not the address is loopback, and whether or not it's multicast # Tests + Formatting Didn't add any tests specifically, didn't seem like there were any relevant tests. Ran existing tests and formatting. --- crates/nu-command/src/system/sys/net.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/crates/nu-command/src/system/sys/net.rs b/crates/nu-command/src/system/sys/net.rs index ee82f73c61..8e2ac19bf5 100644 --- a/crates/nu-command/src/system/sys/net.rs +++ b/crates/nu-command/src/system/sys/net.rs @@ -44,8 +44,29 @@ fn net(span: Span) -> Value { let networks = Networks::new_with_refreshed_list() .iter() .map(|(iface, data)| { + let ip_addresses = data + .ip_networks() + .iter() + .map(|ip| { + let protocol = match ip.addr { + std::net::IpAddr::V4(_) => "ipv4", + std::net::IpAddr::V6(_) => "ipv6", + }; + Value::record( + record! { + "address" => Value::string(ip.addr.to_string(), span), + "protocol" => Value::string(protocol, span), + "loop" => Value::bool(ip.addr.is_loopback(), span), + "multicast" => Value::bool(ip.addr.is_multicast(), span), + }, + span, + ) + }) + .collect(); let record = record! { "name" => Value::string(trim_cstyle_null(iface), span), + "mac" => Value::string(data.mac_address().to_string(), span), + "ip" => Value::list(ip_addresses, span), "sent" => Value::filesize(data.total_transmitted() as i64, span), "recv" => Value::filesize(data.total_received() as i64, span), }; From 582b5f45e8cf6ffe82f5d84329eb7afcc8c40925 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:19:24 +0800 Subject: [PATCH 3/8] Bump shadow-rs from 0.35.2 to 0.36.0 (#14396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [shadow-rs](https://github.com/baoyachi/shadow-rs) from 0.35.2 to 0.36.0.
Release notes

Sourced from shadow-rs's releases.

v0.36.0

What's Changed

Full Changelog: https://github.com/baoyachi/shadow-rs/compare/v0.35.2...v0.36.0

Commits
  • 909510e Merge pull request #190 from baoyachi/hook_ext
  • bad046d Update Cargo.toml
  • 84096a0 feat(HookExt): Add extended hook functionality with custom deny lists
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=shadow-rs&package-manager=cargo&previous-version=0.35.2&new-version=0.36.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/nu-cmd-lang/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2821dab891..887dfc1ffd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5893,9 +5893,9 @@ dependencies = [ [[package]] name = "shadow-rs" -version = "0.35.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1b2328fb3ec0d5302f95915e7e77cfc2ff943714d9970bc4b66e9eacf318687" +checksum = "58cfcd0643497a9f780502063aecbcc4a3212cbe4948fd25ee8fd179c2cf9a18" dependencies = [ "const_format", "is_debug", diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index c5609e4ff8..6e8cb824ba 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -21,10 +21,10 @@ nu-protocol = { path = "../nu-protocol", version = "0.100.1" } nu-utils = { path = "../nu-utils", version = "0.100.1" } itertools = { workspace = true } -shadow-rs = { version = "0.35", default-features = false } +shadow-rs = { version = "0.36", default-features = false } [build-dependencies] -shadow-rs = { version = "0.35", default-features = false } +shadow-rs = { version = "0.36", default-features = false } [features] mimalloc = [] From a6e3470c6f24b781280eaa36e8d610025c588ec6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:19:37 +0800 Subject: [PATCH 4/8] Bump thiserror from 1.0.69 to 2.0.3 (#14394) Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.69 to 2.0.3.
Release notes

Sourced from thiserror's releases.

2.0.3

  • Support the same Path field being repeated in both Debug and Display representation in error message (#383)
  • Improve error message when a format trait used in error message is not implemented by some field (#384)

2.0.2

  • Fix hang on invalid input inside #[error(...)] attribute (#382)

2.0.1

  • Support errors that contain a dynamically sized final field (#375)
  • Improve inference of trait bounds for fields that are interpolated multiple times in an error message (#377)

2.0.0

Breaking changes

  • Referencing keyword-named fields by a raw identifier like {r#type} inside a format string is no longer accepted; simply use the unraw name like {type} (#347)

    This aligns thiserror with the standard library's formatting macros, which gained support for implicit argument capture later than the release of this feature in thiserror 1.x.

    #[derive(Error, Debug)]
    #[error("... {type} ...")]  // Before: {r#type}
    pub struct Error {
        pub r#type: Type,
    }
    
  • Trait bounds are no longer inferred on fields whose value is shadowed by an explicit named argument in a format message (#345)

    // Before: impl<T: Octal> Display for
    Error<T>
    // After: impl<T> Display for Error<T>
    #[derive(Error, Debug)]
    #[error("{thing:o}", thing = "...")]
    pub struct Error<T> {
        thing: T,
    }
    
  • Tuple structs and tuple variants can no longer use numerical {0} {1} access at the same time as supplying extra positional arguments for a format message, as this makes it ambiguous whether the number refers to a tuple field vs a different positional arg (#354)

    #[derive(Error, Debug)]
    #[error("ambiguous: {0} {}", $N)]
    // ^^^ Not allowed, use #[error("... {0} {n}", n = $N)]
    pub struct TupleError(i32);
    
  • Code containing invocations of thiserror's derive(Error) must now have a direct dependency on the thiserror crate regardless of the error data structure's contents (#368, #369, #370, #372)

Features

... (truncated)

Commits
  • 15fd26e Release 2.0.3
  • 7046023 Simplify how has_bonus_display is accumulated
  • 9cc1d0b Merge pull request #384 from dtolnay/nowrap
  • 1d040f3 Use Var wrapper only for Pointer formatting
  • 6a6132d Extend no-display ui test to cover another fmt trait
  • a061beb Merge pull request #383 from dtolnay/both
  • 6388293 Support Display and Debug of same path in error message
  • dc0359e Defer binding_value construction
  • 520343e Add test of Debug and Display of paths
  • 49be39d Release 2.0.2
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=thiserror&package-manager=cargo&previous-version=1.0.69&new-version=2.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- crates/nu-plugin/Cargo.toml | 2 +- crates/nu-protocol/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 887dfc1ffd..cdfa8a1ad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3485,7 +3485,7 @@ dependencies = [ "nu-protocol", "nu-utils", "serde", - "thiserror 1.0.69", + "thiserror 2.0.3", "typetag", ] @@ -3591,7 +3591,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 1.0.69", + "thiserror 2.0.3", "typetag", "windows-sys 0.48.0", ] diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index a2c328375e..2c43a86c5e 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -21,7 +21,7 @@ nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-feat nu-utils = { path = "../nu-utils", version = "0.100.1" } log = { workspace = true } -thiserror = "1.0" +thiserror = "2.0" [dev-dependencies] serde = { workspace = true } diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index 60d6c10acc..dfbcb7b307 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -36,7 +36,7 @@ num-format = { workspace = true } rmp-serde = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } -thiserror = "1.0" +thiserror = "2.0" typetag = "0.2" os_pipe = { workspace = true, features = ["io_safety"] } log = { workspace = true } From 1e7840c376695fc9096a07916e748258c80135b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 09:20:47 +0800 Subject: [PATCH 5/8] Bump terminal_size from 0.3.0 to 0.4.0 (#14393) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [terminal_size](https://github.com/eminence/terminal-size) from 0.3.0 to 0.4.0.
Release notes

Sourced from terminal_size's releases.

v0.4.0

Breaking changes

The big change in this release is the API change in #66:

  • If you were using the terminal_size_using_fd or terminal_size_using_handle functions, these are now deprecated and unsafe. Instead you should use the terminal_size_of function, which does the same thing but is safer.

What's Changed

New Contributors

Full Changelog: https://github.com/eminence/terminal-size/compare/v0.3.0...v0.4.0

Commits
  • f6b81b5 Bump to version 0.4.0
  • 5cbc616 Merge pull request #64 from waywardmonkeys/update-ci
  • 68ceb8d Merge pull request #63 from waywardmonkeys/fix-typo
  • 5307747 Merge pull request #66 from sunfishcode/main
  • a29b904 Mark terminal_size_using_handle as unsafe too.
  • ea92388 Mark terminal_size_using_fd as unsafe.
  • 78e81fa Merge pull request #67 from eminence/windows-sys
  • c69ff4e Update windows-sys to 0.59
  • 76b0cae Merge pull request #62 from barrbrain/windows-sys
  • 56334c3 Update the API for I/O safety
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=terminal_size&package-manager=cargo&previous-version=0.3.0&new-version=0.4.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdfa8a1ad5..7f43aaada7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3333,7 +3333,7 @@ dependencies = [ "sysinfo 0.32.0", "tabled", "tempfile", - "terminal_size 0.3.0", + "terminal_size 0.4.0", "titlecase", "toml 0.8.19", "trash", @@ -3378,7 +3378,7 @@ dependencies = [ "nu-path", "nu-protocol", "nu-utils", - "terminal_size 0.3.0", + "terminal_size 0.4.0", ] [[package]] @@ -3402,7 +3402,7 @@ dependencies = [ "nu-utils", "ratatui", "strip-ansi-escapes", - "terminal_size 0.3.0", + "terminal_size 0.4.0", "unicode-width 0.1.11", ] @@ -3635,7 +3635,7 @@ dependencies = [ "nu-protocol", "nu-utils", "tabled", - "terminal_size 0.3.0", + "terminal_size 0.4.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ad2e886ad8..b9be5c59f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,7 +156,7 @@ syn = "2.0" sysinfo = "0.32" tabled = { version = "0.16.0", default-features = false } tempfile = "3.14" -terminal_size = "0.3" +terminal_size = "0.4" titlecase = "2.0" toml = "0.8" trash = "5.2" From 5d1eb031ebb4dec3bed95e62286e66a2fd3fea6d Mon Sep 17 00:00:00 2001 From: Devyn Cairns Date: Wed, 20 Nov 2024 03:24:03 -0800 Subject: [PATCH 6/8] Turn compile errors into fatal errors (#14388) # Description Because the IR compiler was previously optional, compile errors were not treated as fatal errors, and were just logged like parse warnings are. This unfortunately meant that if a user encountered a compile error, they would see "Can't evaluate block in IR mode" as the actual error in addition to (hopefully) logging the compile error. This changes compile errors to be treated like parse errors so that they show up as the last error, helping users understand what's wrong a little bit more easily. Fixes #14333. # User-Facing Changes - Shouldn't see "Can't evaluate block in IR mode" - Should only see compile error - No evaluation should happen # Tests + Formatting Didn't add any tests specifically for this, but it might be good to have at least one that checks to ensure the compile error shows up and the "can't evaluate" error does not. --- crates/nu-cli/src/eval_cmds.rs | 2 +- crates/nu-cli/src/eval_file.rs | 2 +- crates/nu-cli/src/util.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-cli/src/eval_cmds.rs b/crates/nu-cli/src/eval_cmds.rs index 663185a4a2..5fba00f5c9 100644 --- a/crates/nu-cli/src/eval_cmds.rs +++ b/crates/nu-cli/src/eval_cmds.rs @@ -74,7 +74,7 @@ pub fn evaluate_commands( if let Some(err) = working_set.compile_errors.first() { report_compile_error(&working_set, err); - // Not a fatal error, for now + std::process::exit(1); } (output, working_set.render()) diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index 7636adc8d4..826b6c8eb5 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -89,7 +89,7 @@ pub fn evaluate_file( if let Some(err) = working_set.compile_errors.first() { report_compile_error(&working_set, err); - // Not a fatal error, for now + std::process::exit(1); } // Look for blocks whose name starts with "main" and replace it with the filename. diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 3e69657857..7b9d783534 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -296,7 +296,7 @@ fn evaluate_source( if let Some(err) = working_set.compile_errors.first() { report_compile_error(&working_set, err); - // Not a fatal error, for now + return Ok(true); } (output, working_set.render()) From 42d2adc3e037d2ceb3d3708407b148efa7896c96 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 20 Nov 2024 07:55:26 -0600 Subject: [PATCH 7/8] allow ps1 files to be executed without pwsh/powershell -c file.ps1 (#14379) # Description This PR allows nushell to run powershell scripts easier. You can already do `powershell -c script.ps1` but this PR takes it a step further by doing the `powershell -c` part for you. So, if you have script.ps1 you can execute it by running it in the command position of the repl. ![image](https://github.com/user-attachments/assets/0661a746-27d9-4d21-b576-c244ff7fab2b) or once it's in json, just consume it with nushell. ![image](https://github.com/user-attachments/assets/38f5c5d8-3659-41f0-872b-91a14909760b) # User-Facing Changes Easier to run powershell scripts. It should work on Windows with powershell.exe. # Tests + Formatting Added 1 test # After Submitting --------- Co-authored-by: Wind --- crates/nu-command/src/system/run_external.rs | 45 +++++++++++++++++-- .../nu-command/tests/commands/run_external.rs | 41 +++++++++++++++-- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 604278c676..ffb34bf077 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -5,6 +5,8 @@ use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDe use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; use pathdiff::diff_paths; +#[cfg(windows)] +use std::os::windows::process::CommandExt; use std::{ borrow::Cow, ffi::{OsStr, OsString}, @@ -91,6 +93,22 @@ impl Command for External { false }; + // let's make sure it's a .ps1 script, but only on Windows + let potential_powershell_script = if cfg!(windows) { + if let Some(executable) = which(&expanded_name, "", cwd.as_ref()) { + let ext = executable + .extension() + .unwrap_or_default() + .to_string_lossy() + .to_uppercase(); + ext == "PS1" + } else { + false + } + } else { + false + }; + // Find the absolute path to the executable. On Windows, set the // executable to "cmd.exe" if it's a CMD internal command. If the // command is not found, display a helpful error message. @@ -98,11 +116,16 @@ impl Command for External { && (is_cmd_internal_command(&name_str) || potential_nuscript_in_windows) { PathBuf::from("cmd.exe") + } else if cfg!(windows) && potential_powershell_script { + // If we're on Windows and we're trying to run a PowerShell script, we'll use + // `powershell.exe` to run it. We shouldn't have to check for powershell.exe because + // it's automatically installed on all modern windows systems. + PathBuf::from("powershell.exe") } else { // Determine the PATH to be used and then use `which` to find it - though this has no // effect if it's an absolute path already let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let Some(executable) = which(expanded_name, &paths, cwd.as_ref()) else { + let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else { return Err(command_not_found(&name_str, call.head, engine_state, stack)); }; executable @@ -123,15 +146,29 @@ impl Command for External { let args = eval_arguments_from_call(engine_state, stack, call)?; #[cfg(windows)] if is_cmd_internal_command(&name_str) || potential_nuscript_in_windows { - use std::os::windows::process::CommandExt; - // The /D flag disables execution of AutoRun commands from registry. // The /C flag followed by a command name instructs CMD to execute // that command and quit. - command.args(["/D", "/C", &name_str]); + command.args(["/D", "/C", &expanded_name.to_string_lossy()]); for arg in &args { command.raw_arg(escape_cmd_argument(arg)?); } + } else if potential_powershell_script { + use nu_path::canonicalize_with; + + // canonicalize the path to the script so that tests pass + let canon_path = if let Ok(cwd) = engine_state.cwd_as_string(None) { + canonicalize_with(&expanded_name, cwd)? + } else { + // If we can't get the current working directory, just provide the expanded name + expanded_name + }; + // The -Command flag followed by a script name instructs PowerShell to + // execute that script and quit. + command.args(["-Command", &canon_path.to_string_lossy()]); + for arg in &args { + command.raw_arg(arg.item.clone()); + } } else { command.args(args.into_iter().map(|s| s.item)); } diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 17667c9bb3..8a797300f1 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -355,9 +355,9 @@ fn external_command_receives_raw_binary_data() { #[cfg(windows)] #[test] -fn can_run_batch_files() { +fn can_run_cmd_files() { use nu_test_support::fs::Stub::FileWithContent; - Playground::setup("run a Windows batch file", |dirs, sandbox| { + Playground::setup("run a Windows cmd file", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( "foo.cmd", r#" @@ -371,12 +371,30 @@ fn can_run_batch_files() { }); } +#[cfg(windows)] +#[test] +fn can_run_batch_files() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("run a Windows batch file", |dirs, sandbox| { + sandbox.with_files(&[FileWithContent( + "foo.bat", + r#" + @echo off + echo Hello World + "#, + )]); + + let actual = nu!(cwd: dirs.test(), pipeline("foo.bat")); + assert!(actual.out.contains("Hello World")); + }); +} + #[cfg(windows)] #[test] fn can_run_batch_files_without_cmd_extension() { use nu_test_support::fs::Stub::FileWithContent; Playground::setup( - "run a Windows batch file without specifying the extension", + "run a Windows cmd file without specifying the extension", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( "foo.cmd", @@ -440,3 +458,20 @@ fn redirect_combine() { assert_eq!(actual.out, "FooBar"); }); } + +#[cfg(windows)] +#[test] +fn can_run_ps1_files() { + use nu_test_support::fs::Stub::FileWithContent; + Playground::setup("run_a_windows_ps_file", |dirs, sandbox| { + sandbox.with_files(&[FileWithContent( + "foo.ps1", + r#" + Write-Host Hello World + "#, + )]); + + let actual = nu!(cwd: dirs.test(), pipeline("foo.ps1")); + assert!(actual.out.contains("Hello World")); + }); +} From b318d588fe9abe7268e8658647ab99a555b6e3a6 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:39:15 -0600 Subject: [PATCH 8/8] add new --flatten parameter to the ast command (#14400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description By request, this PR introduces a new `--flatten` parameter to the ast command for generating a more readable version of the AST output. This enhancement improves usability by allowing users to easily visualize the structure of the AST. ![image](https://github.com/user-attachments/assets/a66644ef-5fff-4d3d-a334-4e9f80edb39d) ```nushell ❯ ast 'ls | sort-by type name -i' --flatten --json [ { "content": "ls", "shape": "shape_internalcall", "span": { "start": 0, "end": 2 } }, { "content": "|", "shape": "shape_pipe", "span": { "start": 3, "end": 4 } }, { "content": "sort-by", "shape": "shape_internalcall", "span": { "start": 5, "end": 12 } }, { "content": "type", "shape": "shape_string", "span": { "start": 13, "end": 17 } }, { "content": "name", "shape": "shape_string", "span": { "start": 18, "end": 22 } }, { "content": "-i", "shape": "shape_flag", "span": { "start": 23, "end": 25 } } ] ❯ ast 'ls | sort-by type name -i' --flatten --json --minify [{"content":"ls","shape":"shape_internalcall","span":{"start":0,"end":2}},{"content":"|","shape":"shape_pipe","span":{"start":3,"end":4}},{"content":"sort-by","shape":"shape_internalcall","span":{"start":5,"end":12}},{"content":"type","shape":"shape_string","span":{"start":13,"end":17}},{"content":"name","shape":"shape_string","span":{"start":18,"end":22}},{"content":"-i","shape":"shape_flag","span":{"start":23,"end":25}}] ``` # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-command/src/debug/ast.rs | 346 +++++++++++++++++++++-------- 1 file changed, 250 insertions(+), 96 deletions(-) diff --git a/crates/nu-command/src/debug/ast.rs b/crates/nu-command/src/debug/ast.rs index c6e405d533..45e7738c32 100644 --- a/crates/nu-command/src/debug/ast.rs +++ b/crates/nu-command/src/debug/ast.rs @@ -1,6 +1,7 @@ use nu_engine::command_prelude::*; -use nu_parser::parse; -use nu_protocol::engine::StateWorkingSet; +use nu_parser::{flatten_block, parse}; +use nu_protocol::{engine::StateWorkingSet, record}; +use serde_json::{json, Value as JsonValue}; #[derive(Clone)] pub struct Ast; @@ -16,109 +17,23 @@ impl Command for Ast { fn signature(&self) -> Signature { Signature::build("ast") - .input_output_types(vec![(Type::String, Type::record())]) + .input_output_types(vec![ + (Type::Nothing, Type::table()), + (Type::Nothing, Type::record()), + (Type::Nothing, Type::String), + ]) .required( "pipeline", SyntaxShape::String, "The pipeline to print the ast for.", ) - .switch("json", "serialize to json", Some('j')) - .switch("minify", "minify the nuon or json output", Some('m')) + .switch("json", "Serialize to json", Some('j')) + .switch("minify", "Minify the nuon or json output", Some('m')) + .switch("flatten", "An easier to read version of the ast", Some('f')) .allow_variants_without_examples(true) .category(Category::Debug) } - fn run( - &self, - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - _input: PipelineData, - ) -> Result { - let pipeline: Spanned = call.req(engine_state, stack, 0)?; - let to_json = call.has_flag(engine_state, stack, "json")?; - let minify = call.has_flag(engine_state, stack, "minify")?; - let mut working_set = StateWorkingSet::new(engine_state); - let block_output = parse(&mut working_set, None, pipeline.item.as_bytes(), false); - let error_output = working_set.parse_errors.first(); - let block_span = match &block_output.span { - Some(span) => span, - None => &pipeline.span, - }; - if to_json { - // Get the block as json - let serde_block_str = if minify { - serde_json::to_string(&*block_output) - } else { - serde_json::to_string_pretty(&*block_output) - }; - let block_json = match serde_block_str { - Ok(json) => json, - Err(e) => Err(ShellError::CantConvert { - to_type: "string".to_string(), - from_type: "block".to_string(), - span: *block_span, - help: Some(format!( - "Error: {e}\nCan't convert {block_output:?} to string" - )), - })?, - }; - // Get the error as json - let serde_error_str = if minify { - serde_json::to_string(&error_output) - } else { - serde_json::to_string_pretty(&error_output) - }; - - let error_json = match serde_error_str { - Ok(json) => json, - Err(e) => Err(ShellError::CantConvert { - to_type: "string".to_string(), - from_type: "error".to_string(), - span: *block_span, - help: Some(format!( - "Error: {e}\nCan't convert {error_output:?} to string" - )), - })?, - }; - - // Create a new output record, merging the block and error - let output_record = Value::record( - record! { - "block" => Value::string(block_json, *block_span), - "error" => Value::string(error_json, Span::test_data()), - }, - pipeline.span, - ); - Ok(output_record.into_pipeline_data()) - } else { - let block_value = Value::string( - if minify { - format!("{block_output:?}") - } else { - format!("{block_output:#?}") - }, - pipeline.span, - ); - let error_value = Value::string( - if minify { - format!("{error_output:?}") - } else { - format!("{error_output:#?}") - }, - pipeline.span, - ); - let output_record = Value::record( - record! { - "block" => block_value, - "error" => error_value - }, - pipeline.span, - ); - Ok(output_record.into_pipeline_data()) - } - } - fn examples(&self) -> Vec { vec![ Example { @@ -147,8 +62,247 @@ impl Command for Ast { example: "ast 'for x in 1..10 { echo $x ' --json --minify", result: None, }, + Example { + description: "Print the ast of a string flattened", + example: r#"ast "'hello'" --flatten"#, + result: Some(Value::test_list(vec![Value::test_record(record! { + "content" => Value::test_string("'hello'"), + "shape" => Value::test_string("shape_string"), + "span" => Value::test_record(record! { + "start" => Value::test_int(0), + "end" => Value::test_int(7),}), + })])), + }, + Example { + description: "Print the ast of a string flattened, as json, minified", + example: r#"ast "'hello'" --flatten --json --minify"#, + result: Some(Value::test_string( + r#"[{"content":"'hello'","shape":"shape_string","span":{"start":0,"end":7}}]"#, + )), + }, + Example { + description: "Print the ast of a pipeline flattened", + example: r#"ast 'ls | sort-by type name -i' --flatten"#, + result: Some(Value::test_list(vec![ + Value::test_record(record! { + "content" => Value::test_string("ls"), + "shape" => Value::test_string("shape_external"), + "span" => Value::test_record(record! { + "start" => Value::test_int(0), + "end" => Value::test_int(2),}), + }), + Value::test_record(record! { + "content" => Value::test_string("|"), + "shape" => Value::test_string("shape_pipe"), + "span" => Value::test_record(record! { + "start" => Value::test_int(3), + "end" => Value::test_int(4),}), + }), + Value::test_record(record! { + "content" => Value::test_string("sort-by"), + "shape" => Value::test_string("shape_internalcall"), + "span" => Value::test_record(record! { + "start" => Value::test_int(5), + "end" => Value::test_int(12),}), + }), + Value::test_record(record! { + "content" => Value::test_string("type"), + "shape" => Value::test_string("shape_string"), + "span" => Value::test_record(record! { + "start" => Value::test_int(13), + "end" => Value::test_int(17),}), + }), + Value::test_record(record! { + "content" => Value::test_string("name"), + "shape" => Value::test_string("shape_string"), + "span" => Value::test_record(record! { + "start" => Value::test_int(18), + "end" => Value::test_int(22),}), + }), + Value::test_record(record! { + "content" => Value::test_string("-i"), + "shape" => Value::test_string("shape_flag"), + "span" => Value::test_record(record! { + "start" => Value::test_int(23), + "end" => Value::test_int(25),}), + }), + ])), + }, ] } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let pipeline: Spanned = call.req(engine_state, stack, 0)?; + let to_json = call.has_flag(engine_state, stack, "json")?; + let minify = call.has_flag(engine_state, stack, "minify")?; + let flatten = call.has_flag(engine_state, stack, "flatten")?; + + let mut working_set = StateWorkingSet::new(engine_state); + let offset = working_set.next_span_start(); + let parsed_block = parse(&mut working_set, None, pipeline.item.as_bytes(), false); + + if flatten { + let flat = flatten_block(&working_set, &parsed_block); + if to_json { + let mut json_val: JsonValue = json!([]); + for (span, shape) in flat { + let content = + String::from_utf8_lossy(working_set.get_span_contents(span)).to_string(); + + let json = json!( + { + "content": content, + "shape": shape.to_string(), + "span": { + "start": span.start.checked_sub(offset), + "end": span.end.checked_sub(offset), + }, + } + ); + json_merge(&mut json_val, &json); + } + let json_string = if minify { + if let Ok(json_str) = serde_json::to_string(&json_val) { + json_str + } else { + "{}".to_string() + } + } else if let Ok(json_str) = serde_json::to_string_pretty(&json_val) { + json_str + } else { + "{}".to_string() + }; + + Ok(Value::string(json_string, pipeline.span).into_pipeline_data()) + } else { + // let mut rec: Record = Record::new(); + let mut rec = vec![]; + for (span, shape) in flat { + let content = + String::from_utf8_lossy(working_set.get_span_contents(span)).to_string(); + let each_rec = record! { + "content" => Value::test_string(content), + "shape" => Value::test_string(shape.to_string()), + "span" => Value::test_record(record!{ + "start" => Value::test_int(match span.start.checked_sub(offset) { + Some(start) => start as i64, + None => 0 + }), + "end" => Value::test_int(match span.end.checked_sub(offset) { + Some(end) => end as i64, + None => 0 + }), + }), + }; + rec.push(Value::test_record(each_rec)); + } + Ok(Value::list(rec, pipeline.span).into_pipeline_data()) + } + } else { + let error_output = working_set.parse_errors.first(); + let block_span = match &parsed_block.span { + Some(span) => span, + None => &pipeline.span, + }; + if to_json { + // Get the block as json + let serde_block_str = if minify { + serde_json::to_string(&*parsed_block) + } else { + serde_json::to_string_pretty(&*parsed_block) + }; + let block_json = match serde_block_str { + Ok(json) => json, + Err(e) => Err(ShellError::CantConvert { + to_type: "string".to_string(), + from_type: "block".to_string(), + span: *block_span, + help: Some(format!( + "Error: {e}\nCan't convert {parsed_block:?} to string" + )), + })?, + }; + // Get the error as json + let serde_error_str = if minify { + serde_json::to_string(&error_output) + } else { + serde_json::to_string_pretty(&error_output) + }; + + let error_json = match serde_error_str { + Ok(json) => json, + Err(e) => Err(ShellError::CantConvert { + to_type: "string".to_string(), + from_type: "error".to_string(), + span: *block_span, + help: Some(format!( + "Error: {e}\nCan't convert {error_output:?} to string" + )), + })?, + }; + + // Create a new output record, merging the block and error + let output_record = Value::record( + record! { + "block" => Value::string(block_json, *block_span), + "error" => Value::string(error_json, Span::test_data()), + }, + pipeline.span, + ); + Ok(output_record.into_pipeline_data()) + } else { + let block_value = Value::string( + if minify { + format!("{parsed_block:?}") + } else { + format!("{parsed_block:#?}") + }, + pipeline.span, + ); + let error_value = Value::string( + if minify { + format!("{error_output:?}") + } else { + format!("{error_output:#?}") + }, + pipeline.span, + ); + let output_record = Value::record( + record! { + "block" => block_value, + "error" => error_value + }, + pipeline.span, + ); + Ok(output_record.into_pipeline_data()) + } + } + } +} + +fn json_merge(a: &mut JsonValue, b: &JsonValue) { + match (a, b) { + (JsonValue::Object(ref mut a), JsonValue::Object(b)) => { + for (k, v) in b { + json_merge(a.entry(k).or_insert(JsonValue::Null), v); + } + } + (JsonValue::Array(ref mut a), JsonValue::Array(b)) => { + a.extend(b.clone()); + } + (JsonValue::Array(ref mut a), JsonValue::Object(b)) => { + a.extend([JsonValue::Object(b.clone())]); + } + (a, b) => { + *a = b.clone(); + } + } } #[cfg(test)]