nu-table: Add table option --abbreviated (#10399)

- Added `--abbreviated`/`-a` option
- Adedd `abbreviate_if_longer_than` config opt for it.

```nu
ls | table -a 3
```

```
╭───┬────────────────────┬──────┬───────────┬──────────────╮
│ # │        name        │ type │   size    │   modified   │
├───┼────────────────────┼──────┼───────────┼──────────────┤
│ 0 │ CODE_OF_CONDUCT.md │ file │   3.4 KiB │ 4 days ago   │
│ 1 │ CONTRIBUTING.md    │ file │  18.3 KiB │ 2 weeks ago  │
│ 2 │ Cargo.lock         │ file │ 144.3 KiB │ 15 hours ago │
│ 3 │ ...                │ ...  │ ...       │ ...          │
│ 4 │ tests              │ dir  │   4.0 KiB │ 4 months ago │
│ 5 │ toolkit.nu         │ file │  14.6 KiB │ 5 days ago   │
│ 6 │ wix                │ dir  │   4.0 KiB │ 2 months ago │
╰───┴────────────────────┴──────┴───────────┴──────────────╯
```

```nu
$env | table -a 3
```

```
╭──────────────────┬──────────────────────────────────────────────────────────────────────────╮
│ BROWSER          │ firefox                                                                  │
│ CARGO            │ /home/maxim/.rustup/toolchains/1.70.0-x86_64-unknown-linux-gnu/bin/cargo │
│ CARGO_HOME       │ /home/maxim/.cargo                                                       │
│ ...              │ ...                                                                      │
│ XDG_SESSION_TYPE │ x11                                                                      │
│ XDG_VTNR         │ 7                                                                        │
│ _                │ /home/maxim/.cargo/bin/cargo                                             │
╰──────────────────┴──────────────────────────────────────────────────────────────────────────╯
```

close #10393

PS: Maybe as a separate issue (good candidate for `GOOD FIRST ISSUE`)
add a config option to change a default `...` truncation sign to a
custom? (which would be applicable not only for `--abbreviated` but all
kind of tables)

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
Maxim Zhiburt 2023-09-20 17:59:08 +00:00 committed by GitHub
parent 4ae53d93fb
commit 7cfd4d2cfa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 444 additions and 252 deletions

View File

@ -94,6 +94,12 @@ impl Command for Table {
"expand the table structure in collapse mode.\nBe aware collapse mode currently doesn't support width control",
Some('c'),
)
.named(
"abbreviated",
SyntaxShape::Int,
"abbreviate the data in the table by truncating the middle part and only showing amount provided on top and bottom",
Some('a'),
)
.category(Category::Viewers)
}
@ -104,33 +110,13 @@ impl Command for Table {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let start_num: Option<i64> = call.get_flag(engine_state, stack, "start-number")?;
let row_offset = start_num.unwrap_or_default() as usize;
let list: bool = call.has_flag("list");
let width_param: Option<i64> = call.get_flag(engine_state, stack, "width")?;
let expand: bool = call.has_flag("expand");
let expand_limit: Option<usize> = call.get_flag(engine_state, stack, "expand-deep")?;
let collapse: bool = call.has_flag("collapse");
let flatten: bool = call.has_flag("flatten");
let flatten_separator: Option<String> =
call.get_flag(engine_state, stack, "flatten-separator")?;
let table_view = match (expand, collapse) {
(false, false) => TableView::General,
(_, true) => TableView::Collapsed,
(true, _) => TableView::Expanded {
limit: expand_limit,
flatten,
flatten_separator,
},
};
let list_themes: bool = call.has_flag("list");
let cfg = parse_table_config(call, engine_state, stack)?;
let input = CmdInput::new(engine_state, stack, call, input);
// if list argument is present we just need to return a list of supported table themes
if list {
if list_themes {
let val = Value::list(supported_table_modes(), Span::test_data());
return Ok(val.into_pipeline_data());
}
@ -140,15 +126,7 @@ impl Command for Table {
let _ = nu_utils::enable_vt_processing();
}
handle_table_command(
engine_state,
stack,
call,
input,
row_offset,
table_view,
width_param,
)
handle_table_command(input, cfg)
}
fn examples(&self) -> Vec<Example> {
@ -214,86 +192,138 @@ impl Command for Table {
}
}
fn handle_table_command(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
#[derive(Debug, Clone)]
struct TableConfig {
row_offset: usize,
table_view: TableView,
term_width: Option<i64>,
term_width: usize,
abbreviation: Option<usize>,
}
impl TableConfig {
fn new(
row_offset: usize,
table_view: TableView,
term_width: usize,
abbreviation: Option<usize>,
) -> Self {
Self {
row_offset,
table_view,
term_width,
abbreviation,
}
}
}
fn parse_table_config(
call: &Call,
state: &EngineState,
stack: &mut Stack,
) -> Result<TableConfig, ShellError> {
let start_num: Option<i64> = call.get_flag(state, stack, "start-number")?;
let row_offset = start_num.unwrap_or_default() as usize;
let width_param: Option<i64> = call.get_flag(state, stack, "width")?;
let expand: bool = call.has_flag("expand");
let expand_limit: Option<usize> = call.get_flag(state, stack, "expand-deep")?;
let collapse: bool = call.has_flag("collapse");
let flatten: bool = call.has_flag("flatten");
let flatten_separator: Option<String> = call.get_flag(state, stack, "flatten-separator")?;
let abbrivation: Option<usize> = call
.get_flag(state, stack, "abbreviated")?
.or_else(|| get_config(state, stack).table_abbreviation_threshold);
let table_view = match (expand, collapse) {
(false, false) => TableView::General,
(_, true) => TableView::Collapsed,
(true, _) => TableView::Expanded {
limit: expand_limit,
flatten,
flatten_separator,
},
};
let term_width = get_width_param(width_param);
let cfg = TableConfig::new(row_offset, table_view, term_width, abbrivation);
Ok(cfg)
}
struct CmdInput<'a> {
engine_state: &'a EngineState,
stack: &'a mut Stack,
call: &'a Call,
data: PipelineData,
}
impl<'a> CmdInput<'a> {
fn new(
engine_state: &'a EngineState,
stack: &'a mut Stack,
call: &'a Call,
data: PipelineData,
) -> Self {
Self {
engine_state,
stack,
call,
data,
}
}
}
fn handle_table_command(
mut input: CmdInput<'_>,
cfg: TableConfig,
) -> Result<PipelineData, ShellError> {
let ctrlc = engine_state.ctrlc.clone();
let config = get_config(engine_state, stack);
let span = input.data.span().unwrap_or(input.call.head);
match input.data {
PipelineData::ExternalStream { .. } => Ok(input.data),
PipelineData::Value(Value::Binary { val, .. }, ..) => {
let stream_list = if input.call.redirect_stdout {
vec![Ok(val)]
} else {
let hex = format!("{}\n", nu_pretty_hex::pretty_hex(&val))
.as_bytes()
.to_vec();
vec![Ok(hex)]
};
let span = input.span().unwrap_or(call.head);
match input {
PipelineData::ExternalStream { .. } => Ok(input),
PipelineData::Value(Value::Binary { val, .. }, ..) => Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(if call.redirect_stdout {
vec![Ok(val)].into_iter()
} else {
vec![Ok(format!("{}\n", nu_pretty_hex::pretty_hex(&val))
.as_bytes()
.to_vec())]
.into_iter()
}),
let ctrlc = input.engine_state.ctrlc.clone();
let stream = RawStream::new(
Box::new(stream_list.into_iter()),
ctrlc,
call.head,
input.call.head,
None,
)),
stderr: None,
exit_code: None,
span: call.head,
metadata: None,
trim_end_newline: false,
}),
// None of these two receive a StyleComputer because handle_row_stream() can produce it by itself using engine_state and stack.
PipelineData::Value(Value::List { vals, .. }, metadata) => handle_row_stream(
engine_state,
stack,
ListStream::from_stream(vals.into_iter(), ctrlc.clone()),
call,
row_offset,
ctrlc,
metadata,
),
PipelineData::ListStream(stream, metadata) => handle_row_stream(
engine_state,
stack,
stream,
call,
row_offset,
ctrlc,
metadata,
),
PipelineData::Value(Value::Record { val, .. }, ..) => {
let term_width = get_width_param(term_width);
);
handle_record(
val,
span,
engine_state,
stack,
call,
table_view,
term_width,
ctrlc,
&config,
)
Ok(PipelineData::ExternalStream {
stdout: Some(stream),
stderr: None,
exit_code: None,
span: input.call.head,
metadata: None,
trim_end_newline: false,
})
}
// None of these two receive a StyleComputer because handle_row_stream() can produce it by itself using engine_state and stack.
PipelineData::Value(Value::List { vals, .. }, metadata) => {
let ctrlc = input.engine_state.ctrlc.clone();
let stream = ListStream::from_stream(vals.into_iter(), ctrlc);
input.data = PipelineData::Empty;
handle_row_stream(input, cfg, stream, metadata)
}
PipelineData::ListStream(stream, metadata) => {
input.data = PipelineData::Empty;
handle_row_stream(input, cfg, stream, metadata)
}
PipelineData::Value(Value::Record { val, .. }, ..) => {
input.data = PipelineData::Empty;
handle_record(input, cfg, val)
}
PipelineData::Value(Value::LazyRecord { val, .. }, ..) => {
let collected = val.collect()?.into_pipeline_data();
handle_table_command(
engine_state,
stack,
call,
collected,
row_offset,
table_view,
term_width,
)
input.data = val.collect()?.into_pipeline_data();
handle_table_command(input, cfg)
}
PipelineData::Value(Value::Error { error, .. }, ..) => {
// Propagate this error outward, so that it goes to stderr
@ -302,72 +332,54 @@ fn handle_table_command(
}
PipelineData::Value(Value::CustomValue { val, .. }, ..) => {
let base_pipeline = val.to_base_value(span)?.into_pipeline_data();
Table.run(engine_state, stack, call, base_pipeline)
Table.run(input.engine_state, input.stack, input.call, base_pipeline)
}
PipelineData::Value(Value::Range { val, .. }, metadata) => {
let ctrlc = input.engine_state.ctrlc.clone();
let stream = ListStream::from_stream(val.into_range_iter(ctrlc.clone())?, ctrlc);
input.data = PipelineData::Empty;
handle_row_stream(input, cfg, stream, metadata)
}
PipelineData::Value(Value::Range { val, .. }, metadata) => handle_row_stream(
engine_state,
stack,
ListStream::from_stream(val.into_range_iter(ctrlc.clone())?, ctrlc.clone()),
call,
row_offset,
ctrlc,
metadata,
),
x => Ok(x),
}
}
fn supported_table_modes() -> Vec<Value> {
vec![
Value::test_string("basic"),
Value::test_string("compact"),
Value::test_string("compact_double"),
Value::test_string("default"),
Value::test_string("heavy"),
Value::test_string("light"),
Value::test_string("none"),
Value::test_string("reinforced"),
Value::test_string("rounded"),
Value::test_string("thin"),
Value::test_string("with_love"),
Value::test_string("psql"),
Value::test_string("markdown"),
Value::test_string("dots"),
Value::test_string("restructured"),
Value::test_string("ascii_rounded"),
Value::test_string("basic_compact"),
]
}
#[allow(clippy::too_many_arguments)]
fn handle_record(
record: Record,
span: Span,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
table_view: TableView,
term_width: usize,
ctrlc: Option<Arc<AtomicBool>>,
config: &Config,
input: CmdInput,
cfg: TableConfig,
mut record: Record,
) -> Result<PipelineData, ShellError> {
// Create a StyleComputer to compute styles for each value in the table.
let style_computer = &StyleComputer::from_config(engine_state, stack);
let config = get_config(input.engine_state, input.stack);
let span = input.data.span().unwrap_or(input.call.head);
let styles = &StyleComputer::from_config(input.engine_state, input.stack);
let ctrlc = input.engine_state.ctrlc.clone();
let ctrlc1 = ctrlc.clone();
let result = if record.is_empty() {
create_empty_placeholder("record", term_width, engine_state, stack)
} else {
let indent = (config.table_indent.left, config.table_indent.right);
let opts = TableOpts::new(config, style_computer, ctrlc, span, 0, term_width, indent);
let result = build_table_kv(record, table_view, opts, span)?;
match result {
Some(output) => maybe_strip_color(output, config),
None => report_unsuccessful_output(ctrlc1, term_width),
}
if record.is_empty() {
let value =
create_empty_placeholder("record", cfg.term_width, input.engine_state, input.stack);
let value = Value::string(value, span);
return Ok(value.into_pipeline_data());
};
let val = Value::string(result, call.head);
if let Some(limit) = cfg.abbreviation {
if record.cols.len() > limit * 2 + 1 {
record.cols = abbreviate_list(&record.cols, limit, String::from("..."));
record.vals =
abbreviate_list(&record.vals, limit, Value::string("...", Span::unknown()));
}
}
let indent = (config.table_indent.left, config.table_indent.right);
let opts = TableOpts::new(&config, styles, ctrlc, span, 0, cfg.term_width, indent);
let result = build_table_kv(record, cfg.table_view, opts, span)?;
let result = match result {
Some(output) => maybe_strip_color(output, &config),
None => report_unsuccessful_output(ctrlc1, cfg.term_width),
};
let val = Value::string(result, span);
Ok(val.into_pipeline_data())
}
@ -429,27 +441,31 @@ fn build_table_batch(
}
fn handle_row_stream(
engine_state: &EngineState,
stack: &mut Stack,
input: CmdInput<'_>,
cfg: TableConfig,
stream: ListStream,
call: &Call,
row_offset: usize,
ctrlc: Option<Arc<AtomicBool>>,
metadata: Option<Box<PipelineMetadata>>,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let ctrlc = input.engine_state.ctrlc.clone();
let stream = match metadata.as_deref() {
// First, `ls` sources:
Some(PipelineMetadata {
data_source: DataSource::Ls,
}) => {
let config = get_config(engine_state, stack);
let config = get_config(input.engine_state, input.stack);
let ctrlc = ctrlc.clone();
let ls_colors_env_str = match stack.get_env_var(engine_state, "LS_COLORS") {
Some(v) => Some(env_to_string("LS_COLORS", &v, engine_state, stack)?),
let ls_colors_env_str = match input.stack.get_env_var(input.engine_state, "LS_COLORS") {
Some(v) => Some(env_to_string(
"LS_COLORS",
&v,
input.engine_state,
input.stack,
)?),
None => None,
};
let ls_colors = get_ls_colors(ls_colors_env_str);
let span = input.call.head;
ListStream::from_stream(
stream.map(move |mut x| match &mut x {
@ -459,7 +475,7 @@ fn handle_row_stream(
while idx < record.len() {
// Only the name column gets special colors, for now
if record.cols[idx] == "name" {
let span = record.vals.get(idx).map(|v| v.span()).unwrap_or(head);
let span = record.vals.get(idx).map(|v| v.span()).unwrap_or(span);
if let Some(Value::String { val, .. }) = record.vals.get(idx) {
let val = render_path_name(val, &config, &ls_colors, span);
if let Some(val) = val {
@ -483,6 +499,7 @@ fn handle_row_stream(
data_source: DataSource::HtmlThemes,
}) => {
let ctrlc = ctrlc.clone();
let span = input.call.head;
ListStream::from_stream(
stream.map(move |mut x| match &mut x {
@ -494,7 +511,7 @@ fn handle_row_stream(
// Simple routine to grab the hex code, convert to a style,
// then place it in a new Value::String.
let span = record.vals.get(idx).map(|v| v.span()).unwrap_or(head);
let span = record.vals.get(idx).map(|v| v.span()).unwrap_or(span);
if let Some(Value::String { val, .. }) = record.vals.get(idx) {
let s = match color_from_hex(val) {
Ok(c) => match c {
@ -523,48 +540,23 @@ fn handle_row_stream(
_ => stream,
};
let head = call.head;
let width_param: Option<i64> = call.get_flag(engine_state, stack, "width")?;
let collapse: bool = call.has_flag("collapse");
let expand: bool = call.has_flag("expand");
let limit: Option<usize> = call.get_flag(engine_state, stack, "expand-deep")?;
let flatten: bool = call.has_flag("flatten");
let flatten_separator: Option<String> =
call.get_flag(engine_state, stack, "flatten-separator")?;
let table_view = match (expand, collapse) {
(_, true) => TableView::Collapsed,
(true, _) => TableView::Expanded {
flatten,
flatten_separator,
limit,
},
_ => TableView::General,
};
let paginator = PagingTableCreator::new(
input.call.head,
stream,
// These are passed in as a way to have PagingTable create StyleComputers
// for the values it outputs. Because engine_state is passed in, config doesn't need to.
input.engine_state.clone(),
input.stack.clone(),
ctrlc.clone(),
cfg,
);
let stream = RawStream::new(Box::new(paginator), ctrlc, input.call.head, None);
Ok(PipelineData::ExternalStream {
stdout: Some(RawStream::new(
Box::new(PagingTableCreator::new(
head,
stream,
// These are passed in as a way to have PagingTable create StyleComputers
// for the values it outputs. Because engine_state is passed in, config doesn't need to.
engine_state.clone(),
stack.clone(),
ctrlc.clone(),
row_offset,
width_param,
table_view,
)),
ctrlc,
head,
None,
)),
stdout: Some(stream),
stderr: None,
exit_code: None,
span: head,
span: input.call.head,
metadata: None,
trim_end_newline: false,
})
@ -600,24 +592,19 @@ struct PagingTableCreator {
engine_state: EngineState,
stack: Stack,
ctrlc: Option<Arc<AtomicBool>>,
row_offset: usize,
width_param: Option<i64>,
view: TableView,
elements_displayed: usize,
reached_end: bool,
cfg: TableConfig,
}
impl PagingTableCreator {
#[allow(clippy::too_many_arguments)]
fn new(
head: Span,
stream: ListStream,
engine_state: EngineState,
stack: Stack,
ctrlc: Option<Arc<AtomicBool>>,
row_offset: usize,
width_param: Option<i64>,
view: TableView,
cfg: TableConfig,
) -> Self {
PagingTableCreator {
head,
@ -625,9 +612,7 @@ impl PagingTableCreator {
engine_state,
stack,
ctrlc,
row_offset,
width_param,
view,
cfg,
elements_displayed: 0,
reached_end: false,
}
@ -686,11 +671,23 @@ impl PagingTableCreator {
style_comp,
self.ctrlc.clone(),
self.head,
self.row_offset,
get_width_param(self.width_param),
self.cfg.row_offset,
self.cfg.term_width,
(cfg.table_indent.left, cfg.table_indent.right),
)
}
fn build_table(&mut self, batch: Vec<Value>) -> Result<Option<String>, ShellError> {
match &self.cfg.table_view {
TableView::General => self.build_general(batch),
TableView::Collapsed => self.build_collapsed(batch),
TableView::Expanded {
limit,
flatten,
flatten_separator,
} => self.build_extended(batch, *limit, *flatten, flatten_separator.clone()),
}
}
}
impl Iterator for PagingTableCreator {
@ -736,49 +733,52 @@ impl Iterator for PagingTableCreator {
// Increase elements_displayed by one so on next iteration next branch of this
// if else triggers and terminates stream
self.elements_displayed = 1;
let term_width = get_width_param(self.width_param);
let result =
create_empty_placeholder("list", term_width, &self.engine_state, &self.stack);
let result = create_empty_placeholder(
"list",
self.cfg.term_width,
&self.engine_state,
&self.stack,
);
Some(Ok(result.into_bytes()))
} else {
None
};
}
let table = match &self.view {
TableView::General => self.build_general(batch),
TableView::Collapsed => self.build_collapsed(batch),
TableView::Expanded {
limit,
flatten,
flatten_separator,
} => self.build_extended(batch, *limit, *flatten, flatten_separator.clone()),
};
if let Some(limit) = self.cfg.abbreviation {
// todo: could be optimized cause we already consumed the list there's no point in goint back to pagination;
self.row_offset += idx;
if batch.len() > limit * 2 + 1 {
batch = abbreviate_list(
&batch,
limit,
Value::string(String::from("..."), Span::unknown()),
);
match table {
Ok(Some(table)) => {
let table = maybe_strip_color(table, &get_config(&self.engine_state, &self.stack));
let is_record_list = batch[..limit]
.iter()
.all(|value| matches!(value, Value::Record { .. }))
&& batch[limit + 1..]
.iter()
.all(|value| matches!(value, Value::Record { .. }));
let mut bytes = table.as_bytes().to_vec();
bytes.push(b'\n'); // nu-table tables don't come with a newline on the end
if limit > 0 && is_record_list {
// in case it's a record list we set a default text to each column instead of a single value.
Some(Ok(bytes))
let cols = batch[0].as_record().expect("ok").cols.clone();
let vals =
vec![Value::string(String::from("..."), Span::unknown()); cols.len()];
batch[limit] = Value::record(Record { cols, vals }, Span::unknown());
}
}
Ok(None) => {
let msg = if nu_utils::ctrl_c::was_pressed(&self.ctrlc) {
"".into()
} else {
// assume this failed because the table was too wide
// TODO: more robust error classification
let term_width = get_width_param(self.width_param);
format!("Couldn't fit table into {term_width} columns!")
};
Some(Ok(msg.as_bytes().to_vec()))
}
Err(err) => Some(Err(err)),
}
let table = self.build_table(batch);
self.cfg.row_offset += idx;
let config = get_config(&self.engine_state, &self.stack);
convert_table_to_output(table, &config, &self.ctrlc, self.cfg.term_width)
}
}
@ -822,7 +822,7 @@ fn render_path_name(
Some(Value::string(val, span))
}
#[derive(Debug)]
#[derive(Debug, Clone)]
enum TableView {
General,
Collapsed,
@ -869,3 +869,70 @@ fn create_empty_placeholder(
.draw(config, termwidth)
.expect("Could not create empty table placeholder")
}
fn convert_table_to_output(
table: Result<Option<String>, ShellError>,
config: &Config,
ctrlc: &Option<Arc<AtomicBool>>,
term_width: usize,
) -> Option<Result<Vec<u8>, ShellError>> {
match table {
Ok(Some(table)) => {
let table = maybe_strip_color(table, config);
let mut bytes = table.as_bytes().to_vec();
bytes.push(b'\n'); // nu-table tables don't come with a newline on the end
Some(Ok(bytes))
}
Ok(None) => {
let msg = if nu_utils::ctrl_c::was_pressed(ctrlc) {
String::from("")
} else {
// assume this failed because the table was too wide
// TODO: more robust error classification
format!("Couldn't fit table into {} columns!", term_width)
};
Some(Ok(msg.as_bytes().to_vec()))
}
Err(err) => Some(Err(err)),
}
}
fn abbreviate_list<T>(list: &[T], limit: usize, text: T) -> Vec<T>
where
T: Clone,
{
let head = &list[..limit];
let tail = &list[list.len() - limit..];
let mut out = Vec::with_capacity(limit * 2 + 1);
out.extend(head.iter().cloned());
out.push(text);
out.extend(tail.iter().cloned());
out
}
fn supported_table_modes() -> Vec<Value> {
vec![
Value::test_string("basic"),
Value::test_string("compact"),
Value::test_string("compact_double"),
Value::test_string("default"),
Value::test_string("heavy"),
Value::test_string("light"),
Value::test_string("none"),
Value::test_string("reinforced"),
Value::test_string("rounded"),
Value::test_string("thin"),
Value::test_string("with_love"),
Value::test_string("psql"),
Value::test_string("markdown"),
Value::test_string("dots"),
Value::test_string("restructured"),
Value::test_string("ascii_rounded"),
Value::test_string("basic_compact"),
]
}

View File

@ -2686,3 +2686,114 @@ fn table_leading_trailing_space_bg_expand() {
"╭───┬───────┬───────┬───────────────────────╮│ # │ a │ b │ c │├───┼───────┼───────┼───────────────────────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ 4 │ hello │ ╭───┬───────────────╮ ││ │ │ world │ │ 0 │ 1 │ ││ │ │ │ │ 1 │ 2 │ ││ │ │ │ │ 2 │ ╭───┬───────╮ │ ││ │ │ │ │ │ │ 0 │ 1 │ │ ││ │ │ │ │ │ │ 1 │ 2 │ │ ││ │ │ │ │ │ │ 2 │ 3 │ │ ││ │ │ │ │ │ ╰───┴───────╯ │ ││ │ │ │ ╰───┴───────────────╯ │╰───┴───────┴───────┴───────────────────────╯"
);
}
#[test]
fn table_abbreviation() {
let actual = nu!(
r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table -a 100"#
);
assert_eq!(actual.out, "╭───┬───┬───┬────────────────╮│ # │ a │ b │ c │├───┼───┼───┼────────────────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ 4 │ 5 │ [list 3 items] ││ 2 │ 1 │ 2 │ 3 ││ 3 │ 1 │ 2 │ 3 ││ 4 │ 1 │ 2 │ 3 ││ 5 │ 1 │ 2 │ 3 ││ 6 │ 1 │ 2 │ 3 │╰───┴───┴───┴────────────────╯");
let actual = nu!(
r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table -a 2"#
);
assert_eq!(actual.out, "╭───┬─────┬─────┬────────────────╮│ # │ a │ b │ c │├───┼─────┼─────┼────────────────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ 4 │ 5 │ [list 3 items] ││ 2 │ ... │ ... │ ... ││ 3 │ 1 │ 2 │ 3 ││ 4 │ 1 │ 2 │ 3 │╰───┴─────┴─────┴────────────────╯");
let actual = nu!(
r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table -a 1"#
);
assert_eq!(actual.out, "╭───┬─────┬─────┬─────╮│ # │ a │ b │ c │├───┼─────┼─────┼─────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ ... │ ... │ ... ││ 2 │ 1 │ 2 │ 3 │╰───┴─────┴─────┴─────╯");
let actual = nu!(
r#"[[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table -a 0"#
);
assert_eq!(actual.out, "╭───┬─────╮│ 0 │ ... │╰───┴─────╯");
}
#[test]
fn table_abbreviation_kv() {
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 100"#
);
assert_eq!(actual.out, "╭───┬───────────────────╮│ a │ 1 ││ b │ {record 3 fields} ││ c │ [list 4 items] ││ e │ 1 ││ q │ 2 ││ t │ 4 ││ r │ 1 ││ x │ 9 │╰───┴───────────────────╯");
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 2"#
);
assert_eq!(actual.out, "╭─────┬───────────────────╮│ a │ 1 ││ b │ {record 3 fields} ││ ... │ ... ││ r │ 1 ││ x │ 9 │╰─────┴───────────────────╯");
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 1"#
);
assert_eq!(
actual.out,
"╭─────┬─────╮│ a │ 1 ││ ... │ ... ││ x │ 9 │╰─────┴─────╯"
);
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 0"#
);
assert_eq!(actual.out, "╭─────┬─────╮│ ... │ ... │╰─────┴─────╯");
}
#[test]
fn table_abbreviation_kv_expand() {
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 100 -e"#
);
assert_eq!(actual.out, "╭───┬───────────────────╮│ a │ 1 ││ │ ╭───┬───────────╮ ││ b │ │ a │ 1 │ ││ │ │ │ ╭───┬───╮ │ ││ │ │ b │ │ 0 │ 1 │ │ ││ │ │ │ │ 1 │ 2 │ │ ││ │ │ │ │ 2 │ 3 │ │ ││ │ │ │ ╰───┴───╯ │ ││ │ │ │ ╭───┬───╮ │ ││ │ │ c │ │ 0 │ 1 │ │ ││ │ │ │ │ 1 │ 2 │ │ ││ │ │ │ │ 2 │ 3 │ │ ││ │ │ │ ╰───┴───╯ │ ││ │ ╰───┴───────────╯ ││ │ ╭───┬───────────╮ ││ c │ │ 0 │ 1 │ ││ │ │ 1 │ 2 │ ││ │ │ 2 │ ╭───┬───╮ │ ││ │ │ │ │ 0 │ 1 │ │ ││ │ │ │ │ 1 │ 2 │ │ ││ │ │ │ │ 2 │ 3 │ │ ││ │ │ │ ╰───┴───╯ │ ││ │ │ 3 │ 3 │ ││ │ ╰───┴───────────╯ ││ e │ 1 ││ q │ 2 ││ t │ 4 ││ r │ 1 ││ x │ 9 │╰───┴───────────────────╯");
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 2 -e"#
);
assert_eq!(actual.out, "╭─────┬───────────────────╮│ a │ 1 ││ │ ╭───┬───────────╮ ││ b │ │ a │ 1 │ ││ │ │ │ ╭───┬───╮ │ ││ │ │ b │ │ 0 │ 1 │ │ ││ │ │ │ │ 1 │ 2 │ │ ││ │ │ │ │ 2 │ 3 │ │ ││ │ │ │ ╰───┴───╯ │ ││ │ │ │ ╭───┬───╮ │ ││ │ │ c │ │ 0 │ 1 │ │ ││ │ │ │ │ 1 │ 2 │ │ ││ │ │ │ │ 2 │ 3 │ │ ││ │ │ │ ╰───┴───╯ │ ││ │ ╰───┴───────────╯ ││ ... │ ... ││ r │ 1 ││ x │ 9 │╰─────┴───────────────────╯");
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 1 -e"#
);
assert_eq!(
actual.out,
"╭─────┬─────╮│ a │ 1 ││ ... │ ... ││ x │ 9 │╰─────┴─────╯"
);
let actual = nu!(
r#"{ a: 1 b: { a: 1 b: [1 2 3] c: [1 2 3] } c: [1 2 [1 2 3] 3] e: 1 q: 2 t: 4 r: 1 x: 9 } | table -a 0 -e"#
);
assert_eq!(actual.out, "╭─────┬─────╮│ ... │ ... │╰─────┴─────╯");
}
#[test]
fn table_abbreviation_by_config() {
let actual = nu!(
r#"$env.config.table.abbreviated_row_count = 100; [[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table"#
);
assert_eq!(actual.out, "╭───┬───┬───┬────────────────╮│ # │ a │ b │ c │├───┼───┼───┼────────────────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ 4 │ 5 │ [list 3 items] ││ 2 │ 1 │ 2 │ 3 ││ 3 │ 1 │ 2 │ 3 ││ 4 │ 1 │ 2 │ 3 ││ 5 │ 1 │ 2 │ 3 ││ 6 │ 1 │ 2 │ 3 │╰───┴───┴───┴────────────────╯");
let actual = nu!(
r#"$env.config.table.abbreviated_row_count = 2; [[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table"#
);
assert_eq!(actual.out, "╭───┬─────┬─────┬────────────────╮│ # │ a │ b │ c │├───┼─────┼─────┼────────────────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ 4 │ 5 │ [list 3 items] ││ 2 │ ... │ ... │ ... ││ 3 │ 1 │ 2 │ 3 ││ 4 │ 1 │ 2 │ 3 │╰───┴─────┴─────┴────────────────╯");
let actual = nu!(
r#"$env.config.table.abbreviated_row_count = 1; [[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table"#
);
assert_eq!(actual.out, "╭───┬─────┬─────┬─────╮│ # │ a │ b │ c │├───┼─────┼─────┼─────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ ... │ ... │ ... ││ 2 │ 1 │ 2 │ 3 │╰───┴─────┴─────┴─────╯");
let actual = nu!(
r#"$env.config.table.abbreviated_row_count = 0; [[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table"#
);
assert_eq!(actual.out, "╭───┬─────╮│ 0 │ ... │╰───┴─────╯");
}
#[test]
fn table_abbreviation_by_config_override() {
let actual = nu!(
r#"$env.config.table.abbreviated_row_count = 2; [[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table -a 1"#
);
assert_eq!(actual.out, "╭───┬─────┬─────┬─────╮│ # │ a │ b │ c │├───┼─────┼─────┼─────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ ... │ ... │ ... ││ 2 │ 1 │ 2 │ 3 │╰───┴─────┴─────┴─────╯");
let actual = nu!(
r#"$env.config.table.abbreviated_row_count = 1; [[a b, c]; [1 2 3] [4 5 [1 2 3]] [1 2 3] [1 2 3] [1 2 3] [1 2 3] [1 2 3]] | table -a 2"#
);
assert_eq!(actual.out, "╭───┬─────┬─────┬────────────────╮│ # │ a │ b │ c │├───┼─────┼─────┼────────────────┤│ 0 │ 1 │ 2 │ 3 ││ 1 │ 4 │ 5 │ [list 3 items] ││ 2 │ ... │ ... │ ... ││ 3 │ 1 │ 2 │ 3 ││ 4 │ 1 │ 2 │ 3 │╰───┴─────┴─────┴────────────────╯");
}

View File

@ -76,6 +76,7 @@ pub struct Config {
pub table_move_header: bool,
pub table_show_empty: bool,
pub table_indent: TableIndent,
pub table_abbreviation_threshold: Option<usize>,
pub use_ls_colors: bool,
pub color_config: HashMap<String, Value>,
pub use_grid_icons: bool,
@ -134,6 +135,7 @@ impl Default for Config {
trim_strategy: TRIM_STRATEGY_DEFAULT,
table_move_header: false,
table_indent: TableIndent { left: 1, right: 1 },
table_abbreviation_threshold: None,
datetime_normal_format: None,
datetime_table_format: None,
@ -1023,6 +1025,17 @@ impl Value {
"show_empty" => {
try_bool!(cols, vals, index, span, table_show_empty)
}
"abbreviated_row_count" => {
if let Ok(b) = value.as_int() {
if b < 0 {
invalid!(Some(span), "should be an int unsigned");
}
config.table_abbreviation_threshold = Some(b as usize);
} else {
invalid!(Some(span), "should be an int");
}
}
x => {
invalid_key!(
cols,

View File

@ -165,6 +165,7 @@ $env.config = {
truncating_suffix: "..." # A suffix used by the 'truncating' methodology
}
header_on_separator: false # show header text on separator/border line
# abbreviated_row_count: 10 # limit data rows from top and bottom after reaching a set point
}
error_style: "fancy" # "fancy" or "plain" for screen reader-friendly error messages