forked from extern/nushell
add a new inspect command for more debugging (#8028)
# Description The purpose of this command is to help to debug pipelines. It works by allowing you to inject the `inspect` command into a pipeline at any point. Then it shows you what the input description is and what the input values are that are passed into `inspect`. With each step it prints this information out while also passing the value information on to the next step in the pipeline.  This command is kind of a "hack job" because it clones maybe too much and I had to get creative in order to output two different tables. I'm sure there are many ways this can be improved or combined into other commands but I wanted to start here. Note that the `inspect` output is written to stderr and the normal nushell output is written to stdout. If we were to output both to stdout, nushell would get confused. # User-Facing Changes # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
This commit is contained in:
parent
b9106b633b
commit
0780300fb3
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2801,6 +2801,7 @@ dependencies = [
|
|||||||
"shadow-rs",
|
"shadow-rs",
|
||||||
"sqlparser",
|
"sqlparser",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
|
"tabled",
|
||||||
"terminal_size 0.2.1",
|
"terminal_size 0.2.1",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"titlecase",
|
"titlecase",
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["The Nushell Project Developers"]
|
authors = ["The Nushell Project Developers"]
|
||||||
|
build = "build.rs"
|
||||||
description = "Nushell's built-in commands"
|
description = "Nushell's built-in commands"
|
||||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
|
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||||
version = "0.75.1"
|
version = "0.75.1"
|
||||||
build = "build.rs"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
nu-ansi-term = "0.46.0"
|
||||||
nu-color-config = { path = "../nu-color-config", version = "0.75.1" }
|
nu-color-config = { path = "../nu-color-config", version = "0.75.1" }
|
||||||
nu-engine = { path = "../nu-engine", version = "0.75.1" }
|
nu-engine = { path = "../nu-engine", version = "0.75.1" }
|
||||||
|
nu-explore = { path = "../nu-explore", version = "0.75.1" }
|
||||||
nu-glob = { path = "../nu-glob", version = "0.75.1" }
|
nu-glob = { path = "../nu-glob", version = "0.75.1" }
|
||||||
nu-json = { path = "../nu-json", version = "0.75.1" }
|
nu-json = { path = "../nu-json", version = "0.75.1" }
|
||||||
nu-parser = { path = "../nu-parser", version = "0.75.1" }
|
nu-parser = { path = "../nu-parser", version = "0.75.1" }
|
||||||
@ -23,18 +25,17 @@ nu-system = { path = "../nu-system", version = "0.75.1" }
|
|||||||
nu-table = { path = "../nu-table", version = "0.75.1" }
|
nu-table = { path = "../nu-table", version = "0.75.1" }
|
||||||
nu-term-grid = { path = "../nu-term-grid", version = "0.75.1" }
|
nu-term-grid = { path = "../nu-term-grid", version = "0.75.1" }
|
||||||
nu-utils = { path = "../nu-utils", version = "0.75.1" }
|
nu-utils = { path = "../nu-utils", version = "0.75.1" }
|
||||||
nu-explore = { path = "../nu-explore", version = "0.75.1" }
|
|
||||||
nu-ansi-term = "0.46.0"
|
|
||||||
num-format = { version = "0.4.3" }
|
num-format = { version = "0.4.3" }
|
||||||
|
|
||||||
# Potential dependencies for extras
|
# Potential dependencies for extras
|
||||||
|
Inflector = "0.11"
|
||||||
alphanumeric-sort = "1.4.4"
|
alphanumeric-sort = "1.4.4"
|
||||||
atty = "0.2.14"
|
atty = "0.2.14"
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
byteorder = "1.4.3"
|
byteorder = "1.4.3"
|
||||||
bytesize = "1.1.0"
|
bytesize = "1.1.0"
|
||||||
calamine = "0.19.1"
|
calamine = "0.19.1"
|
||||||
chrono = { version = "0.4.23", features = ["unstable-locales", "std"], default-features = false }
|
chrono = { version = "0.4.23", features = ["std", "unstable-locales"], default-features = false }
|
||||||
chrono-humanize = "0.2.1"
|
chrono-humanize = "0.2.1"
|
||||||
chrono-tz = "0.8.1"
|
chrono-tz = "0.8.1"
|
||||||
crossterm = "0.24.0"
|
crossterm = "0.24.0"
|
||||||
@ -52,7 +53,6 @@ htmlescape = "0.3.1"
|
|||||||
ical = "0.8.0"
|
ical = "0.8.0"
|
||||||
indexmap = { version = "1.7", features = ["serde-1"] }
|
indexmap = { version = "1.7", features = ["serde-1"] }
|
||||||
indicatif = "0.17.2"
|
indicatif = "0.17.2"
|
||||||
Inflector = "0.11"
|
|
||||||
is-root = "0.1.2"
|
is-root = "0.1.2"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
@ -74,45 +74,44 @@ regex = "1.7.1"
|
|||||||
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
reqwest = { version = "0.11", features = ["blocking", "json"] }
|
||||||
roxmltree = "0.17.0"
|
roxmltree = "0.17.0"
|
||||||
rust-embed = "6.3.0"
|
rust-embed = "6.3.0"
|
||||||
|
rust-ini = "0.18.0"
|
||||||
same-file = "1.0.6"
|
same-file = "1.0.6"
|
||||||
serde = { version = "1.0.123", features = ["derive"] }
|
serde = { version = "1.0.123", features = ["derive"] }
|
||||||
rust-ini = "0.18.0"
|
|
||||||
serde_urlencoded = "0.7.0"
|
serde_urlencoded = "0.7.0"
|
||||||
serde_yaml = "0.9.4"
|
serde_yaml = "0.9.4"
|
||||||
sha2 = "0.10.0"
|
sha2 = "0.10.0"
|
||||||
# Disable default features b/c the default features build Git (very slow to compile)
|
# Disable default features b/c the default features build Git (very slow to compile)
|
||||||
|
percent-encoding = "2.2.0"
|
||||||
|
reedline = { version = "0.15.0", features = ["bashisms", "sqlite"] }
|
||||||
|
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||||
shadow-rs = { version = "0.20.0", default-features = false }
|
shadow-rs = { version = "0.20.0", default-features = false }
|
||||||
|
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
|
||||||
sysinfo = "0.27.7"
|
sysinfo = "0.27.7"
|
||||||
|
tabled = "0.10.0"
|
||||||
terminal_size = "0.2.1"
|
terminal_size = "0.2.1"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
titlecase = "2.0.0"
|
titlecase = "2.0.0"
|
||||||
unicode-segmentation = "1.10.0"
|
|
||||||
toml = "0.7.1"
|
toml = "0.7.1"
|
||||||
url = "2.2.1"
|
unicode-segmentation = "1.10.0"
|
||||||
percent-encoding = "2.2.0"
|
|
||||||
uuid = { version = "1.2.2", features = ["v4"] }
|
|
||||||
which = { version = "4.4.0", optional = true }
|
|
||||||
reedline = { version = "0.15.0", features = ["bashisms", "sqlite"] }
|
|
||||||
wax = { version = "0.5.0" }
|
|
||||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
|
||||||
sqlparser = { version = "0.30.0", features = ["serde"], optional = true }
|
|
||||||
unicode-width = "0.1.10"
|
unicode-width = "0.1.10"
|
||||||
|
url = "2.2.1"
|
||||||
|
uuid = { version = "1.2.2", features = ["v4"] }
|
||||||
|
wax = { version = "0.5.0" }
|
||||||
|
which = { version = "4.4.0", optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.10.1"
|
winreg = "0.10.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
libc = "0.2"
|
||||||
umask = "2.0.0"
|
umask = "2.0.0"
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
libc = "0.2"
|
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
|
||||||
version = "3.0.1"
|
|
||||||
optional = true
|
optional = true
|
||||||
|
version = "3.0.1"
|
||||||
|
|
||||||
[dependencies.polars]
|
[dependencies.polars]
|
||||||
version = "0.26.1"
|
|
||||||
optional = true
|
|
||||||
features = [
|
features = [
|
||||||
"arg_where",
|
"arg_where",
|
||||||
"checked_arithmetic",
|
"checked_arithmetic",
|
||||||
@ -121,9 +120,9 @@ features = [
|
|||||||
"csv-file",
|
"csv-file",
|
||||||
"cum_agg",
|
"cum_agg",
|
||||||
"default",
|
"default",
|
||||||
|
"dtype-categorical",
|
||||||
"dtype-datetime",
|
"dtype-datetime",
|
||||||
"dtype-struct",
|
"dtype-struct",
|
||||||
"dtype-categorical",
|
|
||||||
"dynamic_groupby",
|
"dynamic_groupby",
|
||||||
"ipc",
|
"ipc",
|
||||||
"is_in",
|
"is_in",
|
||||||
@ -140,17 +139,19 @@ features = [
|
|||||||
"strings",
|
"strings",
|
||||||
"to_dummies",
|
"to_dummies",
|
||||||
]
|
]
|
||||||
|
optional = true
|
||||||
|
version = "0.26.1"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies.windows]
|
[target.'cfg(windows)'.dependencies.windows]
|
||||||
version = "0.44.0"
|
|
||||||
features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_SystemServices"]
|
features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_SystemServices"]
|
||||||
|
version = "0.44.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
dataframe = ["num", "polars", "sqlparser"]
|
||||||
|
plugin = ["nu-parser/plugin"]
|
||||||
|
sqlite = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
which-support = ["which"]
|
which-support = ["which"]
|
||||||
plugin = ["nu-parser/plugin"]
|
|
||||||
dataframe = ["polars", "num", "sqlparser"]
|
|
||||||
sqlite = ["rusqlite"] # TODO: given that rusqlite is included in reedline, should we just always include it?
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = { version = "0.20.0", default-features = false }
|
shadow-rs = { version = "0.20.0", default-features = false }
|
||||||
@ -158,8 +159,8 @@ shadow-rs = { version = "0.20.0", default-features = false }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
nu-test-support = { path = "../nu-test-support", version = "0.75.1" }
|
nu-test-support = { path = "../nu-test-support", version = "0.75.1" }
|
||||||
|
|
||||||
hamcrest2 = "0.3.0"
|
|
||||||
dirs-next = "2.0.0"
|
dirs-next = "2.0.0"
|
||||||
|
hamcrest2 = "0.3.0"
|
||||||
proptest = "1.0.0"
|
proptest = "1.0.0"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
|
@ -175,6 +175,7 @@ pub fn create_default_context() -> EngineState {
|
|||||||
Complete,
|
Complete,
|
||||||
Explain,
|
Explain,
|
||||||
External,
|
External,
|
||||||
|
Inspect,
|
||||||
NuCheck,
|
NuCheck,
|
||||||
Sys,
|
Sys,
|
||||||
TimeIt,
|
TimeIt,
|
||||||
|
64
crates/nu-command/src/system/inspect.rs
Normal file
64
crates/nu-command/src/system/inspect.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use super::inspect_table;
|
||||||
|
use nu_protocol::{
|
||||||
|
ast::Call,
|
||||||
|
engine::{Command, EngineState, Stack},
|
||||||
|
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||||
|
};
|
||||||
|
use terminal_size::{terminal_size, Height, Width};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Inspect;
|
||||||
|
|
||||||
|
impl Command for Inspect {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"inspect"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Inspect pipeline results while running a pipeline"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("inspect")
|
||||||
|
.input_output_types(vec![(Type::Any, Type::Any)])
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.category(Category::Debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let input_metadata = input.metadata();
|
||||||
|
let input_val = input.into_value(call.head);
|
||||||
|
let original_input = input_val.clone();
|
||||||
|
let description = match input_val {
|
||||||
|
Value::CustomValue { ref val, .. } => val.value_string(),
|
||||||
|
_ => input_val.get_type().to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cols, _rows) = match terminal_size() {
|
||||||
|
Some((w, h)) => (Width(w.0), Height(h.0)),
|
||||||
|
None => (Width(0), Height(0)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let table = inspect_table::build_table(input_val, description, cols.0 as usize);
|
||||||
|
|
||||||
|
// Note that this is printed to stderr. The reason for this is so it doesn't disrupt the regular nushell
|
||||||
|
// tabular output. If we printed to stdout, nushell would get confused with two outputs.
|
||||||
|
eprintln!("{table}\n");
|
||||||
|
|
||||||
|
Ok(original_input.into_pipeline_data_with_metadata(input_metadata))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Inspect pipeline results",
|
||||||
|
example: "ls | inspect | get name | inspect",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
465
crates/nu-command/src/system/inspect_table.rs
Normal file
465
crates/nu-command/src/system/inspect_table.rs
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
use nu_protocol::Value;
|
||||||
|
use tabled::{
|
||||||
|
builder::Builder,
|
||||||
|
peaker::PriorityMax,
|
||||||
|
width::{MinWidth, Wrap},
|
||||||
|
Style,
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::{
|
||||||
|
global_horizontal_char::SetHorizontalChar, peak2::Peak2, table_column_width::GetColumnWidths,
|
||||||
|
truncate_table::TruncateTable, width_increase::IncWidth,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn build_table(value: Value, description: String, termsize: usize) -> String {
|
||||||
|
let (head, mut data) = util::collect_input(value);
|
||||||
|
data.insert(0, head);
|
||||||
|
|
||||||
|
let mut val_table = Builder::from(data).build();
|
||||||
|
let val_table_width = val_table.total_width();
|
||||||
|
|
||||||
|
let desc = vec![vec![String::from("description"), description]];
|
||||||
|
|
||||||
|
let mut desc_table = Builder::from(desc).build();
|
||||||
|
let desc_table_width = desc_table.total_width();
|
||||||
|
|
||||||
|
let width = val_table_width.clamp(desc_table_width, termsize);
|
||||||
|
|
||||||
|
desc_table
|
||||||
|
.with(Style::rounded().off_bottom())
|
||||||
|
.with(Wrap::new(width).priority::<PriorityMax>())
|
||||||
|
.with(MinWidth::new(width).priority::<Peak2>());
|
||||||
|
|
||||||
|
val_table
|
||||||
|
.with(Style::rounded().top_left_corner('├').top_right_corner('┤'))
|
||||||
|
.with(TruncateTable(width))
|
||||||
|
.with(Wrap::new(width).priority::<PriorityMax>())
|
||||||
|
.with(IncWidth(width));
|
||||||
|
|
||||||
|
let mut desc_widths = GetColumnWidths(Vec::new());
|
||||||
|
desc_table.with(&mut desc_widths);
|
||||||
|
|
||||||
|
val_table.with(SetHorizontalChar::new('┼', '┴', 0, desc_widths.0[0]));
|
||||||
|
|
||||||
|
format!("{desc_table}\n{val_table}")
|
||||||
|
}
|
||||||
|
|
||||||
|
mod truncate_table {
|
||||||
|
use tabled::{
|
||||||
|
papergrid::{
|
||||||
|
records::{Records, RecordsMut, Resizable},
|
||||||
|
width::{CfgWidthFunction, WidthEstimator},
|
||||||
|
Estimate,
|
||||||
|
},
|
||||||
|
TableOption,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct TruncateTable(pub usize);
|
||||||
|
|
||||||
|
impl<R> TableOption<R> for TruncateTable
|
||||||
|
where
|
||||||
|
R: Records + RecordsMut<String> + Resizable,
|
||||||
|
{
|
||||||
|
fn change(&mut self, table: &mut tabled::Table<R>) {
|
||||||
|
let width = table.total_width();
|
||||||
|
if width <= self.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let count_columns = table.get_records().count_columns();
|
||||||
|
if count_columns < 1 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut evaluator = WidthEstimator::default();
|
||||||
|
evaluator.estimate(table.get_records(), table.get_config());
|
||||||
|
let columns_width: Vec<_> = evaluator.into();
|
||||||
|
|
||||||
|
const SPLIT_LINE_WIDTH: usize = 1;
|
||||||
|
let mut width = 0;
|
||||||
|
let mut i = 0;
|
||||||
|
for w in columns_width {
|
||||||
|
width += w + SPLIT_LINE_WIDTH;
|
||||||
|
|
||||||
|
if width >= self.0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 && count_columns > 0 {
|
||||||
|
i = 1;
|
||||||
|
} else if i + 1 == count_columns {
|
||||||
|
// we want to left at least 1 column
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let count_columns = table.get_records().count_columns();
|
||||||
|
let y = count_columns - i;
|
||||||
|
|
||||||
|
let mut column = count_columns;
|
||||||
|
for _ in 0..y {
|
||||||
|
column -= 1;
|
||||||
|
table.get_records_mut().remove_column(column);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.get_records_mut().push_column();
|
||||||
|
|
||||||
|
let width_ctrl = CfgWidthFunction::from_cfg(table.get_config());
|
||||||
|
let last_column = table.get_records().count_columns() - 1;
|
||||||
|
for row in 0..table.get_records().count_rows() {
|
||||||
|
table
|
||||||
|
.get_records_mut()
|
||||||
|
.set((row, last_column), String::from("‥"), &width_ctrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod util {
|
||||||
|
use crate::system::explain::debug_string_without_formatting;
|
||||||
|
use nu_engine::get_columns;
|
||||||
|
use nu_protocol::{ast::PathMember, Span, Value};
|
||||||
|
|
||||||
|
/// Try to build column names and a table grid.
|
||||||
|
pub fn collect_input(value: Value) -> (Vec<String>, Vec<Vec<String>>) {
|
||||||
|
match value {
|
||||||
|
Value::Record { cols, vals, .. } => (
|
||||||
|
cols,
|
||||||
|
vec![vals
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| debug_string_without_formatting(&s))
|
||||||
|
.collect()],
|
||||||
|
),
|
||||||
|
Value::List { vals, .. } => {
|
||||||
|
let mut columns = get_columns(&vals);
|
||||||
|
let data = convert_records_to_dataset(&columns, vals);
|
||||||
|
|
||||||
|
if columns.is_empty() && !data.is_empty() {
|
||||||
|
columns = vec![String::from("")];
|
||||||
|
}
|
||||||
|
|
||||||
|
(columns, data)
|
||||||
|
}
|
||||||
|
Value::String { val, span } => {
|
||||||
|
let lines = val
|
||||||
|
.lines()
|
||||||
|
.map(|line| Value::String {
|
||||||
|
val: line.to_string(),
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
.map(|val| vec![debug_string_without_formatting(&val)])
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
(vec![String::from("")], lines)
|
||||||
|
}
|
||||||
|
Value::Nothing { .. } => (vec![], vec![]),
|
||||||
|
value => (
|
||||||
|
vec![String::from("")],
|
||||||
|
vec![vec![debug_string_without_formatting(&value)]],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_records_to_dataset(cols: &Vec<String>, records: Vec<Value>) -> Vec<Vec<String>> {
|
||||||
|
if !cols.is_empty() {
|
||||||
|
create_table_for_record(cols, &records)
|
||||||
|
} else if cols.is_empty() && records.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else if cols.len() == records.len() {
|
||||||
|
vec![records
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| debug_string_without_formatting(&s))
|
||||||
|
.collect()]
|
||||||
|
} else {
|
||||||
|
records
|
||||||
|
.into_iter()
|
||||||
|
.map(|record| vec![debug_string_without_formatting(&record)])
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_table_for_record(headers: &[String], items: &[Value]) -> Vec<Vec<String>> {
|
||||||
|
let mut data = vec![Vec::new(); items.len()];
|
||||||
|
|
||||||
|
for (i, item) in items.iter().enumerate() {
|
||||||
|
let row = record_create_row(headers, item);
|
||||||
|
data[i] = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
data
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_create_row(headers: &[String], item: &Value) -> Vec<String> {
|
||||||
|
let mut rows = vec![String::default(); headers.len()];
|
||||||
|
|
||||||
|
for (i, header) in headers.iter().enumerate() {
|
||||||
|
let value = record_lookup_value(item, header);
|
||||||
|
rows[i] = debug_string_without_formatting(&value);
|
||||||
|
}
|
||||||
|
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
|
||||||
|
fn record_lookup_value(item: &Value, header: &str) -> Value {
|
||||||
|
match item {
|
||||||
|
Value::Record { .. } => {
|
||||||
|
let path = PathMember::String {
|
||||||
|
val: header.to_owned(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
};
|
||||||
|
|
||||||
|
item.clone()
|
||||||
|
.follow_cell_path(&[path], false, false)
|
||||||
|
.unwrap_or_else(|_| item.clone())
|
||||||
|
}
|
||||||
|
item => item.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod style_no_left_right_1st {
|
||||||
|
use tabled::{papergrid::records::Records, Table, TableOption};
|
||||||
|
|
||||||
|
struct StyleOffLeftRightFirstLine;
|
||||||
|
|
||||||
|
impl<R> TableOption<R> for StyleOffLeftRightFirstLine
|
||||||
|
where
|
||||||
|
R: Records,
|
||||||
|
{
|
||||||
|
fn change(&mut self, table: &mut Table<R>) {
|
||||||
|
let shape = table.shape();
|
||||||
|
let cfg = table.get_config_mut();
|
||||||
|
|
||||||
|
let mut b = cfg.get_border((0, 0), shape);
|
||||||
|
b.left = Some(' ');
|
||||||
|
cfg.set_border((0, 0), b);
|
||||||
|
|
||||||
|
let mut b = cfg.get_border((0, shape.1 - 1), shape);
|
||||||
|
b.right = Some(' ');
|
||||||
|
cfg.set_border((0, 0), b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod peak2 {
|
||||||
|
use tabled::peaker::Peaker;
|
||||||
|
|
||||||
|
pub struct Peak2;
|
||||||
|
|
||||||
|
impl Peaker for Peak2 {
|
||||||
|
fn create() -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peak(&mut self, _: &[usize], _: &[usize]) -> Option<usize> {
|
||||||
|
Some(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod table_column_width {
|
||||||
|
use tabled::papergrid::{records::Records, Estimate};
|
||||||
|
|
||||||
|
pub struct GetColumnWidths(pub Vec<usize>);
|
||||||
|
|
||||||
|
impl<R> tabled::TableOption<R> for GetColumnWidths
|
||||||
|
where
|
||||||
|
R: Records,
|
||||||
|
{
|
||||||
|
fn change(&mut self, table: &mut tabled::Table<R>) {
|
||||||
|
let mut evaluator = tabled::papergrid::width::WidthEstimator::default();
|
||||||
|
evaluator.estimate(table.get_records(), table.get_config());
|
||||||
|
self.0 = evaluator.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod global_horizontal_char {
|
||||||
|
use tabled::{
|
||||||
|
papergrid::{records::Records, width::WidthEstimator, Estimate, Offset::Begin},
|
||||||
|
Table, TableOption,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct SetHorizontalChar {
|
||||||
|
c1: char,
|
||||||
|
c2: char,
|
||||||
|
line: usize,
|
||||||
|
position: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SetHorizontalChar {
|
||||||
|
pub fn new(c1: char, c2: char, line: usize, position: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
c1,
|
||||||
|
c2,
|
||||||
|
line,
|
||||||
|
position,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> TableOption<R> for SetHorizontalChar
|
||||||
|
where
|
||||||
|
R: Records,
|
||||||
|
{
|
||||||
|
fn change(&mut self, table: &mut Table<R>) {
|
||||||
|
let shape = table.shape();
|
||||||
|
|
||||||
|
let is_last_line = self.line == (shape.0 * 2);
|
||||||
|
let mut row = self.line;
|
||||||
|
if is_last_line {
|
||||||
|
row = self.line - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut evaluator = WidthEstimator::default();
|
||||||
|
evaluator.estimate(table.get_records(), table.get_config());
|
||||||
|
let widths: Vec<_> = evaluator.into();
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
for column in 0..shape.1 {
|
||||||
|
let has_vertical = table.get_config().has_vertical(column, shape.1);
|
||||||
|
|
||||||
|
if has_vertical {
|
||||||
|
if self.position == i {
|
||||||
|
let mut border = table.get_config().get_border((row, column), shape);
|
||||||
|
if is_last_line {
|
||||||
|
border.left_bottom_corner = Some(self.c1);
|
||||||
|
} else {
|
||||||
|
border.left_top_corner = Some(self.c1);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.get_config_mut().set_border((row, column), border);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = widths[column];
|
||||||
|
|
||||||
|
if self.position < i + width {
|
||||||
|
let offset = self.position + 1 - i;
|
||||||
|
// let offset = width - offset;
|
||||||
|
|
||||||
|
table.get_config_mut().override_horizontal_border(
|
||||||
|
(self.line, column),
|
||||||
|
self.c2,
|
||||||
|
Begin(offset),
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_vertical = table.get_config().has_vertical(shape.1, shape.1);
|
||||||
|
if self.position == i && has_vertical {
|
||||||
|
let mut border = table.get_config().get_border((row, shape.1), shape);
|
||||||
|
if is_last_line {
|
||||||
|
border.left_bottom_corner = Some(self.c1);
|
||||||
|
} else {
|
||||||
|
border.left_top_corner = Some(self.c1);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.get_config_mut().set_border((row, shape.1), border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod width_increase {
|
||||||
|
use tabled::{
|
||||||
|
object::Cell,
|
||||||
|
papergrid::{
|
||||||
|
records::{Records, RecordsMut},
|
||||||
|
width::WidthEstimator,
|
||||||
|
Entity, Estimate, GridConfig,
|
||||||
|
},
|
||||||
|
peaker::PriorityNone,
|
||||||
|
Modify, Width,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tabled::{peaker::Peaker, Table, TableOption};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct IncWidth(pub usize);
|
||||||
|
|
||||||
|
impl<R> TableOption<R> for IncWidth
|
||||||
|
where
|
||||||
|
R: Records + RecordsMut<String>,
|
||||||
|
{
|
||||||
|
fn change(&mut self, table: &mut Table<R>) {
|
||||||
|
if table.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (widths, total_width) =
|
||||||
|
get_table_widths_with_total(table.get_records(), table.get_config());
|
||||||
|
if total_width >= self.0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let increase_list =
|
||||||
|
get_increase_list(widths, self.0, total_width, PriorityNone::default());
|
||||||
|
|
||||||
|
for (col, width) in increase_list.into_iter().enumerate() {
|
||||||
|
for row in 0..table.get_records().count_rows() {
|
||||||
|
let pad = table.get_config().get_padding(Entity::Cell(row, col));
|
||||||
|
let width = width - pad.left.size - pad.right.size;
|
||||||
|
|
||||||
|
table.with(Modify::new(Cell(row, col)).with(Width::increase(width)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_increase_list<F>(
|
||||||
|
mut widths: Vec<usize>,
|
||||||
|
total_width: usize,
|
||||||
|
mut width: usize,
|
||||||
|
mut peaker: F,
|
||||||
|
) -> Vec<usize>
|
||||||
|
where
|
||||||
|
F: Peaker,
|
||||||
|
{
|
||||||
|
while width != total_width {
|
||||||
|
let col = match peaker.peak(&[], &widths) {
|
||||||
|
Some(col) => col,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
widths[col] += 1;
|
||||||
|
width += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
widths
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_table_widths_with_total<R>(records: R, cfg: &GridConfig) -> (Vec<usize>, usize)
|
||||||
|
where
|
||||||
|
R: Records,
|
||||||
|
{
|
||||||
|
let mut evaluator = WidthEstimator::default();
|
||||||
|
evaluator.estimate(&records, cfg);
|
||||||
|
let total_width = get_table_total_width(&records, cfg, &evaluator);
|
||||||
|
let widths = evaluator.into();
|
||||||
|
|
||||||
|
(widths, total_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_table_total_width<W, R>(records: R, cfg: &GridConfig, ctrl: &W) -> usize
|
||||||
|
where
|
||||||
|
W: Estimate<R>,
|
||||||
|
R: Records,
|
||||||
|
{
|
||||||
|
ctrl.total()
|
||||||
|
+ cfg.count_vertical(records.count_columns())
|
||||||
|
+ cfg.get_margin().left.size
|
||||||
|
+ cfg.get_margin().right.size
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ mod complete;
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
mod exec;
|
mod exec;
|
||||||
mod explain;
|
mod explain;
|
||||||
|
mod inspect;
|
||||||
|
mod inspect_table;
|
||||||
mod nu_check;
|
mod nu_check;
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "android",
|
target_os = "android",
|
||||||
@ -21,6 +23,8 @@ pub use complete::Complete;
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub use exec::Exec;
|
pub use exec::Exec;
|
||||||
pub use explain::Explain;
|
pub use explain::Explain;
|
||||||
|
pub use inspect::Inspect;
|
||||||
|
pub use inspect_table::build_table;
|
||||||
pub use nu_check::NuCheck;
|
pub use nu_check::NuCheck;
|
||||||
#[cfg(any(
|
#[cfg(any(
|
||||||
target_os = "android",
|
target_os = "android",
|
||||||
|
Loading…
Reference in New Issue
Block a user