mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 18:57:44 +02:00
relocate debug commands (#8071)
# Description Now that we've landed the debug commands we were working on, let's relocate them to an easier place to find all of them. That's what this PR does. The only actual code change was changing the `timeit` command to a `Category::Debug` command. The rest is just moving things around and hooking them up. # 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:
@ -1,330 +0,0 @@
|
||||
use nu_engine::{eval_expression, CallExt};
|
||||
use nu_protocol::ast::{Argument, Block, Call, Expr, Expression};
|
||||
use nu_protocol::engine::{Closure, Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Explain;
|
||||
|
||||
impl Command for Explain {
|
||||
fn name(&self) -> &str {
|
||||
"explain"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Explain closure contents."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("explain")
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
"the closure to run",
|
||||
)
|
||||
.input_output_types(vec![(Type::Any, Type::Any), (Type::Nothing, Type::Any)])
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Debug)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
// This was all delightfully stolen from benchmark :)
|
||||
let capture_block: Closure = call.req(engine_state, stack, 0)?;
|
||||
let block = engine_state.get_block(capture_block.block_id);
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
|
||||
let elements = get_pipeline_elements(engine_state, &mut stack, block)?;
|
||||
|
||||
Ok(elements.into_pipeline_data(ctrlc))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Explain a command within a closure",
|
||||
example: "explain { ls | sort-by name type -i | get name } | table -e",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_pipeline_elements(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block: &Block,
|
||||
) -> Result<Vec<Value>, ShellError> {
|
||||
let mut element_values = vec![];
|
||||
let span = Span::test_data();
|
||||
|
||||
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
|
||||
let mut i = 0;
|
||||
while i < pipeline.elements.len() {
|
||||
let pipeline_element = &pipeline.elements[i];
|
||||
let pipeline_expression = pipeline_element.expression().clone();
|
||||
let pipeline_span = &pipeline_element.span();
|
||||
let element_str =
|
||||
String::from_utf8_lossy(engine_state.get_span_contents(pipeline_span));
|
||||
let value = Value::string(element_str.to_string(), *pipeline_span);
|
||||
let expr = pipeline_expression.expr.clone();
|
||||
let (command_name, command_args_value) = if let Expr::Call(call) = expr {
|
||||
let command = engine_state.get_decl(call.decl_id);
|
||||
(
|
||||
command.name().to_string(),
|
||||
get_arguments(engine_state, stack, *call),
|
||||
)
|
||||
} else {
|
||||
("no-op".to_string(), vec![])
|
||||
};
|
||||
let index = format!("{pipeline_idx}_{i}");
|
||||
let value_type = value.get_type();
|
||||
let value_span = value.span()?;
|
||||
let value_span_start = value_span.start as i64;
|
||||
let value_span_end = value_span.end as i64;
|
||||
let command_name = command_name;
|
||||
|
||||
let rec = Value::Record {
|
||||
cols: vec![
|
||||
"cmd_index".to_string(),
|
||||
"cmd_name".to_string(),
|
||||
"type".to_string(),
|
||||
"cmd_args".to_string(),
|
||||
"span_start".to_string(),
|
||||
"span_end".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::string(index, span),
|
||||
Value::string(command_name, value_span),
|
||||
Value::string(value_type.to_string(), span),
|
||||
Value::List {
|
||||
vals: command_args_value,
|
||||
span: value_span,
|
||||
},
|
||||
Value::int(value_span_start, span),
|
||||
Value::int(value_span_end, span),
|
||||
],
|
||||
span: value_span,
|
||||
};
|
||||
element_values.push(rec);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
Ok(element_values)
|
||||
}
|
||||
|
||||
fn get_arguments(engine_state: &EngineState, stack: &mut Stack, call: Call) -> Vec<Value> {
|
||||
let mut arg_value = vec![];
|
||||
let span = Span::test_data();
|
||||
for arg in &call.arguments {
|
||||
match arg {
|
||||
// I think the second argument to Argument::Named is the short name, but I'm not really sure.
|
||||
// Please fix it if it's wrong. :)
|
||||
Argument::Named((name, short, opt_expr)) => {
|
||||
let arg_type = "named";
|
||||
let arg_value_name = name.item.clone();
|
||||
let arg_value_name_span_start = name.span.start as i64;
|
||||
let arg_value_name_span_end = name.span.end as i64;
|
||||
|
||||
let rec = Value::Record {
|
||||
cols: vec![
|
||||
"arg_type".to_string(),
|
||||
"name".to_string(),
|
||||
"type".to_string(),
|
||||
"span_start".to_string(),
|
||||
"span_end".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::string(arg_type, span),
|
||||
Value::string(arg_value_name, name.span),
|
||||
Value::string("string".to_string(), span),
|
||||
Value::int(arg_value_name_span_start, span),
|
||||
Value::int(arg_value_name_span_end, span),
|
||||
],
|
||||
span: name.span,
|
||||
};
|
||||
arg_value.push(rec);
|
||||
|
||||
if let Some(shortcut) = short {
|
||||
let arg_type = "short";
|
||||
let arg_value_name = shortcut.item.clone();
|
||||
let arg_value_name_span_start = shortcut.span.start as i64;
|
||||
let arg_value_name_span_end = shortcut.span.end as i64;
|
||||
|
||||
let rec = Value::Record {
|
||||
cols: vec![
|
||||
"arg_type".to_string(),
|
||||
"name".to_string(),
|
||||
"type".to_string(),
|
||||
"span_start".to_string(),
|
||||
"span_end".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::string(arg_type, span),
|
||||
Value::string(arg_value_name, shortcut.span),
|
||||
Value::string("string".to_string(), span),
|
||||
Value::int(arg_value_name_span_start, span),
|
||||
Value::int(arg_value_name_span_end, span),
|
||||
],
|
||||
span: name.span,
|
||||
};
|
||||
arg_value.push(rec);
|
||||
} else {
|
||||
};
|
||||
|
||||
if let Some(expression) = opt_expr {
|
||||
let evaluated_expression =
|
||||
get_expression_as_value(engine_state, stack, expression);
|
||||
let arg_type = "expr";
|
||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||
let evaled_span = evaluated_expression.expect_span();
|
||||
let arg_value_name_span_start = evaled_span.start as i64;
|
||||
let arg_value_name_span_end = evaled_span.end as i64;
|
||||
|
||||
let rec = Value::Record {
|
||||
cols: vec![
|
||||
"arg_type".to_string(),
|
||||
"name".to_string(),
|
||||
"type".to_string(),
|
||||
"span_start".to_string(),
|
||||
"span_end".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::string(arg_type, span),
|
||||
Value::string(arg_value_name, expression.span),
|
||||
Value::string(arg_value_type, span),
|
||||
Value::int(arg_value_name_span_start, span),
|
||||
Value::int(arg_value_name_span_end, span),
|
||||
],
|
||||
span: expression.span,
|
||||
};
|
||||
arg_value.push(rec);
|
||||
} else {
|
||||
};
|
||||
}
|
||||
Argument::Positional(inner_expr) => {
|
||||
let arg_type = "positional";
|
||||
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
|
||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||
let evaled_span = evaluated_expression.expect_span();
|
||||
let arg_value_name_span_start = evaled_span.start as i64;
|
||||
let arg_value_name_span_end = evaled_span.end as i64;
|
||||
|
||||
let rec = Value::Record {
|
||||
cols: vec![
|
||||
"arg_type".to_string(),
|
||||
"name".to_string(),
|
||||
"type".to_string(),
|
||||
"span_start".to_string(),
|
||||
"span_end".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::string(arg_type, span),
|
||||
Value::string(arg_value_name, inner_expr.span),
|
||||
Value::string(arg_value_type, span),
|
||||
Value::int(arg_value_name_span_start, span),
|
||||
Value::int(arg_value_name_span_end, span),
|
||||
],
|
||||
span: inner_expr.span,
|
||||
};
|
||||
arg_value.push(rec);
|
||||
}
|
||||
Argument::Unknown(inner_expr) => {
|
||||
let arg_type = "unknown";
|
||||
let evaluated_expression = get_expression_as_value(engine_state, stack, inner_expr);
|
||||
let arg_value_name = debug_string_without_formatting(&evaluated_expression);
|
||||
let arg_value_type = &evaluated_expression.get_type().to_string();
|
||||
let evaled_span = evaluated_expression.expect_span();
|
||||
let arg_value_name_span_start = evaled_span.start as i64;
|
||||
let arg_value_name_span_end = evaled_span.end as i64;
|
||||
|
||||
let rec = Value::Record {
|
||||
cols: vec![
|
||||
"arg_type".to_string(),
|
||||
"name".to_string(),
|
||||
"type".to_string(),
|
||||
"span_start".to_string(),
|
||||
"span_end".to_string(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::string(arg_type, span),
|
||||
Value::string(arg_value_name, inner_expr.span),
|
||||
Value::string(arg_value_type, span),
|
||||
Value::int(arg_value_name_span_start, span),
|
||||
Value::int(arg_value_name_span_end, span),
|
||||
],
|
||||
span: inner_expr.span,
|
||||
};
|
||||
arg_value.push(rec);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
arg_value
|
||||
}
|
||||
|
||||
fn get_expression_as_value(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
inner_expr: &Expression,
|
||||
) -> Value {
|
||||
match eval_expression(engine_state, stack, inner_expr) {
|
||||
Ok(v) => v,
|
||||
Err(error) => Value::Error { error },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn debug_string_without_formatting(value: &Value) -> String {
|
||||
match value {
|
||||
Value::Bool { val, .. } => val.to_string(),
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
Value::Float { val, .. } => val.to_string(),
|
||||
Value::Filesize { val, .. } => val.to_string(),
|
||||
Value::Duration { val, .. } => val.to_string(),
|
||||
Value::Date { val, .. } => format!("{val:?}"),
|
||||
Value::Range { val, .. } => {
|
||||
format!(
|
||||
"{}..{}",
|
||||
debug_string_without_formatting(&val.from),
|
||||
debug_string_without_formatting(&val.to)
|
||||
)
|
||||
}
|
||||
Value::String { val, .. } => val.clone(),
|
||||
Value::List { vals: val, .. } => format!(
|
||||
"[{}]",
|
||||
val.iter()
|
||||
.map(debug_string_without_formatting)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
Value::Record { cols, vals, .. } => format!(
|
||||
"{{{}}}",
|
||||
cols.iter()
|
||||
.zip(vals.iter())
|
||||
.map(|(x, y)| format!("{}: {}", x, debug_string_without_formatting(y)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
),
|
||||
Value::LazyRecord { val, .. } => match val.collect() {
|
||||
Ok(val) => debug_string_without_formatting(&val),
|
||||
Err(error) => format!("{error:?}"),
|
||||
},
|
||||
//TODO: It would be good to drill in deeper to blocks and closures.
|
||||
Value::Block { val, .. } => format!("<Block {val}>"),
|
||||
Value::Closure { val, .. } => format!("<Closure {val}>"),
|
||||
Value::Nothing { .. } => String::new(),
|
||||
Value::Error { error } => format!("{error:?}"),
|
||||
Value::Binary { val, .. } => format!("{val:?}"),
|
||||
Value::CellPath { val, .. } => val.into_string(),
|
||||
Value::CustomValue { val, .. } => val.value_string(),
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
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,
|
||||
}]
|
||||
}
|
||||
}
|
@ -1,465 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
mod complete;
|
||||
#[cfg(unix)]
|
||||
mod exec;
|
||||
mod explain;
|
||||
mod inspect;
|
||||
mod inspect_table;
|
||||
mod nu_check;
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
@ -16,15 +13,11 @@ mod ps;
|
||||
mod registry_query;
|
||||
mod run_external;
|
||||
mod sys;
|
||||
mod timeit;
|
||||
mod which_;
|
||||
|
||||
pub use complete::Complete;
|
||||
#[cfg(unix)]
|
||||
pub use exec::Exec;
|
||||
pub use explain::Explain;
|
||||
pub use inspect::Inspect;
|
||||
pub use inspect_table::build_table;
|
||||
pub use nu_check::NuCheck;
|
||||
#[cfg(any(
|
||||
target_os = "android",
|
||||
@ -37,5 +30,4 @@ pub use ps::Ps;
|
||||
pub use registry_query::RegistryQuery;
|
||||
pub use run_external::{External, ExternalCommand};
|
||||
pub use sys::Sys;
|
||||
pub use timeit::TimeIt;
|
||||
pub use which_::Which;
|
||||
|
@ -1,135 +0,0 @@
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Closure, Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
use std::time::Instant;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TimeIt;
|
||||
|
||||
impl Command for TimeIt {
|
||||
fn name(&self) -> &str {
|
||||
"timeit"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Time the running time of a closure"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("timeit")
|
||||
.required(
|
||||
"closure",
|
||||
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
|
||||
"the closure to run",
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(Type::Any, Type::Duration),
|
||||
(Type::Nothing, Type::Duration),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::System)
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["timing", "timer", "benchmark", "measure"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let capture_block: Closure = call.req(engine_state, stack, 0)?;
|
||||
let block = engine_state.get_block(capture_block.block_id);
|
||||
|
||||
let redirect_stdout = call.redirect_stdout;
|
||||
let redirect_stderr = call.redirect_stderr;
|
||||
|
||||
let mut stack = stack.captures_to_stack(&capture_block.captures);
|
||||
|
||||
// In order to provide the pipeline as a positional, it must be converted into a value.
|
||||
// But because pipelines do not have Clone, this one has to be cloned as a value
|
||||
// and then converted back into a pipeline for eval_block().
|
||||
// So, the metadata must be saved here and restored at that point.
|
||||
let input_metadata = input.metadata();
|
||||
let input_val = input.into_value(call.head);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, input_val.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Get the start time after all other computation has been done.
|
||||
let start_time = Instant::now();
|
||||
eval_block(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
block,
|
||||
input_val.into_pipeline_data_with_metadata(input_metadata),
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)?
|
||||
.into_value(call.head);
|
||||
|
||||
let end_time = Instant::now();
|
||||
|
||||
let output = Value::Duration {
|
||||
val: (end_time - start_time).as_nanos() as i64,
|
||||
span: call.head,
|
||||
};
|
||||
|
||||
Ok(output.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Times a command within a closure",
|
||||
example: "timeit { sleep 500ms }",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Times a command using an existing input",
|
||||
example: "http get https://www.nushell.sh/book/ | timeit { split chars }",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
// Due to difficulty in observing side-effects from time closures,
|
||||
// checks that the closures have run correctly must use the filesystem.
|
||||
fn test_time_closure() {
|
||||
use nu_test_support::{nu, nu_repl_code, playground::Playground};
|
||||
Playground::setup("test_time_closure", |dirs, _| {
|
||||
let inp = [
|
||||
r#"[2 3 4] | timeit { to nuon | save foo.txt }"#,
|
||||
"open foo.txt",
|
||||
];
|
||||
let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
|
||||
assert_eq!(actual_repl.err, "");
|
||||
assert_eq!(actual_repl.out, "[2, 3, 4]");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_closure_2() {
|
||||
use nu_test_support::{nu, nu_repl_code, playground::Playground};
|
||||
Playground::setup("test_time_closure", |dirs, _| {
|
||||
let inp = [
|
||||
r#"[2 3 4] | timeit {|e| {result: $e} | to nuon | save foo.txt }"#,
|
||||
"open foo.txt",
|
||||
];
|
||||
let actual_repl = nu!(cwd: dirs.test(), nu_repl_code(&inp));
|
||||
assert_eq!(actual_repl.err, "");
|
||||
assert_eq!(actual_repl.out, "{result: [2, 3, 4]}");
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user