forked from extern/nushell
Merge branch 'main' of https://github.com/nushell/engine-q into parse-error
This commit is contained in:
commit
2ea19aeac0
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "0.7.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@ -148,6 +157,7 @@ dependencies = [
|
|||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
|
"nu-table",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"reedline",
|
"reedline",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@ -164,6 +174,12 @@ dependencies = [
|
|||||||
"wasi",
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
@ -276,8 +292,10 @@ dependencies = [
|
|||||||
name = "nu-command"
|
name = "nu-command"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"glob",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
|
"nu-table",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -303,6 +321,15 @@ dependencies = [
|
|||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-table"
|
||||||
|
version = "0.36.0"
|
||||||
|
dependencies = [
|
||||||
|
"nu-ansi-term",
|
||||||
|
"regex",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-integer"
|
name = "num-integer"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
@ -487,12 +514,29 @@ dependencies = [
|
|||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
@ -16,6 +16,7 @@ nu-command = { path="./crates/nu-command" }
|
|||||||
nu-engine = { path="./crates/nu-engine" }
|
nu-engine = { path="./crates/nu-engine" }
|
||||||
nu-parser = { path="./crates/nu-parser" }
|
nu-parser = { path="./crates/nu-parser" }
|
||||||
nu-protocol = { path = "./crates/nu-protocol" }
|
nu-protocol = { path = "./crates/nu-protocol" }
|
||||||
|
nu-table = { path = "./crates/nu-table" }
|
||||||
|
|
||||||
# mimalloc = { version = "*", default-features = false }
|
# mimalloc = { version = "*", default-features = false }
|
||||||
|
|
||||||
|
3
TODO.md
3
TODO.md
@ -17,7 +17,8 @@
|
|||||||
- [x] Column path
|
- [x] Column path
|
||||||
- [x] ...rest without calling it rest
|
- [x] ...rest without calling it rest
|
||||||
- [x] Iteration (`each`) over tables
|
- [x] Iteration (`each`) over tables
|
||||||
- [ ] Row conditions
|
- [x] Row conditions
|
||||||
|
- [x] Simple completions
|
||||||
- [ ] Value serialization
|
- [ ] Value serialization
|
||||||
- [ ] Handling rows with missing columns during a cell path
|
- [ ] Handling rows with missing columns during a cell path
|
||||||
- [ ] Error shortcircuit (stopping on first error)
|
- [ ] Error shortcircuit (stopping on first error)
|
||||||
|
54
crates/nu-cli/src/completions.rs
Normal file
54
crates/nu-cli/src/completions.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use nu_parser::{flatten_block, parse};
|
||||||
|
use nu_protocol::engine::{EngineState, StateWorkingSet};
|
||||||
|
use reedline::Completer;
|
||||||
|
|
||||||
|
pub struct NuCompleter {
|
||||||
|
engine_state: Rc<RefCell<EngineState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NuCompleter {
|
||||||
|
pub fn new(engine_state: Rc<RefCell<EngineState>>) -> Self {
|
||||||
|
Self { engine_state }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Completer for NuCompleter {
|
||||||
|
fn complete(&self, line: &str, pos: usize) -> Vec<(reedline::Span, String)> {
|
||||||
|
let engine_state = self.engine_state.borrow();
|
||||||
|
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||||
|
let offset = working_set.next_span_start();
|
||||||
|
let pos = offset + pos;
|
||||||
|
let (output, _err) = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
|
||||||
|
|
||||||
|
let flattened = flatten_block(&working_set, &output);
|
||||||
|
|
||||||
|
for flat in flattened {
|
||||||
|
if pos >= flat.0.start && pos <= flat.0.end {
|
||||||
|
match flat.1 {
|
||||||
|
nu_parser::FlatShape::External | nu_parser::FlatShape::InternalCall => {
|
||||||
|
let prefix = working_set.get_span_contents(flat.0);
|
||||||
|
let results = working_set.find_commands_by_prefix(prefix);
|
||||||
|
|
||||||
|
return results
|
||||||
|
.into_iter()
|
||||||
|
.map(move |x| {
|
||||||
|
(
|
||||||
|
reedline::Span {
|
||||||
|
start: flat.0.start - offset,
|
||||||
|
end: flat.0.end - offset,
|
||||||
|
},
|
||||||
|
String::from_utf8_lossy(&x).to_string(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
|
mod completions;
|
||||||
mod errors;
|
mod errors;
|
||||||
mod syntax_highlight;
|
mod syntax_highlight;
|
||||||
|
|
||||||
|
pub use completions::NuCompleter;
|
||||||
pub use errors::{report_parsing_error, report_shell_error};
|
pub use errors::{report_parsing_error, report_shell_error};
|
||||||
pub use syntax_highlight::NuHighlighter;
|
pub use syntax_highlight::NuHighlighter;
|
||||||
|
@ -8,3 +8,7 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
nu-protocol = { path = "../nu-protocol" }
|
nu-protocol = { path = "../nu-protocol" }
|
||||||
nu-engine = { path = "../nu-engine" }
|
nu-engine = { path = "../nu-engine" }
|
||||||
|
nu-table = { path = "../nu-table" }
|
||||||
|
|
||||||
|
# Potential dependencies for extras
|
||||||
|
glob = "0.3.0"
|
@ -5,7 +5,10 @@ use nu_protocol::{
|
|||||||
Signature, SyntaxShape,
|
Signature, SyntaxShape,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv};
|
use crate::{
|
||||||
|
where_::Where, Alias, Benchmark, BuildString, Def, Do, Each, For, If, Length, Let, LetEnv, Ls,
|
||||||
|
Table,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||||
let engine_state = Rc::new(RefCell::new(EngineState::new()));
|
let engine_state = Rc::new(RefCell::new(EngineState::new()));
|
||||||
@ -33,12 +36,18 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
|||||||
|
|
||||||
working_set.add_decl(Box::new(Each));
|
working_set.add_decl(Box::new(Each));
|
||||||
|
|
||||||
|
working_set.add_decl(Box::new(Where));
|
||||||
|
|
||||||
working_set.add_decl(Box::new(Do));
|
working_set.add_decl(Box::new(Do));
|
||||||
|
|
||||||
working_set.add_decl(Box::new(Benchmark));
|
working_set.add_decl(Box::new(Benchmark));
|
||||||
|
|
||||||
working_set.add_decl(Box::new(Length));
|
working_set.add_decl(Box::new(Length));
|
||||||
|
|
||||||
|
working_set.add_decl(Box::new(Ls));
|
||||||
|
|
||||||
|
working_set.add_decl(Box::new(Table));
|
||||||
|
|
||||||
let sig = Signature::build("exit");
|
let sig = Signature::build("exit");
|
||||||
working_set.add_decl(sig.predeclare());
|
working_set.add_decl(sig.predeclare());
|
||||||
let sig = Signature::build("vars");
|
let sig = Signature::build("vars");
|
||||||
|
@ -11,7 +11,7 @@ impl Command for If {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn usage(&self) -> &str {
|
fn usage(&self) -> &str {
|
||||||
"Create a variable and give it a value."
|
"Conditionally run a block."
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
@ -10,6 +10,9 @@ mod if_;
|
|||||||
mod length;
|
mod length;
|
||||||
mod let_;
|
mod let_;
|
||||||
mod let_env;
|
mod let_env;
|
||||||
|
mod ls;
|
||||||
|
mod table;
|
||||||
|
mod where_;
|
||||||
|
|
||||||
pub use alias::Alias;
|
pub use alias::Alias;
|
||||||
pub use benchmark::Benchmark;
|
pub use benchmark::Benchmark;
|
||||||
@ -23,3 +26,5 @@ pub use if_::If;
|
|||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
pub use let_::Let;
|
pub use let_::Let;
|
||||||
pub use let_env::LetEnv;
|
pub use let_env::LetEnv;
|
||||||
|
pub use ls::Ls;
|
||||||
|
pub use table::Table;
|
||||||
|
93
crates/nu-command/src/ls.rs
Normal file
93
crates/nu-command/src/ls.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
use nu_engine::eval_expression;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Ls;
|
||||||
|
|
||||||
|
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
||||||
|
impl Command for Ls {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ls"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"List the files in a directory."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("ls").optional(
|
||||||
|
"pattern",
|
||||||
|
SyntaxShape::GlobPattern,
|
||||||
|
"the glob pattern to use",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
_input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
let pattern = if let Some(expr) = call.positional.get(0) {
|
||||||
|
let result = eval_expression(context, expr)?;
|
||||||
|
result.as_string()?
|
||||||
|
} else {
|
||||||
|
"*".into()
|
||||||
|
};
|
||||||
|
|
||||||
|
let call_span = call.head;
|
||||||
|
let glob = glob::glob(&pattern).unwrap();
|
||||||
|
|
||||||
|
Ok(Value::Stream {
|
||||||
|
stream: glob
|
||||||
|
.into_iter()
|
||||||
|
.map(move |x| match x {
|
||||||
|
Ok(path) => match std::fs::symlink_metadata(&path) {
|
||||||
|
Ok(metadata) => {
|
||||||
|
let is_file = metadata.is_file();
|
||||||
|
let is_dir = metadata.is_dir();
|
||||||
|
let filesize = metadata.len();
|
||||||
|
|
||||||
|
Value::Record {
|
||||||
|
cols: vec!["name".into(), "type".into(), "size".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: path.to_string_lossy().to_string(),
|
||||||
|
span: call_span,
|
||||||
|
},
|
||||||
|
if is_file {
|
||||||
|
Value::string("file", call_span)
|
||||||
|
} else if is_dir {
|
||||||
|
Value::string("dir", call_span)
|
||||||
|
} else {
|
||||||
|
Value::Nothing { span: call_span }
|
||||||
|
},
|
||||||
|
Value::Int {
|
||||||
|
val: filesize as i64,
|
||||||
|
span: call_span,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
span: call_span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Value::Record {
|
||||||
|
cols: vec!["name".into(), "type".into(), "size".into()],
|
||||||
|
vals: vec![
|
||||||
|
Value::String {
|
||||||
|
val: path.to_string_lossy().to_string(),
|
||||||
|
span: call_span,
|
||||||
|
},
|
||||||
|
Value::Nothing { span: call_span },
|
||||||
|
Value::Nothing { span: call_span },
|
||||||
|
],
|
||||||
|
span: call_span,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_ => Value::Nothing { span: call_span },
|
||||||
|
})
|
||||||
|
.into_value_stream(),
|
||||||
|
span: call_span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
125
crates/nu-command/src/table.rs
Normal file
125
crates/nu-command/src/table.rs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
pub struct Table;
|
||||||
|
|
||||||
|
//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one.
|
||||||
|
impl Command for Table {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"table"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Render the table."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("table")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
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());
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: result,
|
||||||
|
span: call.head,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Value::Stream { stream, .. } => {
|
||||||
|
let table = convert_to_table(stream);
|
||||||
|
|
||||||
|
if let Some(table) = table {
|
||||||
|
let result = nu_table::draw_table(&table, 80, &HashMap::new());
|
||||||
|
|
||||||
|
Ok(Value::String {
|
||||||
|
val: result,
|
||||||
|
span: call.head,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x => Ok(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_to_table(iter: impl IntoIterator<Item = Value>) -> Option<nu_table::Table> {
|
||||||
|
let mut iter = iter.into_iter().peekable();
|
||||||
|
|
||||||
|
if let Some(first) = iter.peek() {
|
||||||
|
let mut headers = first.columns();
|
||||||
|
headers.insert(0, "#".into());
|
||||||
|
|
||||||
|
let mut data = vec![];
|
||||||
|
|
||||||
|
for (row_num, item) in iter.enumerate() {
|
||||||
|
let mut row = vec![row_num.to_string()];
|
||||||
|
|
||||||
|
for header in headers.iter().skip(1) {
|
||||||
|
let result = item.clone().follow_cell_path(&[PathMember::String {
|
||||||
|
val: header.into(),
|
||||||
|
span: Span::unknown(),
|
||||||
|
}]);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(value) => row.push(value.into_string()),
|
||||||
|
Err(_) => row.push(String::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(nu_table::Table {
|
||||||
|
headers: headers
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| StyledString {
|
||||||
|
contents: x,
|
||||||
|
style: nu_table::TextStyle::default_header(),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
data: data
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| {
|
||||||
|
x.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(col, y)| {
|
||||||
|
if col == 0 {
|
||||||
|
StyledString {
|
||||||
|
contents: y,
|
||||||
|
style: nu_table::TextStyle::default_header(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StyledString {
|
||||||
|
contents: y,
|
||||||
|
style: nu_table::TextStyle::basic_left(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<StyledString>>()
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
theme: nu_table::Theme::rounded(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
92
crates/nu-command/src/where_.rs
Normal file
92
crates/nu-command/src/where_.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use nu_engine::eval_expression;
|
||||||
|
use nu_protocol::ast::{Call, Expr, Expression};
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{IntoValueStream, ShellError, Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Where;
|
||||||
|
|
||||||
|
impl Command for Where {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"where"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Filter values based on a condition."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("where").required("cond", SyntaxShape::RowCondition, "condition")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
let cond = call.positional[0].clone();
|
||||||
|
|
||||||
|
let context = context.enter_scope();
|
||||||
|
|
||||||
|
let (var_id, cond) = match cond {
|
||||||
|
Expression {
|
||||||
|
expr: Expr::RowCondition(var_id, expr),
|
||||||
|
..
|
||||||
|
} => (var_id, expr),
|
||||||
|
_ => return Err(ShellError::InternalError("Expected row condition".into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
match input {
|
||||||
|
Value::Stream { stream, span } => {
|
||||||
|
let output_stream = stream
|
||||||
|
.filter(move |value| {
|
||||||
|
context.add_var(var_id, value.clone());
|
||||||
|
|
||||||
|
let result = eval_expression(&context, &cond);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(result) => result.is_true(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_value_stream();
|
||||||
|
|
||||||
|
Ok(Value::Stream {
|
||||||
|
stream: output_stream,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Value::List { vals, span } => {
|
||||||
|
let output_stream = vals
|
||||||
|
.into_iter()
|
||||||
|
.filter(move |value| {
|
||||||
|
context.add_var(var_id, value.clone());
|
||||||
|
|
||||||
|
let result = eval_expression(&context, &cond);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(result) => result.is_true(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_value_stream();
|
||||||
|
|
||||||
|
Ok(Value::Stream {
|
||||||
|
stream: output_stream,
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
x => {
|
||||||
|
context.add_var(var_id, x.clone());
|
||||||
|
|
||||||
|
let result = eval_expression(&context, &cond)?;
|
||||||
|
|
||||||
|
if result.is_true() {
|
||||||
|
Ok(x)
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -135,6 +135,7 @@ pub fn eval_expression(
|
|||||||
|
|
||||||
value.follow_cell_path(&column_path.tail)
|
value.follow_cell_path(&column_path.tail)
|
||||||
}
|
}
|
||||||
|
Expr::RowCondition(_, expr) => eval_expression(context, expr),
|
||||||
Expr::Call(call) => eval_call(context, call, Value::nothing()),
|
Expr::Call(call) => eval_call(context, call, Value::nothing()),
|
||||||
Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)),
|
Expr::ExternalCall(_, _) => Err(ShellError::ExternalNotSupported(expr.span)),
|
||||||
Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }),
|
Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }),
|
||||||
|
@ -114,6 +114,7 @@ pub fn flatten_expression(
|
|||||||
Expr::String(_) => {
|
Expr::String(_) => {
|
||||||
vec![(expr.span, FlatShape::String)]
|
vec![(expr.span, FlatShape::String)]
|
||||||
}
|
}
|
||||||
|
Expr::RowCondition(_, expr) => flatten_expression(working_set, expr),
|
||||||
Expr::Subexpression(block_id) => {
|
Expr::Subexpression(block_id) => {
|
||||||
flatten_block(working_set, working_set.get_block(*block_id))
|
flatten_block(working_set, working_set.get_block(*block_id))
|
||||||
}
|
}
|
||||||
|
@ -871,7 +871,7 @@ pub(crate) fn parse_dollar_expr(
|
|||||||
} else if let (expr, None) = parse_range(working_set, span) {
|
} else if let (expr, None) = parse_range(working_set, span) {
|
||||||
(expr, None)
|
(expr, None)
|
||||||
} else {
|
} else {
|
||||||
parse_full_column_path(working_set, span)
|
parse_full_column_path(working_set, None, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -942,7 +942,7 @@ pub fn parse_string_interpolation(
|
|||||||
end: b + 1,
|
end: b + 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expr, err) = parse_full_column_path(working_set, span);
|
let (expr, err) = parse_full_column_path(working_set, None, span);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
output.push(expr);
|
output.push(expr);
|
||||||
}
|
}
|
||||||
@ -977,7 +977,7 @@ pub fn parse_string_interpolation(
|
|||||||
end,
|
end,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (expr, err) = parse_full_column_path(working_set, span);
|
let (expr, err) = parse_full_column_path(working_set, None, span);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
output.push(expr);
|
output.push(expr);
|
||||||
}
|
}
|
||||||
@ -1067,6 +1067,7 @@ pub fn parse_variable_expr(
|
|||||||
|
|
||||||
pub fn parse_full_column_path(
|
pub fn parse_full_column_path(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
|
implicit_head: Option<VarId>,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> (Expression, Option<ParseError>) {
|
) -> (Expression, Option<ParseError>) {
|
||||||
// FIXME: assume for now a paren expr, but needs more
|
// FIXME: assume for now a paren expr, but needs more
|
||||||
@ -1077,10 +1078,10 @@ pub fn parse_full_column_path(
|
|||||||
let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']);
|
let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let mut tokens = tokens.into_iter();
|
let mut tokens = tokens.into_iter().peekable();
|
||||||
if let Some(head) = tokens.next() {
|
if let Some(head) = tokens.peek() {
|
||||||
let bytes = working_set.get_span_contents(head.span);
|
let bytes = working_set.get_span_contents(head.span);
|
||||||
let head = if bytes.starts_with(b"(") {
|
let (head, mut expect_dot) = if bytes.starts_with(b"(") {
|
||||||
let mut start = head.span.start;
|
let mut start = head.span.start;
|
||||||
let mut end = head.span.end;
|
let mut end = head.span.end;
|
||||||
|
|
||||||
@ -1105,27 +1106,42 @@ pub fn parse_full_column_path(
|
|||||||
|
|
||||||
let source = working_set.get_span_contents(span);
|
let source = working_set.get_span_contents(span);
|
||||||
|
|
||||||
let (tokens, err) = lex(source, span.start, &[b'\n'], &[]);
|
let (output, err) = lex(source, span.start, &[b'\n'], &[]);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let (output, err) = lite_parse(&tokens);
|
let (output, err) = lite_parse(&output);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let (output, err) = parse_block(working_set, &output, true);
|
let (output, err) = parse_block(working_set, &output, true);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let block_id = working_set.add_block(output);
|
let block_id = working_set.add_block(output);
|
||||||
|
tokens.next();
|
||||||
|
|
||||||
|
(
|
||||||
Expression {
|
Expression {
|
||||||
expr: Expr::Subexpression(block_id),
|
expr: Expr::Subexpression(block_id),
|
||||||
span,
|
span,
|
||||||
ty: Type::Unknown, // FIXME
|
ty: Type::Unknown, // FIXME
|
||||||
}
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
} else if bytes.starts_with(b"$") {
|
} else if bytes.starts_with(b"$") {
|
||||||
let (out, err) = parse_variable_expr(working_set, head.span);
|
let (out, err) = parse_variable_expr(working_set, head.span);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
out
|
tokens.next();
|
||||||
|
|
||||||
|
(out, true)
|
||||||
|
} else if let Some(var_id) = implicit_head {
|
||||||
|
(
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Var(var_id),
|
||||||
|
span: Span::unknown(),
|
||||||
|
ty: Type::Unknown,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
garbage(span),
|
garbage(span),
|
||||||
@ -1139,7 +1155,6 @@ pub fn parse_full_column_path(
|
|||||||
|
|
||||||
let mut tail = vec![];
|
let mut tail = vec![];
|
||||||
|
|
||||||
let mut expect_dot = true;
|
|
||||||
for path_element in tokens {
|
for path_element in tokens {
|
||||||
let bytes = working_set.get_span_contents(path_element.span);
|
let bytes = working_set.get_span_contents(path_element.span);
|
||||||
|
|
||||||
@ -1313,11 +1328,40 @@ pub fn parse_var_with_opt_type(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expand_to_cell_path(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
expression: &mut Expression,
|
||||||
|
var_id: VarId,
|
||||||
|
) {
|
||||||
|
if let Expression {
|
||||||
|
expr: Expr::String(_),
|
||||||
|
span,
|
||||||
|
..
|
||||||
|
} = expression
|
||||||
|
{
|
||||||
|
// Re-parse the string as if it were a cell-path
|
||||||
|
let (new_expression, _err) = parse_full_column_path(working_set, Some(var_id), *span);
|
||||||
|
|
||||||
|
*expression = new_expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_row_condition(
|
pub fn parse_row_condition(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
spans: &[Span],
|
||||||
) -> (Expression, Option<ParseError>) {
|
) -> (Expression, Option<ParseError>) {
|
||||||
parse_math_expression(working_set, spans)
|
let var_id = working_set.add_variable(b"$it".to_vec(), Type::Unknown);
|
||||||
|
let (expression, err) = parse_math_expression(working_set, spans, Some(var_id));
|
||||||
|
let span = span(spans);
|
||||||
|
(
|
||||||
|
Expression {
|
||||||
|
ty: Type::Bool,
|
||||||
|
span,
|
||||||
|
expr: Expr::RowCondition(var_id, Box::new(expression)),
|
||||||
|
},
|
||||||
|
err,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_signature(
|
pub fn parse_signature(
|
||||||
@ -2015,7 +2059,7 @@ pub fn parse_value(
|
|||||||
if let (expr, None) = parse_range(working_set, span) {
|
if let (expr, None) = parse_range(working_set, span) {
|
||||||
return (expr, None);
|
return (expr, None);
|
||||||
} else {
|
} else {
|
||||||
return parse_full_column_path(working_set, span);
|
return parse_full_column_path(working_set, None, span);
|
||||||
}
|
}
|
||||||
} else if bytes.starts_with(b"{") {
|
} else if bytes.starts_with(b"{") {
|
||||||
if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) {
|
if matches!(shape, SyntaxShape::Block) || matches!(shape, SyntaxShape::Any) {
|
||||||
@ -2162,6 +2206,7 @@ pub fn parse_operator(
|
|||||||
pub fn parse_math_expression(
|
pub fn parse_math_expression(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
spans: &[Span],
|
||||||
|
lhs_row_var_id: Option<VarId>,
|
||||||
) -> (Expression, Option<ParseError>) {
|
) -> (Expression, Option<ParseError>) {
|
||||||
// As the expr_stack grows, we increase the required precedence to grow larger
|
// As the expr_stack grows, we increase the required precedence to grow larger
|
||||||
// If, at any time, the operator we're looking at is the same or lower precedence
|
// If, at any time, the operator we're looking at is the same or lower precedence
|
||||||
@ -2220,6 +2265,10 @@ pub fn parse_math_expression(
|
|||||||
.pop()
|
.pop()
|
||||||
.expect("internal error: expression stack empty");
|
.expect("internal error: expression stack empty");
|
||||||
|
|
||||||
|
if let Some(row_var_id) = lhs_row_var_id {
|
||||||
|
expand_to_cell_path(working_set, &mut lhs, row_var_id);
|
||||||
|
}
|
||||||
|
|
||||||
let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
|
let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
@ -2250,6 +2299,10 @@ pub fn parse_math_expression(
|
|||||||
.pop()
|
.pop()
|
||||||
.expect("internal error: expression stack empty");
|
.expect("internal error: expression stack empty");
|
||||||
|
|
||||||
|
if let Some(row_var_id) = lhs_row_var_id {
|
||||||
|
expand_to_cell_path(working_set, &mut lhs, row_var_id);
|
||||||
|
}
|
||||||
|
|
||||||
let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
|
let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
@ -2276,7 +2329,7 @@ pub fn parse_expression(
|
|||||||
|
|
||||||
match bytes[0] {
|
match bytes[0] {
|
||||||
b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{'
|
b'0' | b'1' | b'2' | b'3' | b'4' | b'5' | b'6' | b'7' | b'8' | b'9' | b'(' | b'{'
|
||||||
| b'[' | b'$' | b'"' | b'\'' | b'-' => parse_math_expression(working_set, spans),
|
| b'[' | b'$' | b'"' | b'\'' | b'-' => parse_math_expression(working_set, spans, None),
|
||||||
_ => parse_call(working_set, spans, true),
|
_ => parse_call(working_set, spans, true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ pub enum Expr {
|
|||||||
Call(Box<Call>),
|
Call(Box<Call>),
|
||||||
ExternalCall(Vec<u8>, Vec<Vec<u8>>),
|
ExternalCall(Vec<u8>, Vec<Vec<u8>>),
|
||||||
Operator(Operator),
|
Operator(Operator),
|
||||||
|
RowCondition(VarId, Box<Expression>),
|
||||||
BinaryOp(Box<Expression>, Box<Expression>, Box<Expression>), //lhs, op, rhs
|
BinaryOp(Box<Expression>, Box<Expression>, Box<Expression>), //lhs, op, rhs
|
||||||
Subexpression(BlockId),
|
Subexpression(BlockId),
|
||||||
Block(BlockId),
|
Block(BlockId),
|
||||||
|
@ -123,6 +123,24 @@ impl EngineState {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec<Vec<u8>> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for scope in self.scope.iter().rev() {
|
||||||
|
for decl in &scope.decls {
|
||||||
|
if decl.0.starts_with(name) {
|
||||||
|
output.push(decl.0.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_span_contents(&self, span: Span) -> &[u8] {
|
||||||
|
&self.file_contents[span.start..span.end]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_var(&self, var_id: VarId) -> &Type {
|
pub fn get_var(&self, var_id: VarId) -> &Type {
|
||||||
self.vars
|
self.vars
|
||||||
.get(var_id)
|
.get(var_id)
|
||||||
@ -496,6 +514,24 @@ impl<'a> StateWorkingSet<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_commands_by_prefix(&self, name: &[u8]) -> Vec<Vec<u8>> {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for scope in self.delta.scope.iter().rev() {
|
||||||
|
for decl in &scope.decls {
|
||||||
|
if decl.0.starts_with(name) {
|
||||||
|
output.push(decl.0.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut permanent = self.permanent_state.find_commands_by_prefix(name);
|
||||||
|
|
||||||
|
output.append(&mut permanent);
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_block(&self, block_id: BlockId) -> &Block {
|
pub fn get_block(&self, block_id: BlockId) -> &Block {
|
||||||
let num_permanent_blocks = self.permanent_state.num_blocks();
|
let num_permanent_blocks = self.permanent_state.num_blocks();
|
||||||
if block_id < num_permanent_blocks {
|
if block_id < num_permanent_blocks {
|
||||||
|
@ -277,6 +277,24 @@ impl Value {
|
|||||||
|
|
||||||
Ok(current)
|
Ok(current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string(s: &str, span: Span) -> Value {
|
||||||
|
Value::String {
|
||||||
|
val: s.into(),
|
||||||
|
span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_true(&self) -> bool {
|
||||||
|
matches!(self, Value::Bool { val: true, .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn columns(&self) -> Vec<String> {
|
||||||
|
match self {
|
||||||
|
Value::Record { cols, .. } => cols.clone(),
|
||||||
|
_ => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Value {
|
impl PartialEq for Value {
|
||||||
|
22
crates/nu-table/.gitignore
vendored
Normal file
22
crates/nu-table/.gitignore
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/target
|
||||||
|
/scratch
|
||||||
|
**/*.rs.bk
|
||||||
|
history.txt
|
||||||
|
tests/fixtures/nuplayground
|
||||||
|
crates/*/target
|
||||||
|
|
||||||
|
# Debian/Ubuntu
|
||||||
|
debian/.debhelper/
|
||||||
|
debian/debhelper-build-stamp
|
||||||
|
debian/files
|
||||||
|
debian/nu.substvars
|
||||||
|
debian/nu/
|
||||||
|
|
||||||
|
# macOS junk
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# JetBrains' IDE items
|
||||||
|
.idea/*
|
||||||
|
|
||||||
|
# VSCode's IDE items
|
||||||
|
.vscode/*
|
18
crates/nu-table/Cargo.toml
Normal file
18
crates/nu-table/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
authors = ["The Nu Project Contributors"]
|
||||||
|
description = "Nushell table printing"
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT"
|
||||||
|
name = "nu-table"
|
||||||
|
version = "0.36.0"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[[bin]]
|
||||||
|
name = "table"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
nu-ansi-term = "0.36.0"
|
||||||
|
|
||||||
|
regex = "1.4"
|
||||||
|
unicode-width = "0.1.8"
|
5
crates/nu-table/src/lib.rs
Normal file
5
crates/nu-table/src/lib.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mod table;
|
||||||
|
mod wrap;
|
||||||
|
|
||||||
|
pub use table::{draw_table, StyledString, Table, TextStyle, Theme};
|
||||||
|
pub use wrap::Alignment;
|
86
crates/nu-table/src/main.rs
Normal file
86
crates/nu-table/src/main.rs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
use nu_table::{draw_table, StyledString, Table, TextStyle, Theme};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<_> = std::env::args().collect();
|
||||||
|
let mut width = 0;
|
||||||
|
|
||||||
|
if args.len() > 1 {
|
||||||
|
// Width in terminal characters
|
||||||
|
width = args[1].parse::<usize>().expect("Need a width in columns");
|
||||||
|
}
|
||||||
|
|
||||||
|
if width < 4 {
|
||||||
|
println!("Width must be greater than or equal to 4, setting width to 80");
|
||||||
|
width = 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mocked up table data
|
||||||
|
let (table_headers, row_data) = make_table_data();
|
||||||
|
// The table headers
|
||||||
|
let headers = vec_of_str_to_vec_of_styledstr(&table_headers, true);
|
||||||
|
// The table rows
|
||||||
|
let rows = vec_of_str_to_vec_of_styledstr(&row_data, false);
|
||||||
|
// The table itself
|
||||||
|
let table = Table::new(headers, vec![rows; 3], Theme::rounded());
|
||||||
|
// FIXME: Config isn't available from here so just put these here to compile
|
||||||
|
let color_hm: HashMap<String, nu_ansi_term::Style> = HashMap::new();
|
||||||
|
// Capture the table as a string
|
||||||
|
let output_table = draw_table(&table, width, &color_hm);
|
||||||
|
// Draw the table
|
||||||
|
println!("{}", output_table)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_table_data() -> (Vec<&'static str>, Vec<&'static str>) {
|
||||||
|
let table_headers = vec![
|
||||||
|
"category",
|
||||||
|
"description",
|
||||||
|
"emoji",
|
||||||
|
"ios_version",
|
||||||
|
"unicode_version",
|
||||||
|
"aliases",
|
||||||
|
"tags",
|
||||||
|
"category2",
|
||||||
|
"description2",
|
||||||
|
"emoji2",
|
||||||
|
"ios_version2",
|
||||||
|
"unicode_version2",
|
||||||
|
"aliases2",
|
||||||
|
"tags2",
|
||||||
|
];
|
||||||
|
|
||||||
|
let row_data = vec![
|
||||||
|
"Smileys & Emotion",
|
||||||
|
"grinning face",
|
||||||
|
"😀",
|
||||||
|
"6",
|
||||||
|
"6.1",
|
||||||
|
"grinning",
|
||||||
|
"smile",
|
||||||
|
"Smileys & Emotion",
|
||||||
|
"grinning face",
|
||||||
|
"😀",
|
||||||
|
"6",
|
||||||
|
"6.1",
|
||||||
|
"grinning",
|
||||||
|
"smile",
|
||||||
|
];
|
||||||
|
|
||||||
|
(table_headers, row_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vec_of_str_to_vec_of_styledstr(data: &[&str], is_header: bool) -> Vec<StyledString> {
|
||||||
|
let mut v = vec![];
|
||||||
|
|
||||||
|
for x in data {
|
||||||
|
if is_header {
|
||||||
|
v.push(StyledString::new(
|
||||||
|
String::from(*x),
|
||||||
|
TextStyle::default_header(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
v.push(StyledString::new(String::from(*x), TextStyle::basic_left()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v
|
||||||
|
}
|
1259
crates/nu-table/src/table.rs
Normal file
1259
crates/nu-table/src/table.rs
Normal file
File diff suppressed because it is too large
Load Diff
274
crates/nu-table/src/wrap.rs
Normal file
274
crates/nu-table/src/wrap.rs
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
use crate::table::TextStyle;
|
||||||
|
use nu_ansi_term::Style;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::{fmt::Display, iter::Iterator};
|
||||||
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Alignment {
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Subline<'a> {
|
||||||
|
pub subline: &'a str,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Line<'a> {
|
||||||
|
pub sublines: Vec<Subline<'a>>,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WrappedLine {
|
||||||
|
pub line: String,
|
||||||
|
pub width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WrappedCell {
|
||||||
|
pub lines: Vec<WrappedLine>,
|
||||||
|
pub max_width: usize,
|
||||||
|
|
||||||
|
pub style: TextStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Display for Line<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut first = true;
|
||||||
|
for subline in &self.sublines {
|
||||||
|
if !first {
|
||||||
|
write!(f, " ")?;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
write!(f, "{}", subline.subline)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_sublines(input: &str) -> Vec<Vec<Subline>> {
|
||||||
|
input
|
||||||
|
.split_terminator('\n')
|
||||||
|
.map(|line| {
|
||||||
|
line.split_terminator(' ')
|
||||||
|
.map(|x| Subline {
|
||||||
|
subline: x,
|
||||||
|
width: {
|
||||||
|
// We've tried UnicodeWidthStr::width(x), UnicodeSegmentation::graphemes(x, true).count()
|
||||||
|
// and x.chars().count() with all types of combinations. Currently, it appears that
|
||||||
|
// getting the max of char count and Unicode width seems to produce the best layout.
|
||||||
|
// However, it's not perfect.
|
||||||
|
let c = x.chars().count();
|
||||||
|
let u = UnicodeWidthStr::width(x);
|
||||||
|
std::cmp::max(c, u)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column_width(input: &[Vec<Subline>]) -> usize {
|
||||||
|
let mut max = 0;
|
||||||
|
|
||||||
|
for line in input {
|
||||||
|
let mut total = 0;
|
||||||
|
|
||||||
|
let mut first = true;
|
||||||
|
for inp in line {
|
||||||
|
if !first {
|
||||||
|
// Account for the space
|
||||||
|
total += 1;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
total += inp.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
if total > max {
|
||||||
|
max = total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
max
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_word(cell_width: usize, word: &str) -> Vec<Subline> {
|
||||||
|
use unicode_width::UnicodeWidthChar;
|
||||||
|
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut current_width = 0;
|
||||||
|
let mut start_index = 0;
|
||||||
|
let mut end_index;
|
||||||
|
|
||||||
|
for c in word.char_indices() {
|
||||||
|
if let Some(width) = c.1.width() {
|
||||||
|
end_index = c.0;
|
||||||
|
if current_width + width > cell_width {
|
||||||
|
output.push(Subline {
|
||||||
|
subline: &word[start_index..end_index],
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
|
||||||
|
start_index = c.0;
|
||||||
|
current_width = width;
|
||||||
|
} else {
|
||||||
|
current_width += width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if start_index != word.len() {
|
||||||
|
output.push(Subline {
|
||||||
|
subline: &word[start_index..],
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wrap<'a>(
|
||||||
|
cell_width: usize,
|
||||||
|
mut input: impl Iterator<Item = Subline<'a>>,
|
||||||
|
color_hm: &HashMap<String, Style>,
|
||||||
|
re_leading: ®ex::Regex,
|
||||||
|
re_trailing: ®ex::Regex,
|
||||||
|
) -> (Vec<WrappedLine>, usize) {
|
||||||
|
let mut lines = vec![];
|
||||||
|
let mut current_line: Vec<Subline> = vec![];
|
||||||
|
let mut current_width = 0;
|
||||||
|
let mut first = true;
|
||||||
|
let mut max_width = 0;
|
||||||
|
let lead_trail_space_bg_color = color_hm
|
||||||
|
.get("leading_trailing_space_bg")
|
||||||
|
.unwrap_or(&Style::default())
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match input.next() {
|
||||||
|
Some(item) => {
|
||||||
|
if !first {
|
||||||
|
current_width += 1;
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.width + current_width > cell_width {
|
||||||
|
// If this is a really long single word, we need to split the word
|
||||||
|
if current_line.len() == 1 && current_width > cell_width {
|
||||||
|
max_width = cell_width;
|
||||||
|
let sublines = split_word(cell_width, current_line[0].subline);
|
||||||
|
for subline in sublines {
|
||||||
|
let width = subline.width;
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: vec![subline],
|
||||||
|
width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
first = true;
|
||||||
|
|
||||||
|
current_width = item.width;
|
||||||
|
current_line = vec![item];
|
||||||
|
} else {
|
||||||
|
if !current_line.is_empty() {
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: current_line,
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
first = true;
|
||||||
|
|
||||||
|
current_width = item.width;
|
||||||
|
current_line = vec![item];
|
||||||
|
max_width = std::cmp::max(max_width, current_width);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
current_width += item.width;
|
||||||
|
current_line.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if current_width > cell_width {
|
||||||
|
// We need to break up the last word
|
||||||
|
let sublines = split_word(cell_width, current_line[0].subline);
|
||||||
|
for subline in sublines {
|
||||||
|
let width = subline.width;
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: vec![subline],
|
||||||
|
width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if current_width > 0 {
|
||||||
|
lines.push(Line {
|
||||||
|
sublines: current_line,
|
||||||
|
width: current_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut current_max = 0;
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for line in lines {
|
||||||
|
let mut current_line_width = 0;
|
||||||
|
let mut first = true;
|
||||||
|
let mut current_line = String::new();
|
||||||
|
|
||||||
|
for subline in line.sublines {
|
||||||
|
if !first {
|
||||||
|
current_line_width += 1 + subline.width;
|
||||||
|
current_line.push(' ');
|
||||||
|
} else {
|
||||||
|
first = false;
|
||||||
|
current_line_width = subline.width;
|
||||||
|
}
|
||||||
|
current_line.push_str(subline.subline);
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_line_width > current_max {
|
||||||
|
current_max = current_line_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// highlight leading and trailing spaces so they stand out.
|
||||||
|
let mut bg_color_string = Style::default().prefix().to_string();
|
||||||
|
// right now config settings can only set foreground colors so, in this
|
||||||
|
// instance we take the foreground color and make it a background color
|
||||||
|
if let Some(bg) = lead_trail_space_bg_color.foreground {
|
||||||
|
bg_color_string = Style::default().on(bg).prefix().to_string()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(leading_match) = re_leading.find(¤t_line.clone()) {
|
||||||
|
String::insert_str(
|
||||||
|
&mut current_line,
|
||||||
|
leading_match.end(),
|
||||||
|
nu_ansi_term::ansi::RESET,
|
||||||
|
);
|
||||||
|
String::insert_str(&mut current_line, leading_match.start(), &bg_color_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(trailing_match) = re_trailing.find(¤t_line.clone()) {
|
||||||
|
String::insert_str(&mut current_line, trailing_match.start(), &bg_color_string);
|
||||||
|
current_line += nu_ansi_term::ansi::RESET;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(WrappedLine {
|
||||||
|
line: current_line,
|
||||||
|
width: current_line_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(output, current_max)
|
||||||
|
}
|
10
src/main.rs
10
src/main.rs
@ -1,4 +1,4 @@
|
|||||||
use nu_cli::{report_parsing_error, report_shell_error, NuHighlighter};
|
use nu_cli::{report_parsing_error, report_shell_error, NuCompleter, NuHighlighter};
|
||||||
use nu_command::create_default_context;
|
use nu_command::create_default_context;
|
||||||
use nu_engine::eval_block;
|
use nu_engine::eval_block;
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
@ -6,6 +6,7 @@ use nu_protocol::{
|
|||||||
engine::{EngineState, EvaluationContext, StateWorkingSet},
|
engine::{EngineState, EvaluationContext, StateWorkingSet},
|
||||||
Value,
|
Value,
|
||||||
};
|
};
|
||||||
|
use reedline::DefaultCompletionActionHandler;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
@ -53,6 +54,8 @@ fn main() -> std::io::Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal};
|
use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal};
|
||||||
|
|
||||||
|
let completer = NuCompleter::new(engine_state.clone());
|
||||||
|
|
||||||
let mut line_editor = Reedline::create()?
|
let mut line_editor = Reedline::create()?
|
||||||
.with_history(Box::new(FileBackedHistory::with_file(
|
.with_history(Box::new(FileBackedHistory::with_file(
|
||||||
1000,
|
1000,
|
||||||
@ -60,7 +63,10 @@ fn main() -> std::io::Result<()> {
|
|||||||
)?))?
|
)?))?
|
||||||
.with_highlighter(Box::new(NuHighlighter {
|
.with_highlighter(Box::new(NuHighlighter {
|
||||||
engine_state: engine_state.clone(),
|
engine_state: engine_state.clone(),
|
||||||
}));
|
}))
|
||||||
|
.with_completion_action_handler(Box::new(
|
||||||
|
DefaultCompletionActionHandler::default().with_completer(Box::new(completer)),
|
||||||
|
));
|
||||||
|
|
||||||
let prompt = DefaultPrompt::new(1);
|
let prompt = DefaultPrompt::new(1);
|
||||||
let mut current_line = 1;
|
let mut current_line = 1;
|
||||||
|
16
src/tests.rs
16
src/tests.rs
@ -292,3 +292,19 @@ fn row_iteration() -> TestResult {
|
|||||||
fn record_iteration() -> TestResult {
|
fn record_iteration() -> TestResult {
|
||||||
run_test("([[name, level]; [aa, 100], [bb, 200]] | each { $it | each { |x| if $x.column == \"level\" { $x.value + 100 } else { $x.value } } }).level", "[200, 300]")
|
run_test("([[name, level]; [aa, 100], [bb, 200]] | each { $it | each { |x| if $x.column == \"level\" { $x.value + 100 } else { $x.value } } }).level", "[200, 300]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn row_condition1() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"([[name, size]; [a, 1], [b, 2], [c, 3]] | where size < 3).name",
|
||||||
|
"[a, b]",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn row_condition2() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
"[[name, size]; [a, 1], [b, 2], [c, 3]] | where $it.size > 2 | length",
|
||||||
|
"1",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user