forked from extern/nushell
Merge pull request #96 from fdncred/ls_grid_output
output `ls` as a grid vs table
This commit is contained in:
@ -11,9 +11,11 @@ nu-json = { path = "../nu-json" }
|
||||
nu-path = { path = "../nu-path" }
|
||||
nu-protocol = { path = "../nu-protocol" }
|
||||
nu-table = { path = "../nu-table" }
|
||||
nu-term-grid = { path = "../nu-term-grid" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
glob = "0.3.0"
|
||||
thiserror = "1.0.29"
|
||||
sysinfo = "0.20.4"
|
||||
chrono = { version="0.4.19", features=["serde"] }
|
||||
terminal_size = "0.1.17"
|
||||
|
@ -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));
|
||||
|
289
crates/nu-command/src/viewers/griddle.rs
Normal file
289
crates/nu-command/src/viewers/griddle.rs
Normal file
@ -0,0 +1,289 @@
|
||||
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(
|
||||
"columns",
|
||||
SyntaxShape::Int,
|
||||
"number of columns wide",
|
||||
Some('c'),
|
||||
)
|
||||
}
|
||||
|
||||
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 columns_param: Option<String> = call.get_flag(context, "columns")?;
|
||||
|
||||
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, columns_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, columns_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, columns_param))
|
||||
}
|
||||
x => {
|
||||
// dbg!("other value");
|
||||
// dbg!(x.get_type());
|
||||
Ok(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_grid_output2(
|
||||
items: Vec<(usize, String, String)>,
|
||||
call: &Call,
|
||||
columns_param: Option<String>,
|
||||
) -> Value {
|
||||
let mut grid = Grid::new(GridOptions {
|
||||
direction: Direction::TopToBottom,
|
||||
filling: Filling::Text(" | ".into()),
|
||||
});
|
||||
|
||||
for (_row_index, header, value) in items {
|
||||
// only output value if the header name is 'name'
|
||||
if header == "name" {
|
||||
let mut cell = Cell::from(value);
|
||||
cell.alignment = Alignment::Right;
|
||||
grid.add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
let cols = if let Some(col) = columns_param {
|
||||
col.parse::<u16>().unwrap_or(80)
|
||||
} else if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() {
|
||||
w
|
||||
} else {
|
||||
80u16
|
||||
};
|
||||
|
||||
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
|
||||
// }
|
||||
// }
|
@ -1,3 +1,5 @@
|
||||
mod griddle;
|
||||
mod table;
|
||||
|
||||
pub use griddle::Griddle;
|
||||
pub use table::Table;
|
||||
|
@ -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,
|
||||
|
Reference in New Issue
Block a user