Merge branch 'main' of https://github.com/nushell/engine-q into unit-test

This commit is contained in:
Fernando Herrera
2021-10-09 14:17:07 +01:00
38 changed files with 2354 additions and 96 deletions

View File

@ -15,7 +15,7 @@ impl Command for Do {
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("do").required(
Signature::build("do").desc(self.usage()).required(
"block",
SyntaxShape::Block(Some(vec![])),
"the block to run",

View File

@ -1,10 +1,10 @@
use nu_protocol::{
ast::Call,
engine::{Command, EvaluationContext},
Example, ShellError, Signature, Spanned, SyntaxShape, Value,
span, Example, ShellError, Signature, Spanned, SyntaxShape, Value,
};
use nu_engine::CallExt;
use nu_engine::{get_full_help, CallExt};
pub struct Help;
@ -73,11 +73,11 @@ impl Command for Help {
}
fn help(context: &EvaluationContext, call: &Call) -> Result<Value, ShellError> {
let span = call.head;
let head = call.head;
let find: Option<Spanned<String>> = call.get_flag(context, "find")?;
let rest: Vec<Spanned<String>> = call.rest(context, 0)?;
let full_commands = context.get_commands_info();
let full_commands = context.get_signatures_with_examples();
if let Some(f) = find {
let search_string = f.item;
@ -87,29 +87,36 @@ fn help(context: &EvaluationContext, call: &Call) -> Result<Value, ShellError> {
let mut cols = vec![];
let mut vals = vec![];
let key = cmd.name.clone();
let c = cmd.usage.clone();
let e = cmd.extra_usage.clone();
let key = cmd.0.name.clone();
let c = cmd.0.usage.clone();
let e = cmd.0.extra_usage.clone();
if key.to_lowercase().contains(&search_string)
|| c.to_lowercase().contains(&search_string)
|| e.to_lowercase().contains(&search_string)
{
cols.push("name".into());
vals.push(Value::String { val: key, span });
vals.push(Value::String {
val: key,
span: head,
});
cols.push("usage".into());
vals.push(Value::String { val: c, span });
vals.push(Value::String { val: c, span: head });
cols.push("extra_usage".into());
vals.push(Value::String { val: e, span });
vals.push(Value::String { val: e, span: head });
found_cmds_vec.push(Value::Record { cols, vals, span });
found_cmds_vec.push(Value::Record {
cols,
vals,
span: head,
});
}
}
return Ok(Value::List {
vals: found_cmds_vec,
span,
span: head,
});
}
@ -121,25 +128,38 @@ fn help(context: &EvaluationContext, call: &Call) -> Result<Value, ShellError> {
let mut cols = vec![];
let mut vals = vec![];
let key = cmd.name.clone();
let c = cmd.usage.clone();
let e = cmd.extra_usage.clone();
let key = cmd.0.name.clone();
let c = cmd.0.usage.clone();
let e = cmd.0.extra_usage.clone();
cols.push("name".into());
vals.push(Value::String { val: key, span });
vals.push(Value::String {
val: key,
span: head,
});
cols.push("usage".into());
vals.push(Value::String { val: c, span });
vals.push(Value::String { val: c, span: head });
cols.push("extra_usage".into());
vals.push(Value::String { val: e, span });
vals.push(Value::String { val: e, span: head });
found_cmds_vec.push(Value::Record { cols, vals, span });
found_cmds_vec.push(Value::Record {
cols,
vals,
span: head,
});
}
Ok(Value::List {
vals: found_cmds_vec,
span: head,
})
} else {
let mut name = String::new();
let mut output = String::new();
for r in rest {
for r in &rest {
if !name.is_empty() {
name.push(' ');
}
@ -147,31 +167,24 @@ fn help(context: &EvaluationContext, call: &Call) -> Result<Value, ShellError> {
}
for cmd in full_commands {
let mut cols = vec![];
let mut vals = vec![];
let key = cmd.name.clone();
let c = cmd.usage.clone();
let e = cmd.extra_usage.clone();
if key.starts_with(&name) {
cols.push("name".into());
vals.push(Value::String { val: key, span });
cols.push("usage".into());
vals.push(Value::String { val: c, span });
cols.push("extra_usage".into());
vals.push(Value::String { val: e, span });
found_cmds_vec.push(Value::Record { cols, vals, span });
if cmd.0.name == name {
let help = get_full_help(&cmd.0, &cmd.1, context);
output.push_str(&help);
}
}
if !output.is_empty() {
Ok(Value::String {
val: output,
span: call.head,
})
} else {
Err(ShellError::CommandNotFound(span(&[
rest[0].span,
rest[rest.len() - 1].span,
])))
}
}
Ok(Value::List {
vals: found_cmds_vec,
span,
})
// FIXME: the fancy help stuff needs to be reimplemented
/*
@ -341,7 +354,7 @@ You can also learn more at https://www.nushell.sh/book/"#;
Ok(Value::String {
val: msg.into(),
span,
span: head,
})
}
}

View File

@ -27,6 +27,7 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
working_set.add_decl(Box::new(From));
working_set.add_decl(Box::new(FromJson));
working_set.add_decl(Box::new(Get));
working_set.add_decl(Box::new(Griddle));
working_set.add_decl(Box::new(Help));
working_set.add_decl(Box::new(Hide));
working_set.add_decl(Box::new(If));
@ -35,12 +36,18 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
working_set.add_decl(Box::new(LetEnv));
working_set.add_decl(Box::new(Lines));
working_set.add_decl(Box::new(Ls));
working_set.add_decl(Box::new(Mkdir));
working_set.add_decl(Box::new(Module));
working_set.add_decl(Box::new(Mv));
working_set.add_decl(Box::new(Ps));
working_set.add_decl(Box::new(Select));
working_set.add_decl(Box::new(Split));
working_set.add_decl(Box::new(SplitChars));
working_set.add_decl(Box::new(SplitColumn));
working_set.add_decl(Box::new(SplitRow));
working_set.add_decl(Box::new(Sys));
working_set.add_decl(Box::new(Table));
working_set.add_decl(Box::new(Touch));
working_set.add_decl(Box::new(Use));
working_set.add_decl(Box::new(Where));
working_set.add_decl(Box::new(Wrap));

View File

@ -60,7 +60,7 @@ impl Command for Cp {
}
let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir()));
let recursive = call.named.iter().any(|p| &p.0 == "recursive");
let recursive: bool = call.has_flag("recursive");
if any_source_is_dir && !recursive {
return Err(ShellError::MoveNotPossibleSingle(
"Directories must be copied using \"--recursive\"".to_string(),

View File

@ -0,0 +1,74 @@
use std::collections::VecDeque;
use std::env::current_dir;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EvaluationContext};
use nu_protocol::{ShellError, Signature, SyntaxShape, Value, ValueStream};
pub struct Mkdir;
impl Command for Mkdir {
fn name(&self) -> &str {
"mkdir"
}
fn signature(&self) -> Signature {
Signature::build("mkdir")
.rest(
"rest",
SyntaxShape::Filepath,
"the name(s) of the path(s) to create",
)
.switch("show-created-paths", "show the path(s) created.", Some('s'))
}
fn usage(&self) -> &str {
"Make directories, creates intermediary directories as required."
}
fn run(
&self,
context: &EvaluationContext,
call: &Call,
_input: Value,
) -> Result<Value, ShellError> {
let path = current_dir()?;
let mut directories = call
.rest::<String>(context, 0)?
.into_iter()
.map(|dir| path.join(dir))
.peekable();
let show_created_paths = call.has_flag("show-created-paths");
let mut stream: VecDeque<Value> = VecDeque::new();
if directories.peek().is_none() {
return Err(ShellError::MissingParameter(
"requires directory paths".to_string(),
call.head,
));
}
for (i, dir) in directories.enumerate() {
let span = call.positional[i].span;
let dir_res = std::fs::create_dir_all(&dir);
if let Err(reason) = dir_res {
return Err(ShellError::CreateNotPossible(
format!("failed to create directory: {}", reason),
call.positional[i].span,
));
}
if show_created_paths {
let val = format!("{:}", dir.to_string_lossy());
stream.push_back(Value::String { val, span });
}
}
let stream = ValueStream::from_stream(stream.into_iter());
let span = call.head;
Ok(Value::Stream { stream, span })
}
}

View File

@ -1,10 +1,14 @@
mod cd;
mod cp;
mod ls;
mod mkdir;
mod mv;
mod touch;
mod util;
pub use cd::Cd;
pub use cp::Cp;
pub use ls::Ls;
pub use mkdir::Mkdir;
pub use mv::Mv;
pub use touch::Touch;

View File

@ -0,0 +1,52 @@
use std::fs::OpenOptions;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EvaluationContext};
use nu_protocol::{ShellError, Signature, SyntaxShape, Value};
pub struct Touch;
impl Command for Touch {
fn name(&self) -> &str {
"touch"
}
fn signature(&self) -> Signature {
Signature::build("touch")
.required(
"filename",
SyntaxShape::Filepath,
"the path of the file you want to create",
)
.rest("rest", SyntaxShape::Filepath, "additional files to create")
}
fn usage(&self) -> &str {
"Creates one or more files."
}
fn run(
&self,
context: &EvaluationContext,
call: &Call,
_input: Value,
) -> Result<Value, ShellError> {
let target: String = call.req(context, 0)?;
let rest: Vec<String> = call.rest(context, 1)?;
for (index, item) in vec![target].into_iter().chain(rest).enumerate() {
match OpenOptions::new().write(true).create(true).open(&item) {
Ok(_) => continue,
Err(err) => {
return Err(ShellError::CreateNotPossible(
format!("Failed to create file: {}", err),
call.positional[index].span,
));
}
}
}
Ok(Value::Nothing { span: call.head })
}
}

View File

@ -1,3 +1,5 @@
mod build_string;
mod split;
pub use build_string::BuildString;
pub use split::*;

View File

@ -0,0 +1,100 @@
use nu_protocol::{
ast::Call,
engine::{Command, EvaluationContext},
Example, ShellError, Signature, Span, Type, Value,
};
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"split chars"
}
fn signature(&self) -> Signature {
Signature::build("split chars")
}
fn usage(&self) -> &str {
"splits a string's characters into separate rows"
}
fn run(
&self,
_context: &EvaluationContext,
call: &Call,
input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
split_chars(call, input)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Split the string's characters into separate rows",
example: "echo 'hello' | split chars",
result: Some(vec![
Value::String {
val: "h".into(),
span: Span::unknown(),
},
Value::String {
val: "e".into(),
span: Span::unknown(),
},
Value::String {
val: "l".into(),
span: Span::unknown(),
},
Value::String {
val: "l".into(),
span: Span::unknown(),
},
Value::String {
val: "o".into(),
span: Span::unknown(),
},
]),
}]
}
}
fn split_chars(call: &Call, input: Value) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
let span = call.head;
Ok(input.flat_map(span, move |x| split_chars_helper(&x, span)))
}
fn split_chars_helper(v: &Value, name: Span) -> Vec<Value> {
if let Ok(s) = v.as_string() {
let v_span = v.span();
s.chars()
.collect::<Vec<_>>()
.into_iter()
.map(move |x| Value::String {
val: x.to_string(),
span: v_span,
})
.collect()
} else {
vec![Value::Error {
error: ShellError::PipelineMismatch {
expected: Type::String,
expected_span: name,
origin: v.span(),
},
}]
}
}
// #[cfg(test)]
// mod tests {
// use super::ShellError;
// use super::SubCommand;
// #[test]
// fn examples_work_as_expected() -> Result<(), ShellError> {
// use crate::examples::test as test_examples;
// test_examples(SubCommand {})
// }
// }

View File

@ -0,0 +1,130 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EvaluationContext},
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"split column"
}
fn signature(&self) -> Signature {
Signature::build("split column")
.required(
"separator",
SyntaxShape::String,
"the character that denotes what separates columns",
)
.switch("collapse-empty", "remove empty columns", Some('c'))
.rest(
"rest",
SyntaxShape::String,
"column names to give the new columns",
)
}
fn usage(&self) -> &str {
"splits contents across multiple columns via the separator."
}
fn run(
&self,
context: &EvaluationContext,
call: &Call,
input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
split_column(context, call, input)
}
}
fn split_column(
context: &EvaluationContext,
call: &Call,
input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
let name_span = call.head;
let separator: Spanned<String> = call.req(context, 0)?;
let rest: Vec<Spanned<String>> = call.rest(context, 1)?;
let collapse_empty = call.has_flag("collapse-empty");
Ok(input.map(name_span, move |x| {
split_column_helper(&x, &separator, &rest, collapse_empty, name_span)
}))
}
fn split_column_helper(
v: &Value,
separator: &Spanned<String>,
rest: &[Spanned<String>],
collapse_empty: bool,
head: Span,
) -> Value {
if let Ok(s) = v.as_string() {
let splitter = separator.item.replace("\\n", "\n");
let split_result: Vec<_> = if collapse_empty {
s.split(&splitter).filter(|s| !s.is_empty()).collect()
} else {
s.split(&splitter).collect()
};
let positional: Vec<_> = rest.iter().map(|f| f.item.clone()).collect();
// If they didn't provide column names, make up our own
let mut cols = vec![];
let mut vals = vec![];
if positional.is_empty() {
let mut gen_columns = vec![];
for i in 0..split_result.len() {
gen_columns.push(format!("Column{}", i + 1));
}
for (&k, v) in split_result.iter().zip(&gen_columns) {
cols.push(v.to_string());
vals.push(Value::String {
val: k.into(),
span: head,
});
}
} else {
for (&k, v) in split_result.iter().zip(&positional) {
cols.push(v.into());
vals.push(Value::String {
val: k.into(),
span: head,
})
}
}
Value::Record {
cols,
vals,
span: head,
}
} else {
Value::Error {
error: ShellError::PipelineMismatch {
expected: Type::String,
expected_span: head,
origin: v.span(),
},
}
}
}
// #[cfg(test)]
// mod tests {
// use super::ShellError;
// use super::SubCommand;
// #[test]
// fn examples_work_as_expected() -> Result<(), ShellError> {
// use crate::examples::test as test_examples;
// test_examples(SubCommand {})
// }
// }

View File

@ -0,0 +1,48 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EvaluationContext},
Signature, Value,
};
#[derive(Clone)]
pub struct SplitCommand;
impl Command for SplitCommand {
fn name(&self) -> &str {
"split"
}
fn signature(&self) -> Signature {
Signature::build("split")
}
fn usage(&self) -> &str {
"Split contents across desired subcommand (like row, column) via the separator."
}
fn run(
&self,
context: &EvaluationContext,
call: &Call,
_input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(&SplitCommand.signature(), &SplitCommand.examples(), context),
span: call.head,
})
}
}
// #[cfg(test)]
// mod tests {
// use super::Command;
// use super::ShellError;
// #[test]
// fn examples_work_as_expected() -> Result<(), ShellError> {
// use crate::examples::test as test_examples;
// test_examples(Command {})
// }
// }

View File

@ -0,0 +1,9 @@
pub mod chars;
pub mod column;
pub mod command;
pub mod row;
pub use chars::SubCommand as SplitChars;
pub use column::SubCommand as SplitColumn;
pub use command::SplitCommand as Split;
pub use row::SubCommand as SplitRow;

View File

@ -0,0 +1,87 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EvaluationContext},
ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"split row"
}
fn signature(&self) -> Signature {
Signature::build("split row").required(
"separator",
SyntaxShape::String,
"the character that denotes what separates rows",
)
}
fn usage(&self) -> &str {
"splits contents over multiple rows via the separator."
}
fn run(
&self,
context: &EvaluationContext,
call: &Call,
input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
split_row(context, call, input)
}
}
fn split_row(
context: &EvaluationContext,
call: &Call,
input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
let name_span = call.head;
let separator: Spanned<String> = call.req(context, 0)?;
Ok(input.flat_map(name_span, move |x| {
split_row_helper(&x, &separator, name_span)
}))
}
fn split_row_helper(v: &Value, separator: &Spanned<String>, name: Span) -> Vec<Value> {
if let Ok(s) = v.as_string() {
let splitter = separator.item.replace("\\n", "\n");
s.split(&splitter)
.filter_map(|s| {
if s.trim() != "" {
Some(Value::String {
val: s.into(),
span: v.span(),
})
} else {
None
}
})
.collect()
} else {
vec![Value::Error {
error: ShellError::PipelineMismatch {
expected: Type::String,
expected_span: name,
origin: v.span(),
},
}]
}
}
// #[cfg(test)]
// mod tests {
// use super::ShellError;
// use super::SubCommand;
// #[test]
// fn examples_work_as_expected() -> Result<(), ShellError> {
// use crate::examples::test as test_examples;
// test_examples(SubCommand {})
// }
// }

View File

@ -0,0 +1,334 @@
use lscolors::{LsColors, Style};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, PathMember},
engine::{Command, EvaluationContext},
Signature, Span, SyntaxShape, Value,
};
use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions};
use terminal_size::{Height, Width};
pub struct Griddle;
impl Command for Griddle {
fn name(&self) -> &str {
"grid"
}
fn usage(&self) -> &str {
"Renders the output to a textual terminal grid."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("grid")
.named(
"width",
SyntaxShape::Int,
"number of columns wide",
Some('w'),
)
.switch("color", "draw output with color", Some('c'))
.named(
"separator",
SyntaxShape::String,
"character to separate grid with",
Some('s'),
)
}
fn extra_usage(&self) -> &str {
r#"grid was built to give a concise gridded layout for ls. however,
it determines what to put in the grid by looking for a column named
'name'. this works great for tables and records but for lists we
need to do something different. such as with '[one two three] | grid'
it creates a fake column called 'name' for these values so that it
prints out the list properly."#
}
fn run(
&self,
context: &EvaluationContext,
call: &Call,
input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
let width_param: Option<String> = call.get_flag(context, "width")?;
let color_param: bool = call.has_flag("color");
let seperator_param: Option<String> = call.get_flag(context, "separator")?;
match input {
Value::List { vals, .. } => {
// dbg!("value::list");
let data = convert_to_list2(vals);
if let Some(items) = data {
Ok(create_grid_output2(
items,
call,
width_param,
color_param,
seperator_param,
))
} else {
Ok(Value::Nothing { span: call.head })
}
}
Value::Stream { stream, .. } => {
// dbg!("value::stream");
let data = convert_to_list2(stream);
if let Some(items) = data {
Ok(create_grid_output2(
items,
call,
width_param,
color_param,
seperator_param,
))
} else {
// dbg!(data);
Ok(Value::Nothing { span: call.head })
}
}
Value::Record { cols, vals, .. } => {
// dbg!("value::record");
let mut items = vec![];
for (i, (c, v)) in cols.into_iter().zip(vals.into_iter()).enumerate() {
items.push((i, c, v.into_string()))
}
Ok(create_grid_output2(
items,
call,
width_param,
color_param,
seperator_param,
))
}
x => {
// dbg!("other value");
// dbg!(x.get_type());
Ok(x)
}
}
}
}
fn create_grid_output2(
items: Vec<(usize, String, String)>,
call: &Call,
width_param: Option<String>,
color_param: bool,
separator_param: Option<String>,
) -> Value {
let ls_colors = LsColors::from_env().unwrap_or_default();
let cols = if let Some(col) = width_param {
col.parse::<u16>().unwrap_or(80)
} else if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() {
w
} else {
80u16
};
let sep = if let Some(seperator) = separator_param {
seperator
} else {
"".to_string()
};
let mut grid = Grid::new(GridOptions {
direction: Direction::TopToBottom,
filling: Filling::Text(sep),
});
for (_row_index, header, value) in items {
// only output value if the header name is 'name'
if header == "name" {
if color_param {
let style = ls_colors.style_for_path(value.clone());
let ansi_style = style.map(Style::to_crossterm_style).unwrap_or_default();
let mut cell = Cell::from(ansi_style.apply(value).to_string());
cell.alignment = Alignment::Right;
grid.add(cell);
} else {
let mut cell = Cell::from(value);
cell.alignment = Alignment::Right;
grid.add(cell);
}
}
}
if let Some(grid_display) = grid.fit_into_width(cols as usize) {
Value::String {
val: grid_display.to_string(),
span: call.head,
}
} else {
Value::String {
val: format!("Couldn't fit grid into {} columns!", cols),
span: call.head,
}
}
}
// fn create_grid_output(
// items: Vec<Vec<String>>,
// call: &Call,
// columns_param: Option<String>,
// ) -> Value {
// let mut grid = Grid::new(GridOptions {
// direction: Direction::TopToBottom,
// filling: Filling::Text(" | ".into()),
// });
// for list in items {
// dbg!(&list);
// // looks like '&list = [ "0", "one",]'
// let a_string = (&list[1]).to_string();
// let mut cell = Cell::from(a_string);
// cell.alignment = Alignment::Right;
// grid.add(cell);
// }
// let cols = if let Some(col) = columns_param {
// col.parse::<u16>().unwrap_or(80)
// } else {
// // 80usize
// if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() {
// w
// } else {
// 80u16
// }
// };
// // eprintln!("columns size = {}", cols);
// if let Some(grid_display) = grid.fit_into_width(cols as usize) {
// // println!("{}", grid_display);
// Value::String {
// val: grid_display.to_string(),
// span: call.head,
// }
// } else {
// // println!("Couldn't fit grid into 80 columns!");
// Value::String {
// val: format!("Couldn't fit grid into {} columns!", cols),
// span: call.head,
// }
// }
// }
fn convert_to_list2(iter: impl IntoIterator<Item = Value>) -> Option<Vec<(usize, String, String)>> {
let mut iter = iter.into_iter().peekable();
if let Some(first) = iter.peek() {
let mut headers = first.columns();
if !headers.is_empty() {
headers.insert(0, "#".into());
}
let mut data = vec![];
for (row_num, item) in iter.enumerate() {
let mut row = vec![row_num.to_string()];
if headers.is_empty() {
row.push(item.into_string())
} else {
for header in headers.iter().skip(1) {
let result = match item {
Value::Record { .. } => {
item.clone().follow_cell_path(&[PathMember::String {
val: header.into(),
span: Span::unknown(),
}])
}
_ => Ok(item.clone()),
};
match result {
Ok(value) => row.push(value.into_string()),
Err(_) => row.push(String::new()),
}
}
}
data.push(row);
}
// TODO: later, let's color these string with LS_COLORS
// let h: Vec<String> = headers.into_iter().map(|x| x.trim().to_string()).collect();
// let d: Vec<Vec<String>> = data.into_iter().map(|x| x.into_iter().collect()).collect();
let mut h: Vec<String> = headers.into_iter().collect();
// let d: Vec<Vec<String>> = data.into_iter().collect();
// This is just a list
if h.is_empty() {
// let's fake the header
h.push("#".to_string());
h.push("name".to_string());
}
// this tuple is (row_index, header_name, value)
let mut interleaved = vec![];
for (i, v) in data.into_iter().enumerate() {
for (n, s) in v.into_iter().enumerate() {
if h.len() == 1 {
// always get the 1th element since this is a simple list
// and we hacked the header above because it was empty
// 0th element is an index, 1th element is the value
interleaved.push((i, h[1].clone(), s))
} else {
interleaved.push((i, h[n].clone(), s))
}
}
}
Some(interleaved)
} else {
None
}
}
// fn convert_to_list(iter: impl IntoIterator<Item = Value>) -> Option<Vec<Vec<String>>> {
// let mut iter = iter.into_iter().peekable();
// let mut data = vec![];
// if let Some(first) = iter.peek() {
// // dbg!(&first);
// let mut headers = first.columns();
// if !headers.is_empty() {
// headers.insert(0, "#".into());
// }
// for (row_num, item) in iter.enumerate() {
// let mut row = vec![row_num.to_string()];
// if headers.is_empty() {
// row.push(item.into_string())
// } else {
// for header in headers.iter().skip(1) {
// let result = match item {
// Value::Record { .. } => {
// item.clone().follow_cell_path(&[PathMember::String {
// val: header.into(),
// span: Span::unknown(),
// }])
// }
// _ => Ok(item.clone()),
// };
// match result {
// Ok(value) => row.push(value.into_string()),
// Err(_) => row.push(String::new()),
// }
// }
// }
// data.push(row);
// }
// Some(data)
// } else {
// None
// }
// }

View File

@ -1,3 +1,5 @@
mod griddle;
mod table;
pub use griddle::Griddle;
pub use table::Table;

View File

@ -1,9 +1,9 @@
use std::collections::HashMap;
use nu_protocol::ast::{Call, PathMember};
use nu_protocol::engine::{Command, EvaluationContext};
use nu_protocol::{Signature, Span, Value};
use nu_table::StyledString;
use std::collections::HashMap;
use terminal_size::{Height, Width};
pub struct Table;
@ -27,12 +27,18 @@ impl Command for Table {
call: &Call,
input: Value,
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
let term_width = if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() {
w as usize
} else {
80usize
};
match input {
Value::List { vals, .. } => {
let table = convert_to_table(vals);
if let Some(table) = table {
let result = nu_table::draw_table(&table, 80, &HashMap::new());
let result = nu_table::draw_table(&table, term_width, &HashMap::new());
Ok(Value::String {
val: result,
@ -46,7 +52,7 @@ impl Command for Table {
let table = convert_to_table(stream);
if let Some(table) = table {
let result = nu_table::draw_table(&table, 80, &HashMap::new());
let result = nu_table::draw_table(&table, term_width, &HashMap::new());
Ok(Value::String {
val: result,
@ -78,7 +84,7 @@ impl Command for Table {
theme: nu_table::Theme::rounded(),
};
let result = nu_table::draw_table(&table, 80, &HashMap::new());
let result = nu_table::draw_table(&table, term_width, &HashMap::new());
Ok(Value::String {
val: result,