mirror of
https://github.com/nushell/nushell
synced 2024-12-29 14:33:13 +00:00
Merge branch 'main' into source-command
This commit is contained in:
commit
dfd321a679
38 changed files with 1168 additions and 126 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -499,6 +499,7 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-json",
|
"nu-json",
|
||||||
|
"nu-path",
|
||||||
"nu-protocol",
|
"nu-protocol",
|
||||||
"nu-table",
|
"nu-table",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
|
|
3
TODO.md
3
TODO.md
|
@ -23,7 +23,8 @@
|
||||||
- [x] Signature needs to make parameters visible in scope before block is parsed
|
- [x] Signature needs to make parameters visible in scope before block is parsed
|
||||||
- [x] Externals
|
- [x] Externals
|
||||||
- [x] Modules and imports
|
- [x] Modules and imports
|
||||||
- [ ] Exports
|
- [x] Exports
|
||||||
|
- [ ] Input/output types
|
||||||
- [ ] Support for `$in`
|
- [ ] Support for `$in`
|
||||||
- [ ] Value serialization
|
- [ ] Value serialization
|
||||||
- [ ] Handling rows with missing columns during a cell path
|
- [ ] Handling rows with missing columns during a cell path
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
mod completions;
|
mod completions;
|
||||||
mod errors;
|
mod errors;
|
||||||
|
mod prompt;
|
||||||
mod syntax_highlight;
|
mod syntax_highlight;
|
||||||
mod validation;
|
mod validation;
|
||||||
|
|
||||||
pub use completions::NuCompleter;
|
pub use completions::NuCompleter;
|
||||||
pub use errors::report_error;
|
pub use errors::report_error;
|
||||||
|
pub use prompt::NushellPrompt;
|
||||||
pub use syntax_highlight::NuHighlighter;
|
pub use syntax_highlight::NuHighlighter;
|
||||||
pub use validation::NuValidator;
|
pub use validation::NuValidator;
|
||||||
|
|
89
crates/nu-cli/src/prompt.rs
Normal file
89
crates/nu-cli/src/prompt.rs
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
use {
|
||||||
|
reedline::{
|
||||||
|
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||||
|
},
|
||||||
|
std::borrow::Cow,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Nushell prompt definition
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NushellPrompt {
|
||||||
|
prompt_command: String,
|
||||||
|
prompt_string: String,
|
||||||
|
// These are part of the struct definition in case we want to allow
|
||||||
|
// further customization to the shell status
|
||||||
|
default_prompt_indicator: String,
|
||||||
|
default_vi_insert_prompt_indicator: String,
|
||||||
|
default_vi_visual_prompt_indicator: String,
|
||||||
|
default_multiline_indicator: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NushellPrompt {
|
||||||
|
fn default() -> Self {
|
||||||
|
NushellPrompt::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NushellPrompt {
|
||||||
|
pub fn new() -> NushellPrompt {
|
||||||
|
NushellPrompt {
|
||||||
|
prompt_command: "".to_string(),
|
||||||
|
prompt_string: "".to_string(),
|
||||||
|
default_prompt_indicator: "〉".to_string(),
|
||||||
|
default_vi_insert_prompt_indicator: ": ".to_string(),
|
||||||
|
default_vi_visual_prompt_indicator: "v ".to_string(),
|
||||||
|
default_multiline_indicator: "::: ".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_new_prompt(&self, prompt_command: &str) -> bool {
|
||||||
|
self.prompt_command != prompt_command
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_prompt(&mut self, prompt_command: String, prompt_string: String) {
|
||||||
|
self.prompt_command = prompt_command;
|
||||||
|
self.prompt_string = prompt_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_wrapped_custom_string(&self, str: String) -> String {
|
||||||
|
format!("({})", str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prompt for NushellPrompt {
|
||||||
|
fn render_prompt(&self, _: usize) -> Cow<str> {
|
||||||
|
self.prompt_string.as_str().into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
|
||||||
|
match edit_mode {
|
||||||
|
PromptEditMode::Default => self.default_prompt_indicator.as_str().into(),
|
||||||
|
PromptEditMode::Emacs => self.default_prompt_indicator.as_str().into(),
|
||||||
|
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||||
|
PromptViMode::Normal => self.default_prompt_indicator.as_str().into(),
|
||||||
|
PromptViMode::Insert => self.default_vi_insert_prompt_indicator.as_str().into(),
|
||||||
|
PromptViMode::Visual => self.default_vi_visual_prompt_indicator.as_str().into(),
|
||||||
|
},
|
||||||
|
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
|
||||||
|
Cow::Borrowed(self.default_multiline_indicator.as_str())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_prompt_history_search_indicator(
|
||||||
|
&self,
|
||||||
|
history_search: PromptHistorySearch,
|
||||||
|
) -> Cow<str> {
|
||||||
|
let prefix = match history_search.status {
|
||||||
|
PromptHistorySearchStatus::Passing => "",
|
||||||
|
PromptHistorySearchStatus::Failing => "failing ",
|
||||||
|
};
|
||||||
|
|
||||||
|
Cow::Owned(format!(
|
||||||
|
"({}reverse-search: {})",
|
||||||
|
prefix, history_search.term
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
nu-engine = { path = "../nu-engine" }
|
nu-engine = { path = "../nu-engine" }
|
||||||
nu-json = { path = "../nu-json" }
|
nu-json = { path = "../nu-json" }
|
||||||
|
nu-path = { path = "../nu-path" }
|
||||||
nu-protocol = { path = "../nu-protocol" }
|
nu-protocol = { path = "../nu-protocol" }
|
||||||
nu-table = { path = "../nu-table" }
|
nu-table = { path = "../nu-table" }
|
||||||
|
|
||||||
|
|
35
crates/nu-command/src/core_commands/export_def.rs
Normal file
35
crates/nu-command/src/core_commands/export_def.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct ExportDef;
|
||||||
|
|
||||||
|
impl Command for ExportDef {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"export def"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Define a custom command and export it from a module"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("export def")
|
||||||
|
.required("target", SyntaxShape::String, "definition name")
|
||||||
|
.required("params", SyntaxShape::Signature, "parameters")
|
||||||
|
.required(
|
||||||
|
"block",
|
||||||
|
SyntaxShape::Block(Some(vec![])),
|
||||||
|
"body of the definition",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
_input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
28
crates/nu-command/src/core_commands/hide.rs
Normal file
28
crates/nu-command/src/core_commands/hide.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Hide;
|
||||||
|
|
||||||
|
impl Command for Hide {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hide"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Hide definitions in the current scope"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("hide").required("pattern", SyntaxShape::String, "import pattern")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
_input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
mod alias;
|
mod alias;
|
||||||
mod def;
|
mod def;
|
||||||
mod do_;
|
mod do_;
|
||||||
|
mod export_def;
|
||||||
mod help;
|
mod help;
|
||||||
|
mod hide;
|
||||||
mod if_;
|
mod if_;
|
||||||
mod let_;
|
mod let_;
|
||||||
mod module;
|
mod module;
|
||||||
|
@ -11,7 +13,9 @@ mod use_;
|
||||||
pub use alias::Alias;
|
pub use alias::Alias;
|
||||||
pub use def::Def;
|
pub use def::Def;
|
||||||
pub use do_::Do;
|
pub use do_::Do;
|
||||||
|
pub use export_def::ExportDef;
|
||||||
pub use help::Help;
|
pub use help::Help;
|
||||||
|
pub use hide::Hide;
|
||||||
pub use if_::If;
|
pub use if_::If;
|
||||||
pub use let_::Let;
|
pub use let_::Let;
|
||||||
pub use module::Module;
|
pub use module::Module;
|
||||||
|
|
|
@ -14,7 +14,7 @@ impl Command for Use {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> nu_protocol::Signature {
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
Signature::build("use").required("module_name", SyntaxShape::String, "module name")
|
Signature::build("use").required("pattern", SyntaxShape::String, "import pattern")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
|
|
|
@ -5,11 +5,7 @@ use nu_protocol::{
|
||||||
Signature,
|
Signature,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::*;
|
||||||
Alias, Benchmark, BuildString, Def, Do, Each, External, For, From, FromJson, Git, GitCheckout,
|
|
||||||
Help, If, Length, Let, LetEnv, Lines, ListGitBranches, Ls, Module, Ps, Source, Sys, Table, Use,
|
|
||||||
Where,
|
|
||||||
};
|
|
||||||
|
|
||||||
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()));
|
||||||
|
@ -20,14 +16,18 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||||
working_set.add_decl(Box::new(Alias));
|
working_set.add_decl(Box::new(Alias));
|
||||||
working_set.add_decl(Box::new(Benchmark));
|
working_set.add_decl(Box::new(Benchmark));
|
||||||
working_set.add_decl(Box::new(BuildString));
|
working_set.add_decl(Box::new(BuildString));
|
||||||
|
working_set.add_decl(Box::new(Cd));
|
||||||
working_set.add_decl(Box::new(Def));
|
working_set.add_decl(Box::new(Def));
|
||||||
working_set.add_decl(Box::new(Do));
|
working_set.add_decl(Box::new(Do));
|
||||||
working_set.add_decl(Box::new(Each));
|
working_set.add_decl(Box::new(Each));
|
||||||
|
working_set.add_decl(Box::new(ExportDef));
|
||||||
working_set.add_decl(Box::new(External));
|
working_set.add_decl(Box::new(External));
|
||||||
working_set.add_decl(Box::new(For));
|
working_set.add_decl(Box::new(For));
|
||||||
working_set.add_decl(Box::new(From));
|
working_set.add_decl(Box::new(From));
|
||||||
working_set.add_decl(Box::new(FromJson));
|
working_set.add_decl(Box::new(FromJson));
|
||||||
|
working_set.add_decl(Box::new(Get));
|
||||||
working_set.add_decl(Box::new(Help));
|
working_set.add_decl(Box::new(Help));
|
||||||
|
working_set.add_decl(Box::new(Hide));
|
||||||
working_set.add_decl(Box::new(If));
|
working_set.add_decl(Box::new(If));
|
||||||
working_set.add_decl(Box::new(Length));
|
working_set.add_decl(Box::new(Length));
|
||||||
working_set.add_decl(Box::new(Let));
|
working_set.add_decl(Box::new(Let));
|
||||||
|
@ -36,10 +36,12 @@ pub fn create_default_context() -> Rc<RefCell<EngineState>> {
|
||||||
working_set.add_decl(Box::new(Ls));
|
working_set.add_decl(Box::new(Ls));
|
||||||
working_set.add_decl(Box::new(Module));
|
working_set.add_decl(Box::new(Module));
|
||||||
working_set.add_decl(Box::new(Ps));
|
working_set.add_decl(Box::new(Ps));
|
||||||
|
working_set.add_decl(Box::new(Select));
|
||||||
working_set.add_decl(Box::new(Sys));
|
working_set.add_decl(Box::new(Sys));
|
||||||
working_set.add_decl(Box::new(Table));
|
working_set.add_decl(Box::new(Table));
|
||||||
working_set.add_decl(Box::new(Use));
|
working_set.add_decl(Box::new(Use));
|
||||||
working_set.add_decl(Box::new(Where));
|
working_set.add_decl(Box::new(Where));
|
||||||
|
working_set.add_decl(Box::new(Wrap));
|
||||||
|
|
||||||
// This is a WIP proof of concept
|
// This is a WIP proof of concept
|
||||||
working_set.add_decl(Box::new(ListGitBranches));
|
working_set.add_decl(Box::new(ListGitBranches));
|
||||||
|
|
46
crates/nu-command/src/filesystem/cd.rs
Normal file
46
crates/nu-command/src/filesystem/cd.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Cd;
|
||||||
|
|
||||||
|
impl Command for Cd {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"cd"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Change directory."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("cd").optional("path", SyntaxShape::FilePath, "the path to change to")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
_input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
let path: Option<String> = call.opt(context, 0)?;
|
||||||
|
|
||||||
|
let path = match path {
|
||||||
|
Some(path) => {
|
||||||
|
let path = nu_path::expand_tilde(path);
|
||||||
|
path.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let path = nu_path::expand_tilde("~");
|
||||||
|
path.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let _ = std::env::set_current_dir(&path);
|
||||||
|
|
||||||
|
//FIXME: this only changes the current scope, but instead this environment variable
|
||||||
|
//should probably be a block that loads the information from the state in the overlay
|
||||||
|
context.add_env_var("PWD".into(), path);
|
||||||
|
Ok(Value::Nothing { span: call.head })
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,8 +63,8 @@ impl Command for Ls {
|
||||||
} else {
|
} else {
|
||||||
Value::Nothing { span: call_span }
|
Value::Nothing { span: call_span }
|
||||||
},
|
},
|
||||||
Value::Int {
|
Value::Filesize {
|
||||||
val: filesize as i64,
|
val: filesize,
|
||||||
span: call_span,
|
span: call_span,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod cd;
|
||||||
mod ls;
|
mod ls;
|
||||||
|
|
||||||
|
pub use cd::Cd;
|
||||||
pub use ls::Ls;
|
pub use ls::Ls;
|
||||||
|
|
35
crates/nu-command/src/filters/get.rs
Normal file
35
crates/nu-command/src/filters/get.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Get;
|
||||||
|
|
||||||
|
impl Command for Get {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"get"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Extract data using a cell path."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("wrap").required(
|
||||||
|
"cell_path",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"the cell path to the data",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
let cell_path: CellPath = call.req(context, 0)?;
|
||||||
|
|
||||||
|
input.follow_cell_path(&cell_path.members)
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ impl Command for Lines {
|
||||||
.filter_map(|s| {
|
.filter_map(|s| {
|
||||||
if !s.is_empty() {
|
if !s.is_empty() {
|
||||||
Some(Value::String {
|
Some(Value::String {
|
||||||
val: s.trim().into(),
|
val: s.into(),
|
||||||
span,
|
span,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
mod each;
|
mod each;
|
||||||
mod for_;
|
mod for_;
|
||||||
|
mod get;
|
||||||
mod length;
|
mod length;
|
||||||
mod lines;
|
mod lines;
|
||||||
|
mod select;
|
||||||
mod where_;
|
mod where_;
|
||||||
|
mod wrap;
|
||||||
|
|
||||||
pub use each::Each;
|
pub use each::Each;
|
||||||
pub use for_::For;
|
pub use for_::For;
|
||||||
|
pub use get::Get;
|
||||||
pub use length::Length;
|
pub use length::Length;
|
||||||
pub use lines::Lines;
|
pub use lines::Lines;
|
||||||
|
pub use select::Select;
|
||||||
pub use where_::Where;
|
pub use where_::Where;
|
||||||
|
pub use wrap::Wrap;
|
||||||
|
|
182
crates/nu-command/src/filters/select.rs
Normal file
182
crates/nu-command/src/filters/select.rs
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::{Call, CellPath};
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{Example, IntoValueStream, ShellError, Signature, Span, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Select;
|
||||||
|
|
||||||
|
impl Command for Select {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"select"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("select").rest(
|
||||||
|
"rest",
|
||||||
|
SyntaxShape::CellPath,
|
||||||
|
"the columns to select from the table",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Down-select table to only these columns."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
let columns: Vec<CellPath> = call.rest(context, 0)?;
|
||||||
|
let span = call.head;
|
||||||
|
|
||||||
|
select(span, columns, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Select just the name column",
|
||||||
|
example: "ls | select name",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Select the name and size columns",
|
||||||
|
example: "ls | select name size",
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select(span: Span, columns: Vec<CellPath>, input: Value) -> Result<Value, ShellError> {
|
||||||
|
if columns.is_empty() {
|
||||||
|
return Err(ShellError::CantFindColumn(span));
|
||||||
|
}
|
||||||
|
|
||||||
|
match input {
|
||||||
|
Value::List {
|
||||||
|
vals: input_vals,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
let mut output = vec![];
|
||||||
|
|
||||||
|
for input_val in input_vals {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
for path in &columns {
|
||||||
|
//FIXME: improve implementation to not clone
|
||||||
|
let fetcher = input_val.clone().follow_cell_path(&path.members)?;
|
||||||
|
|
||||||
|
cols.push(path.into_string());
|
||||||
|
vals.push(fetcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(Value::Record { cols, vals, span })
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::List { vals: output, span })
|
||||||
|
}
|
||||||
|
Value::Stream { stream, span } => Ok(Value::Stream {
|
||||||
|
stream: stream
|
||||||
|
.map(move |x| {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
for path in &columns {
|
||||||
|
//FIXME: improve implementation to not clone
|
||||||
|
match x.clone().follow_cell_path(&path.members) {
|
||||||
|
Ok(value) => {
|
||||||
|
cols.push(path.into_string());
|
||||||
|
vals.push(value);
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
cols.push(path.into_string());
|
||||||
|
vals.push(Value::Error { error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value::Record { cols, vals, span }
|
||||||
|
})
|
||||||
|
.into_value_stream(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
v => {
|
||||||
|
let mut cols = vec![];
|
||||||
|
let mut vals = vec![];
|
||||||
|
|
||||||
|
for cell_path in columns {
|
||||||
|
// FIXME: remove clone
|
||||||
|
let result = v.clone().follow_cell_path(&cell_path.members)?;
|
||||||
|
|
||||||
|
cols.push(cell_path.into_string());
|
||||||
|
vals.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Value::Record { cols, vals, span })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
|
// mod tests {
|
||||||
|
// use nu_protocol::ColumnPath;
|
||||||
|
// use nu_source::Span;
|
||||||
|
// use nu_source::SpannedItem;
|
||||||
|
// use nu_source::Tag;
|
||||||
|
// use nu_stream::InputStream;
|
||||||
|
// use nu_test_support::value::nothing;
|
||||||
|
// use nu_test_support::value::row;
|
||||||
|
// use nu_test_support::value::string;
|
||||||
|
|
||||||
|
// use super::select;
|
||||||
|
// use super::Command;
|
||||||
|
// use super::ShellError;
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn examples_work_as_expected() -> Result<(), ShellError> {
|
||||||
|
// use crate::examples::test as test_examples;
|
||||||
|
|
||||||
|
// test_examples(Command {})
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn select_using_sparse_table() {
|
||||||
|
// // Create a sparse table with 3 rows:
|
||||||
|
// // col_foo | col_bar
|
||||||
|
// // -----------------
|
||||||
|
// // foo |
|
||||||
|
// // | bar
|
||||||
|
// // foo |
|
||||||
|
// let input = vec![
|
||||||
|
// row(indexmap! {"col_foo".into() => string("foo")}),
|
||||||
|
// row(indexmap! {"col_bar".into() => string("bar")}),
|
||||||
|
// row(indexmap! {"col_foo".into() => string("foo")}),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// let expected = vec![
|
||||||
|
// row(
|
||||||
|
// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()},
|
||||||
|
// ),
|
||||||
|
// row(
|
||||||
|
// indexmap! {"col_none".into() => nothing(), "col_foo".into() => nothing(), "col_bar".into() => string("bar")},
|
||||||
|
// ),
|
||||||
|
// row(
|
||||||
|
// indexmap! {"col_none".into() => nothing(), "col_foo".into() => string("foo"), "col_bar".into() => nothing()},
|
||||||
|
// ),
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// let actual = select(
|
||||||
|
// Tag::unknown(),
|
||||||
|
// vec![
|
||||||
|
// ColumnPath::build(&"col_none".to_string().spanned(Span::unknown())),
|
||||||
|
// ColumnPath::build(&"col_foo".to_string().spanned(Span::unknown())),
|
||||||
|
// ColumnPath::build(&"col_bar".to_string().spanned(Span::unknown())),
|
||||||
|
// ],
|
||||||
|
// input.into(),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// assert_eq!(Ok(expected), actual.map(InputStream::into_vec));
|
||||||
|
// }
|
||||||
|
// }
|
59
crates/nu-command/src/filters/wrap.rs
Normal file
59
crates/nu-command/src/filters/wrap.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EvaluationContext};
|
||||||
|
use nu_protocol::{IntoValueStream, Signature, SyntaxShape, Value};
|
||||||
|
|
||||||
|
pub struct Wrap;
|
||||||
|
|
||||||
|
impl Command for Wrap {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"wrap"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Wrap the value into a column."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> nu_protocol::Signature {
|
||||||
|
Signature::build("wrap").required("name", SyntaxShape::String, "the name of the column")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
call: &Call,
|
||||||
|
input: Value,
|
||||||
|
) -> Result<nu_protocol::Value, nu_protocol::ShellError> {
|
||||||
|
let span = call.head;
|
||||||
|
let name: String = call.req(context, 0)?;
|
||||||
|
|
||||||
|
match input {
|
||||||
|
Value::List { vals, .. } => Ok(Value::List {
|
||||||
|
vals: vals
|
||||||
|
.into_iter()
|
||||||
|
.map(move |x| Value::Record {
|
||||||
|
cols: vec![name.clone()],
|
||||||
|
vals: vec![x],
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
Value::Stream { stream, .. } => Ok(Value::Stream {
|
||||||
|
stream: stream
|
||||||
|
.map(move |x| Value::Record {
|
||||||
|
cols: vec![name.clone()],
|
||||||
|
vals: vec![x],
|
||||||
|
span,
|
||||||
|
})
|
||||||
|
.into_value_stream(),
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
_ => Ok(Value::Record {
|
||||||
|
cols: vec![name],
|
||||||
|
vals: vec![input],
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -274,10 +274,11 @@ pub fn host(sys: &mut System, span: Span) -> Option<Value> {
|
||||||
span,
|
span,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// dict.insert_untagged(
|
cols.push("uptime".into());
|
||||||
// "uptime",
|
vals.push(Value::Duration {
|
||||||
// UntaggedValue::duration(1000000000 * sys.uptime() as i64),
|
val: 1000000000 * sys.uptime() as u64,
|
||||||
// );
|
span,
|
||||||
|
});
|
||||||
|
|
||||||
let mut users = vec![];
|
let mut users = vec![];
|
||||||
for user in sys.users() {
|
for user in sys.users() {
|
||||||
|
|
|
@ -63,7 +63,7 @@ impl Command for Table {
|
||||||
output.push(vec![
|
output.push(vec![
|
||||||
StyledString {
|
StyledString {
|
||||||
contents: c,
|
contents: c,
|
||||||
style: nu_table::TextStyle::default_header(),
|
style: nu_table::TextStyle::default_field(),
|
||||||
},
|
},
|
||||||
StyledString {
|
StyledString {
|
||||||
contents: v.into_string(),
|
contents: v.into_string(),
|
||||||
|
|
|
@ -14,6 +14,14 @@ pub trait CallExt {
|
||||||
context: &EvaluationContext,
|
context: &EvaluationContext,
|
||||||
starting_pos: usize,
|
starting_pos: usize,
|
||||||
) -> Result<Vec<T>, ShellError>;
|
) -> Result<Vec<T>, ShellError>;
|
||||||
|
|
||||||
|
fn opt<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
pos: usize,
|
||||||
|
) -> Result<Option<T>, ShellError>;
|
||||||
|
|
||||||
|
fn req<T: FromValue>(&self, context: &EvaluationContext, pos: usize) -> Result<T, ShellError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallExt for Call {
|
impl CallExt for Call {
|
||||||
|
@ -44,4 +52,29 @@ impl CallExt for Call {
|
||||||
|
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn opt<T: FromValue>(
|
||||||
|
&self,
|
||||||
|
context: &EvaluationContext,
|
||||||
|
pos: usize,
|
||||||
|
) -> Result<Option<T>, ShellError> {
|
||||||
|
if let Some(expr) = self.nth(pos) {
|
||||||
|
let result = eval_expression(context, &expr)?;
|
||||||
|
FromValue::from_value(&result).map(Some)
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn req<T: FromValue>(&self, context: &EvaluationContext, pos: usize) -> Result<T, ShellError> {
|
||||||
|
if let Some(expr) = self.nth(pos) {
|
||||||
|
let result = eval_expression(context, &expr)?;
|
||||||
|
FromValue::from_value(&result)
|
||||||
|
} else {
|
||||||
|
Err(ShellError::AccessBeyondEnd(
|
||||||
|
self.positional.len(),
|
||||||
|
self.head,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,6 +157,10 @@ pub fn eval_expression(
|
||||||
Expr::Var(var_id) => context
|
Expr::Var(var_id) => context
|
||||||
.get_var(*var_id)
|
.get_var(*var_id)
|
||||||
.map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)),
|
.map_err(move |_| ShellError::VariableNotFoundAtRuntime(expr.span)),
|
||||||
|
Expr::CellPath(cell_path) => Ok(Value::CellPath {
|
||||||
|
val: cell_path.clone(),
|
||||||
|
span: expr.span,
|
||||||
|
}),
|
||||||
Expr::FullCellPath(cell_path) => {
|
Expr::FullCellPath(cell_path) => {
|
||||||
let value = eval_expression(context, &cell_path.head)?;
|
let value = eval_expression(context, &cell_path.head)?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// use std::path::PathBuf;
|
// use std::path::PathBuf;
|
||||||
|
|
||||||
// use nu_path::expand_path;
|
// use nu_path::expand_path;
|
||||||
|
use nu_protocol::ast::{CellPath, PathMember};
|
||||||
use nu_protocol::ShellError;
|
use nu_protocol::ShellError;
|
||||||
use nu_protocol::{Range, Spanned, Value};
|
use nu_protocol::{Range, Spanned, Value};
|
||||||
|
|
||||||
|
@ -110,28 +111,32 @@ impl FromValue for ColumnPath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromValue for bool {
|
*/
|
||||||
|
|
||||||
|
impl FromValue for CellPath {
|
||||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||||
|
let span = v.span();
|
||||||
match v {
|
match v {
|
||||||
Value {
|
Value::CellPath { val, .. } => Ok(val.clone()),
|
||||||
value: UntaggedValue::Primitive(Primitive::Boolean(b)),
|
Value::String { val, .. } => Ok(CellPath {
|
||||||
..
|
members: vec![PathMember::String {
|
||||||
} => Ok(*b),
|
val: val.clone(),
|
||||||
Value {
|
span,
|
||||||
value: UntaggedValue::Row(_),
|
}],
|
||||||
..
|
}),
|
||||||
} => {
|
v => Err(ShellError::CantConvert("cell path".into(), v.span())),
|
||||||
let mut shell_error = ShellError::type_error("boolean", v.spanned_type_name());
|
}
|
||||||
shell_error.notes.push(
|
}
|
||||||
"Note: you can access columns using dot. eg) $it.column or (ls).column".into(),
|
}
|
||||||
);
|
|
||||||
Err(shell_error)
|
impl FromValue for bool {
|
||||||
}
|
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||||
v => Err(ShellError::type_error("boolean", v.spanned_type_name())),
|
match v {
|
||||||
|
Value::Bool { val, .. } => Ok(*val),
|
||||||
|
v => Err(ShellError::CantConvert("bool".into(), v.span())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
impl FromValue for Spanned<bool> {
|
impl FromValue for Spanned<bool> {
|
||||||
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
fn from_value(v: &Value) -> Result<Self, ShellError> {
|
||||||
|
|
|
@ -57,6 +57,14 @@ pub enum ParseError {
|
||||||
#[diagnostic(code(nu::parser::expected_keyword), url(docsrs))]
|
#[diagnostic(code(nu::parser::expected_keyword), url(docsrs))]
|
||||||
ExpectedKeyword(String, #[label("expected {0}")] Span),
|
ExpectedKeyword(String, #[label("expected {0}")] Span),
|
||||||
|
|
||||||
|
#[error("Unexpected keyword.")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(nu::parser::unexpected_keyword),
|
||||||
|
url(docsrs),
|
||||||
|
help("'export' keyword is allowed only in a module.")
|
||||||
|
)]
|
||||||
|
UnexpectedKeyword(String, #[label("unexpected {0}")] Span),
|
||||||
|
|
||||||
#[error("Multiple rest params.")]
|
#[error("Multiple rest params.")]
|
||||||
#[diagnostic(code(nu::parser::multiple_rest_params), url(docsrs))]
|
#[diagnostic(code(nu::parser::multiple_rest_params), url(docsrs))]
|
||||||
MultipleRestParams(#[label = "multiple rest params"] Span),
|
MultipleRestParams(#[label = "multiple rest params"] Span),
|
||||||
|
@ -69,6 +77,10 @@ pub enum ParseError {
|
||||||
#[diagnostic(code(nu::parser::module_not_found), url(docsrs))]
|
#[diagnostic(code(nu::parser::module_not_found), url(docsrs))]
|
||||||
ModuleNotFound(#[label = "module not found"] Span),
|
ModuleNotFound(#[label = "module not found"] Span),
|
||||||
|
|
||||||
|
#[error("Duplicate command definition within a block.")]
|
||||||
|
#[diagnostic(code(nu::parser::duplicate_command_def), url(docsrs))]
|
||||||
|
DuplicateCommandDef(#[label = "defined more than once"] Span),
|
||||||
|
|
||||||
#[error("Unknown command.")]
|
#[error("Unknown command.")]
|
||||||
#[diagnostic(
|
#[diagnostic(
|
||||||
code(nu::parser::unknown_command),
|
code(nu::parser::unknown_command),
|
||||||
|
|
|
@ -79,6 +79,16 @@ pub fn flatten_expression(
|
||||||
Expr::Float(_) => {
|
Expr::Float(_) => {
|
||||||
vec![(expr.span, FlatShape::Float)]
|
vec![(expr.span, FlatShape::Float)]
|
||||||
}
|
}
|
||||||
|
Expr::CellPath(cell_path) => {
|
||||||
|
let mut output = vec![];
|
||||||
|
for path_element in &cell_path.members {
|
||||||
|
match path_element {
|
||||||
|
PathMember::String { span, .. } => output.push((*span, FlatShape::String)),
|
||||||
|
PathMember::Int { span, .. } => output.push((*span, FlatShape::Int)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
Expr::FullCellPath(cell_path) => {
|
Expr::FullCellPath(cell_path) => {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
output.extend(flatten_expression(working_set, &cell_path.head));
|
output.extend(flatten_expression(working_set, &cell_path.head));
|
||||||
|
|
|
@ -14,9 +14,16 @@ use crate::{
|
||||||
ParseError,
|
ParseError,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) {
|
pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) -> Option<ParseError> {
|
||||||
let name = working_set.get_span_contents(spans[0]);
|
let name = working_set.get_span_contents(spans[0]);
|
||||||
|
|
||||||
|
// handle "export def" same as "def"
|
||||||
|
let (name, spans) = if name == b"export" && spans.len() >= 2 {
|
||||||
|
(working_set.get_span_contents(spans[1]), &spans[1..])
|
||||||
|
} else {
|
||||||
|
(name, spans)
|
||||||
|
};
|
||||||
|
|
||||||
if name == b"def" && spans.len() >= 4 {
|
if name == b"def" && spans.len() >= 4 {
|
||||||
let (name_expr, ..) = parse_string(working_set, spans[1]);
|
let (name_expr, ..) = parse_string(working_set, spans[1]);
|
||||||
let name = name_expr.as_string();
|
let name = name_expr.as_string();
|
||||||
|
@ -36,9 +43,13 @@ pub fn parse_def_predecl(working_set: &mut StateWorkingSet, spans: &[Span]) {
|
||||||
signature.name = name;
|
signature.name = name;
|
||||||
let decl = signature.predeclare();
|
let decl = signature.predeclare();
|
||||||
|
|
||||||
working_set.add_decl(decl);
|
if working_set.add_predecl(decl).is_some() {
|
||||||
|
return Some(ParseError::DuplicateCommandDef(spans[1]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_def(
|
pub fn parse_def(
|
||||||
|
@ -89,17 +100,22 @@ pub fn parse_def(
|
||||||
call.positional.push(block);
|
call.positional.push(block);
|
||||||
|
|
||||||
if let (Some(name), Some(mut signature), Some(block_id)) =
|
if let (Some(name), Some(mut signature), Some(block_id)) =
|
||||||
(name, signature, block_id)
|
(&name, signature, block_id)
|
||||||
{
|
{
|
||||||
let decl_id = working_set
|
if let Some(decl_id) = working_set.find_decl(name.as_bytes()) {
|
||||||
.find_decl(name.as_bytes())
|
let declaration = working_set.get_decl_mut(decl_id);
|
||||||
.expect("internal error: predeclaration failed to add definition");
|
|
||||||
|
|
||||||
let declaration = working_set.get_decl_mut(decl_id);
|
signature.name = name.clone();
|
||||||
|
|
||||||
signature.name = name;
|
*declaration = signature.into_block_command(block_id);
|
||||||
|
} else {
|
||||||
*declaration = signature.into_block_command(block_id);
|
error = error.or_else(|| {
|
||||||
|
Some(ParseError::UnknownState(
|
||||||
|
"Could not define hidden command".into(),
|
||||||
|
spans[1],
|
||||||
|
))
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let err_span = Span {
|
let err_span = Span {
|
||||||
|
@ -112,6 +128,19 @@ pub fn parse_def(
|
||||||
}
|
}
|
||||||
working_set.exit_scope();
|
working_set.exit_scope();
|
||||||
|
|
||||||
|
if let Some(name) = name {
|
||||||
|
// It's OK if it returns None: The decl was already merged in previous parse
|
||||||
|
// pass.
|
||||||
|
working_set.merge_predecl(name.as_bytes());
|
||||||
|
} else {
|
||||||
|
error = error.or_else(|| {
|
||||||
|
Some(ParseError::UnknownState(
|
||||||
|
"Could not get string from string expression".into(),
|
||||||
|
*name_span,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
call
|
call
|
||||||
} else {
|
} else {
|
||||||
let err_span = Span {
|
let err_span = Span {
|
||||||
|
@ -219,6 +248,71 @@ pub fn parse_alias(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_export(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
spans: &[Span],
|
||||||
|
) -> (Statement, Option<ParseError>) {
|
||||||
|
let bytes = working_set.get_span_contents(spans[0]);
|
||||||
|
|
||||||
|
if bytes == b"export" && spans.len() >= 3 {
|
||||||
|
let export_name = working_set.get_span_contents(spans[1]);
|
||||||
|
|
||||||
|
match export_name {
|
||||||
|
b"def" => {
|
||||||
|
let (stmt, err) = parse_def(working_set, &spans[1..]);
|
||||||
|
|
||||||
|
let export_def_decl_id = working_set
|
||||||
|
.find_decl(b"export def")
|
||||||
|
.expect("internal error: missing 'export def' command");
|
||||||
|
|
||||||
|
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
|
||||||
|
let stmt = if let Statement::Pipeline(ref pipe) = stmt {
|
||||||
|
if !pipe.expressions.is_empty() {
|
||||||
|
if let Expr::Call(ref call) = pipe.expressions[0].expr {
|
||||||
|
let mut call = call.clone();
|
||||||
|
|
||||||
|
call.head = span(&spans[0..=1]);
|
||||||
|
call.decl_id = export_def_decl_id;
|
||||||
|
|
||||||
|
Statement::Pipeline(Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: span(spans),
|
||||||
|
ty: Type::Unknown,
|
||||||
|
custom_completion: None,
|
||||||
|
}]))
|
||||||
|
} else {
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stmt
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stmt
|
||||||
|
};
|
||||||
|
|
||||||
|
(stmt, err)
|
||||||
|
}
|
||||||
|
_ => (
|
||||||
|
garbage_statement(spans),
|
||||||
|
Some(ParseError::Expected(
|
||||||
|
// TODO: Fill in more as they come
|
||||||
|
"def keyword".into(),
|
||||||
|
spans[1],
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
garbage_statement(spans),
|
||||||
|
Some(ParseError::UnknownState(
|
||||||
|
// TODO: fill in more as they come
|
||||||
|
"Expected structure: export def [] {}".into(),
|
||||||
|
span(spans),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_module(
|
pub fn parse_module(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
spans: &[Span],
|
||||||
|
@ -308,15 +402,21 @@ pub fn parse_module(
|
||||||
b"def" => {
|
b"def" => {
|
||||||
let (stmt, err) = parse_def(working_set, &pipeline.commands[0].parts);
|
let (stmt, err) = parse_def(working_set, &pipeline.commands[0].parts);
|
||||||
|
|
||||||
|
(stmt, err)
|
||||||
|
}
|
||||||
|
b"export" => {
|
||||||
|
let (stmt, err) =
|
||||||
|
parse_export(working_set, &pipeline.commands[0].parts);
|
||||||
|
|
||||||
if err.is_none() {
|
if err.is_none() {
|
||||||
let decl_name =
|
let decl_name =
|
||||||
working_set.get_span_contents(pipeline.commands[0].parts[1]);
|
// parts[2] is safe since it's checked in parse_def already
|
||||||
|
working_set.get_span_contents(pipeline.commands[0].parts[2]);
|
||||||
|
|
||||||
let decl_id = working_set
|
let decl_id = working_set
|
||||||
.find_decl(decl_name)
|
.find_decl(decl_name)
|
||||||
.expect("internal error: failed to find added declaration");
|
.expect("internal error: failed to find added declaration");
|
||||||
|
|
||||||
// TODO: Later, we want to put this behind 'export'
|
|
||||||
exports.push((decl_name.into(), decl_id));
|
exports.push((decl_name.into(), decl_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,7 +425,8 @@ pub fn parse_module(
|
||||||
_ => (
|
_ => (
|
||||||
garbage_statement(&pipeline.commands[0].parts),
|
garbage_statement(&pipeline.commands[0].parts),
|
||||||
Some(ParseError::Expected(
|
Some(ParseError::Expected(
|
||||||
"def".into(),
|
// TODO: Fill in more as they com
|
||||||
|
"def or export keyword".into(),
|
||||||
pipeline.commands[0].parts[0],
|
pipeline.commands[0].parts[0],
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
@ -394,8 +495,6 @@ pub fn parse_use(
|
||||||
let mut error = None;
|
let mut error = None;
|
||||||
let bytes = working_set.get_span_contents(spans[0]);
|
let bytes = working_set.get_span_contents(spans[0]);
|
||||||
|
|
||||||
// TODO: Currently, this directly imports the module's definitions into the current scope.
|
|
||||||
// Later, we want to put them behind the module's name and add selective importing
|
|
||||||
if bytes == b"use" && spans.len() >= 2 {
|
if bytes == b"use" && spans.len() >= 2 {
|
||||||
let (module_name_expr, err) = parse_string(working_set, spans[1]);
|
let (module_name_expr, err) = parse_string(working_set, spans[1]);
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
@ -404,8 +503,6 @@ pub fn parse_use(
|
||||||
error = error.or(err);
|
error = error.or(err);
|
||||||
|
|
||||||
let exports = if let Some(block_id) = working_set.find_module(&import_pattern.head) {
|
let exports = if let Some(block_id) = working_set.find_module(&import_pattern.head) {
|
||||||
// TODO: Since we don't use the Block at all, we might just as well create a separate
|
|
||||||
// Module that holds only the exports, without having Blocks in the way.
|
|
||||||
working_set.get_block(block_id).exports.clone()
|
working_set.get_block(block_id).exports.clone()
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
@ -493,6 +590,57 @@ pub fn parse_use(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_hide(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
spans: &[Span],
|
||||||
|
) -> (Statement, Option<ParseError>) {
|
||||||
|
let mut error = None;
|
||||||
|
let bytes = working_set.get_span_contents(spans[0]);
|
||||||
|
|
||||||
|
if bytes == b"hide" && spans.len() >= 2 {
|
||||||
|
let (name_expr, err) = parse_string(working_set, spans[1]);
|
||||||
|
error = error.or(err);
|
||||||
|
|
||||||
|
let name_bytes: Vec<u8> = working_set.get_span_contents(spans[1]).into();
|
||||||
|
|
||||||
|
// TODO: Do the import pattern stuff for bulk-hiding
|
||||||
|
|
||||||
|
if working_set.hide_decl(&name_bytes).is_none() {
|
||||||
|
error = error.or_else(|| Some(ParseError::UnknownCommand(spans[1])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the Hide command call
|
||||||
|
let hide_decl_id = working_set
|
||||||
|
.find_decl(b"hide")
|
||||||
|
.expect("internal error: missing hide command");
|
||||||
|
|
||||||
|
let call = Box::new(Call {
|
||||||
|
head: spans[0],
|
||||||
|
decl_id: hide_decl_id,
|
||||||
|
positional: vec![name_expr],
|
||||||
|
named: vec![],
|
||||||
|
});
|
||||||
|
|
||||||
|
(
|
||||||
|
Statement::Pipeline(Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: span(spans),
|
||||||
|
ty: Type::Unknown,
|
||||||
|
custom_completion: None,
|
||||||
|
}])),
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
garbage_statement(spans),
|
||||||
|
Some(ParseError::UnknownState(
|
||||||
|
"Expected structure: hide <name>".into(),
|
||||||
|
span(spans),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_let(
|
pub fn parse_let(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
spans: &[Span],
|
spans: &[Span],
|
||||||
|
|
|
@ -7,15 +7,15 @@ use crate::{
|
||||||
|
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::{
|
ast::{
|
||||||
Block, Call, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember, Operator,
|
Block, Call, CellPath, Expr, Expression, FullCellPath, ImportPattern, ImportPatternMember,
|
||||||
PathMember, Pipeline, RangeInclusion, RangeOperator, Statement,
|
Operator, PathMember, Pipeline, RangeInclusion, RangeOperator, Statement,
|
||||||
},
|
},
|
||||||
engine::StateWorkingSet,
|
engine::StateWorkingSet,
|
||||||
span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId,
|
span, Flag, PositionalArg, Signature, Span, SyntaxShape, Type, VarId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::parse_keywords::{
|
use crate::parse_keywords::{
|
||||||
parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use,
|
parse_alias, parse_def, parse_def_predecl, parse_hide, parse_let, parse_module, parse_use,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -1158,6 +1158,62 @@ pub fn parse_variable_expr(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_cell_path(
|
||||||
|
working_set: &mut StateWorkingSet,
|
||||||
|
tokens: impl Iterator<Item = Token>,
|
||||||
|
mut expect_dot: bool,
|
||||||
|
span: Span,
|
||||||
|
) -> (Vec<PathMember>, Option<ParseError>) {
|
||||||
|
let mut error = None;
|
||||||
|
let mut tail = vec![];
|
||||||
|
|
||||||
|
for path_element in tokens {
|
||||||
|
let bytes = working_set.get_span_contents(path_element.span);
|
||||||
|
|
||||||
|
if expect_dot {
|
||||||
|
expect_dot = false;
|
||||||
|
if bytes.len() != 1 || bytes[0] != b'.' {
|
||||||
|
error = error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
expect_dot = true;
|
||||||
|
|
||||||
|
match parse_int(bytes, path_element.span) {
|
||||||
|
(
|
||||||
|
Expression {
|
||||||
|
expr: Expr::Int(val),
|
||||||
|
span,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
) => tail.push(PathMember::Int {
|
||||||
|
val: val as usize,
|
||||||
|
span,
|
||||||
|
}),
|
||||||
|
_ => {
|
||||||
|
let (result, err) = parse_string(working_set, path_element.span);
|
||||||
|
error = error.or(err);
|
||||||
|
match result {
|
||||||
|
Expression {
|
||||||
|
expr: Expr::String(string),
|
||||||
|
span,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
tail.push(PathMember::String { val: string, span });
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error =
|
||||||
|
error.or_else(|| Some(ParseError::Expected("string".into(), span)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(tail, error)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_full_cell_path(
|
pub fn parse_full_cell_path(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
implicit_head: Option<VarId>,
|
implicit_head: Option<VarId>,
|
||||||
|
@ -1174,7 +1230,7 @@ pub fn parse_full_cell_path(
|
||||||
let mut tokens = tokens.into_iter().peekable();
|
let mut tokens = tokens.into_iter().peekable();
|
||||||
if let Some(head) = tokens.peek() {
|
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, mut expect_dot) = if bytes.starts_with(b"(") {
|
let (head, 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;
|
||||||
|
|
||||||
|
@ -1248,52 +1304,8 @@ pub fn parse_full_cell_path(
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut tail = vec![];
|
let (tail, err) = parse_cell_path(working_set, tokens, expect_dot, span);
|
||||||
|
error = error.or(err);
|
||||||
for path_element in tokens {
|
|
||||||
let bytes = working_set.get_span_contents(path_element.span);
|
|
||||||
|
|
||||||
if expect_dot {
|
|
||||||
expect_dot = false;
|
|
||||||
if bytes.len() != 1 || bytes[0] != b'.' {
|
|
||||||
error =
|
|
||||||
error.or_else(|| Some(ParseError::Expected('.'.into(), path_element.span)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
expect_dot = true;
|
|
||||||
|
|
||||||
match parse_int(bytes, path_element.span) {
|
|
||||||
(
|
|
||||||
Expression {
|
|
||||||
expr: Expr::Int(val),
|
|
||||||
span,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
) => tail.push(PathMember::Int {
|
|
||||||
val: val as usize,
|
|
||||||
span,
|
|
||||||
}),
|
|
||||||
_ => {
|
|
||||||
let (result, err) = parse_string(working_set, path_element.span);
|
|
||||||
error = error.or(err);
|
|
||||||
match result {
|
|
||||||
Expression {
|
|
||||||
expr: Expr::String(string),
|
|
||||||
span,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
tail.push(PathMember::String { val: string, span });
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error = error
|
|
||||||
.or_else(|| Some(ParseError::Expected("string".into(), span)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Expression {
|
Expression {
|
||||||
|
@ -2352,6 +2364,28 @@ pub fn parse_value(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SyntaxShape::CellPath => {
|
||||||
|
let source = working_set.get_span_contents(span);
|
||||||
|
let mut error = None;
|
||||||
|
|
||||||
|
let (tokens, err) = lex(source, span.start, &[b'\n'], &[b'.']);
|
||||||
|
error = error.or(err);
|
||||||
|
|
||||||
|
let tokens = tokens.into_iter().peekable();
|
||||||
|
|
||||||
|
let (cell_path, err) = parse_cell_path(working_set, tokens, false, span);
|
||||||
|
error = error.or(err);
|
||||||
|
|
||||||
|
(
|
||||||
|
Expression {
|
||||||
|
expr: Expr::CellPath(CellPath { members: cell_path }),
|
||||||
|
span,
|
||||||
|
ty: Type::CellPath,
|
||||||
|
custom_completion: None,
|
||||||
|
},
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
}
|
||||||
SyntaxShape::Any => {
|
SyntaxShape::Any => {
|
||||||
if bytes.starts_with(b"[") {
|
if bytes.starts_with(b"[") {
|
||||||
parse_value(working_set, span, &SyntaxShape::Table)
|
parse_value(working_set, span, &SyntaxShape::Table)
|
||||||
|
@ -2587,6 +2621,11 @@ pub fn parse_statement(
|
||||||
b"module" => parse_module(working_set, spans),
|
b"module" => parse_module(working_set, spans),
|
||||||
b"use" => parse_use(working_set, spans),
|
b"use" => parse_use(working_set, spans),
|
||||||
b"source" => parse_source(working_set, spans),
|
b"source" => parse_source(working_set, spans),
|
||||||
|
b"export" => (
|
||||||
|
garbage_statement(spans),
|
||||||
|
Some(ParseError::UnexpectedKeyword("export".into(), spans[0])),
|
||||||
|
),
|
||||||
|
b"hide" => parse_hide(working_set, spans),
|
||||||
_ => {
|
_ => {
|
||||||
let (expr, err) = parse_expression(working_set, spans);
|
let (expr, err) = parse_expression(working_set, spans);
|
||||||
(Statement::Pipeline(Pipeline::from_vec(vec![expr])), err)
|
(Statement::Pipeline(Pipeline::from_vec(vec![expr])), err)
|
||||||
|
@ -2603,16 +2642,18 @@ pub fn parse_block(
|
||||||
working_set.enter_scope();
|
working_set.enter_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut error = None;
|
||||||
|
|
||||||
// Pre-declare any definition so that definitions
|
// Pre-declare any definition so that definitions
|
||||||
// that share the same block can see each other
|
// that share the same block can see each other
|
||||||
for pipeline in &lite_block.block {
|
for pipeline in &lite_block.block {
|
||||||
if pipeline.commands.len() == 1 {
|
if pipeline.commands.len() == 1 {
|
||||||
parse_def_predecl(working_set, &pipeline.commands[0].parts);
|
if let Some(err) = parse_def_predecl(working_set, &pipeline.commands[0].parts) {
|
||||||
|
error = error.or(Some(err));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut error = None;
|
|
||||||
|
|
||||||
let block: Block = lite_block
|
let block: Block = lite_block
|
||||||
.block
|
.block
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -8,4 +8,4 @@ edition = "2018"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.29"
|
thiserror = "1.0.29"
|
||||||
miette = "3.0.0"
|
miette = "3.0.0"
|
||||||
serde = "1.0.130"
|
serde = {version = "1.0.130", features = ["derive"]}
|
||||||
|
|
|
@ -45,4 +45,8 @@ impl Call {
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nth(&self, pos: usize) -> Option<Expression> {
|
||||||
|
self.positional.get(pos).cloned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,36 @@
|
||||||
use super::Expression;
|
use super::Expression;
|
||||||
use crate::Span;
|
use crate::Span;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub enum PathMember {
|
pub enum PathMember {
|
||||||
String { val: String, span: Span },
|
String { val: String, span: Span },
|
||||||
Int { val: usize, span: Span },
|
Int { val: usize, span: Span },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct CellPath {
|
pub struct CellPath {
|
||||||
pub members: Vec<PathMember>,
|
pub members: Vec<PathMember>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CellPath {
|
||||||
|
pub fn into_string(&self) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
|
||||||
|
for (idx, elem) in self.members.iter().enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
output.push('.');
|
||||||
|
}
|
||||||
|
match elem {
|
||||||
|
PathMember::Int { val, .. } => output.push_str(&format!("{}", val)),
|
||||||
|
PathMember::String { val, .. } => output.push_str(val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FullCellPath {
|
pub struct FullCellPath {
|
||||||
pub head: Expression,
|
pub head: Expression,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{Call, Expression, FullCellPath, Operator, RangeOperator};
|
use super::{Call, CellPath, Expression, FullCellPath, Operator, RangeOperator};
|
||||||
use crate::{BlockId, Signature, Span, VarId};
|
use crate::{BlockId, Signature, Span, VarId};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -24,6 +24,7 @@ pub enum Expr {
|
||||||
Table(Vec<Expression>, Vec<Vec<Expression>>),
|
Table(Vec<Expression>, Vec<Vec<Expression>>),
|
||||||
Keyword(Vec<u8>, Span, Box<Expression>),
|
Keyword(Vec<u8>, Span, Box<Expression>),
|
||||||
String(String), // FIXME: improve this in the future?
|
String(String), // FIXME: improve this in the future?
|
||||||
|
CellPath(CellPath),
|
||||||
FullCellPath(Box<FullCellPath>),
|
FullCellPath(Box<FullCellPath>),
|
||||||
Signature(Box<Signature>),
|
Signature(Box<Signature>),
|
||||||
Garbage,
|
Garbage,
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use super::Command;
|
use super::Command;
|
||||||
use crate::{ast::Block, BlockId, DeclId, Signature, Span, Type, VarId};
|
use crate::{ast::Block, BlockId, DeclId, Signature, Span, Type, VarId};
|
||||||
use core::panic;
|
use core::panic;
|
||||||
use std::{collections::HashMap, slice::Iter};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
slice::Iter,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct EngineState {
|
pub struct EngineState {
|
||||||
files: Vec<(String, usize, usize)>,
|
files: Vec<(String, usize, usize)>,
|
||||||
|
@ -18,6 +21,7 @@ pub struct ScopeFrame {
|
||||||
decls: HashMap<Vec<u8>, DeclId>,
|
decls: HashMap<Vec<u8>, DeclId>,
|
||||||
aliases: HashMap<Vec<u8>, Vec<Span>>,
|
aliases: HashMap<Vec<u8>, Vec<Span>>,
|
||||||
modules: HashMap<Vec<u8>, BlockId>,
|
modules: HashMap<Vec<u8>, BlockId>,
|
||||||
|
hiding: HashSet<DeclId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScopeFrame {
|
impl ScopeFrame {
|
||||||
|
@ -27,6 +31,7 @@ impl ScopeFrame {
|
||||||
decls: HashMap::new(),
|
decls: HashMap::new(),
|
||||||
aliases: HashMap::new(),
|
aliases: HashMap::new(),
|
||||||
modules: HashMap::new(),
|
modules: HashMap::new(),
|
||||||
|
hiding: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,6 +86,9 @@ impl EngineState {
|
||||||
for item in first.modules.into_iter() {
|
for item in first.modules.into_iter() {
|
||||||
last.modules.insert(item.0, item.1);
|
last.modules.insert(item.0, item.1);
|
||||||
}
|
}
|
||||||
|
for item in first.hiding.into_iter() {
|
||||||
|
last.hiding.insert(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +132,15 @@ impl EngineState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> {
|
pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> {
|
||||||
|
let mut hiding: HashSet<DeclId> = HashSet::new();
|
||||||
|
|
||||||
for scope in self.scope.iter().rev() {
|
for scope in self.scope.iter().rev() {
|
||||||
|
hiding.extend(&scope.hiding);
|
||||||
|
|
||||||
if let Some(decl_id) = scope.decls.get(name) {
|
if let Some(decl_id) = scope.decls.get(name) {
|
||||||
return Some(*decl_id);
|
if !hiding.contains(decl_id) {
|
||||||
|
return Some(*decl_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,9 +252,10 @@ pub struct StateWorkingSet<'a> {
|
||||||
pub struct StateDelta {
|
pub struct StateDelta {
|
||||||
files: Vec<(String, usize, usize)>,
|
files: Vec<(String, usize, usize)>,
|
||||||
pub(crate) file_contents: Vec<u8>,
|
pub(crate) file_contents: Vec<u8>,
|
||||||
vars: Vec<Type>, // indexed by VarId
|
vars: Vec<Type>, // indexed by VarId
|
||||||
decls: Vec<Box<dyn Command>>, // indexed by DeclId
|
decls: Vec<Box<dyn Command>>, // indexed by DeclId
|
||||||
blocks: Vec<Block>, // indexed by BlockId
|
blocks: Vec<Block>, // indexed by BlockId
|
||||||
|
predecls: HashMap<Vec<u8>, DeclId>, // this should get erased after every def call
|
||||||
pub scope: Vec<ScopeFrame>,
|
pub scope: Vec<ScopeFrame>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -274,6 +289,7 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
file_contents: vec![],
|
file_contents: vec![],
|
||||||
vars: vec![],
|
vars: vec![],
|
||||||
decls: vec![],
|
decls: vec![],
|
||||||
|
predecls: HashMap::new(),
|
||||||
blocks: vec![],
|
blocks: vec![],
|
||||||
scope: vec![ScopeFrame::new()],
|
scope: vec![ScopeFrame::new()],
|
||||||
},
|
},
|
||||||
|
@ -304,11 +320,71 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
.scope
|
.scope
|
||||||
.last_mut()
|
.last_mut()
|
||||||
.expect("internal error: missing required scope frame");
|
.expect("internal error: missing required scope frame");
|
||||||
|
|
||||||
scope_frame.decls.insert(name, decl_id);
|
scope_frame.decls.insert(name, decl_id);
|
||||||
|
|
||||||
decl_id
|
decl_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_predecl(&mut self, decl: Box<dyn Command>) -> Option<DeclId> {
|
||||||
|
let name = decl.name().as_bytes().to_vec();
|
||||||
|
|
||||||
|
self.delta.decls.push(decl);
|
||||||
|
let decl_id = self.num_decls() - 1;
|
||||||
|
|
||||||
|
self.delta.predecls.insert(name, decl_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_predecl(&mut self, name: &[u8]) -> Option<DeclId> {
|
||||||
|
if let Some(decl_id) = self.delta.predecls.remove(name) {
|
||||||
|
let scope_frame = self
|
||||||
|
.delta
|
||||||
|
.scope
|
||||||
|
.last_mut()
|
||||||
|
.expect("internal error: missing required scope frame");
|
||||||
|
|
||||||
|
scope_frame.decls.insert(name.into(), decl_id);
|
||||||
|
|
||||||
|
return Some(decl_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hide_decl(&mut self, name: &[u8]) -> Option<DeclId> {
|
||||||
|
let mut hiding: HashSet<DeclId> = HashSet::new();
|
||||||
|
|
||||||
|
// Since we can mutate scope frames in delta, remove the id directly
|
||||||
|
for scope in self.delta.scope.iter_mut().rev() {
|
||||||
|
hiding.extend(&scope.hiding);
|
||||||
|
|
||||||
|
if let Some(decl_id) = scope.decls.remove(name) {
|
||||||
|
return Some(decl_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We cannot mutate the permanent state => store the information in the current scope frame
|
||||||
|
let last_scope_frame = self
|
||||||
|
.delta
|
||||||
|
.scope
|
||||||
|
.last_mut()
|
||||||
|
.expect("internal error: missing required scope frame");
|
||||||
|
|
||||||
|
for scope in self.permanent_state.scope.iter().rev() {
|
||||||
|
hiding.extend(&scope.hiding);
|
||||||
|
|
||||||
|
if let Some(decl_id) = scope.decls.get(name) {
|
||||||
|
if !hiding.contains(decl_id) {
|
||||||
|
// Do not hide already hidden decl
|
||||||
|
last_scope_frame.hiding.insert(*decl_id);
|
||||||
|
return Some(*decl_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
pub fn add_block(&mut self, block: Block) -> BlockId {
|
pub fn add_block(&mut self, block: Block) -> BlockId {
|
||||||
self.delta.blocks.push(block);
|
self.delta.blocks.push(block);
|
||||||
|
|
||||||
|
@ -416,15 +492,27 @@ impl<'a> StateWorkingSet<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> {
|
pub fn find_decl(&self, name: &[u8]) -> Option<DeclId> {
|
||||||
|
let mut hiding: HashSet<DeclId> = HashSet::new();
|
||||||
|
|
||||||
|
if let Some(decl_id) = self.delta.predecls.get(name) {
|
||||||
|
return Some(*decl_id);
|
||||||
|
}
|
||||||
|
|
||||||
for scope in self.delta.scope.iter().rev() {
|
for scope in self.delta.scope.iter().rev() {
|
||||||
|
hiding.extend(&scope.hiding);
|
||||||
|
|
||||||
if let Some(decl_id) = scope.decls.get(name) {
|
if let Some(decl_id) = scope.decls.get(name) {
|
||||||
return Some(*decl_id);
|
return Some(*decl_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for scope in self.permanent_state.scope.iter().rev() {
|
for scope in self.permanent_state.scope.iter().rev() {
|
||||||
|
hiding.extend(&scope.hiding);
|
||||||
|
|
||||||
if let Some(decl_id) = scope.decls.get(name) {
|
if let Some(decl_id) = scope.decls.get(name) {
|
||||||
return Some(*decl_id);
|
if !hiding.contains(decl_id) {
|
||||||
|
return Some(*decl_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,10 @@ impl Stack {
|
||||||
self.0.borrow().env_vars.clone()
|
self.0.borrow().env_vars.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_env_var(&self, name: &str) -> Option<String> {
|
||||||
|
self.0.borrow().env_vars.get(name).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print_stack(&self) {
|
pub fn print_stack(&self) {
|
||||||
println!("===frame===");
|
println!("===frame===");
|
||||||
println!("vars:");
|
println!("vars:");
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl SyntaxShape {
|
||||||
SyntaxShape::Custom(custom, _) => custom.to_type(),
|
SyntaxShape::Custom(custom, _) => custom.to_type(),
|
||||||
SyntaxShape::Duration => Type::Duration,
|
SyntaxShape::Duration => Type::Duration,
|
||||||
SyntaxShape::Expression => Type::Unknown,
|
SyntaxShape::Expression => Type::Unknown,
|
||||||
SyntaxShape::FilePath => Type::FilePath,
|
SyntaxShape::FilePath => Type::String,
|
||||||
SyntaxShape::Filesize => Type::Filesize,
|
SyntaxShape::Filesize => Type::Filesize,
|
||||||
SyntaxShape::FullCellPath => Type::Unknown,
|
SyntaxShape::FullCellPath => Type::Unknown,
|
||||||
SyntaxShape::GlobPattern => Type::String,
|
SyntaxShape::GlobPattern => Type::String,
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub use stream::*;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::ast::PathMember;
|
use crate::ast::{CellPath, PathMember};
|
||||||
use crate::{span, BlockId, Span, Type};
|
use crate::{span, BlockId, Span, Type};
|
||||||
|
|
||||||
use crate::ShellError;
|
use crate::ShellError;
|
||||||
|
@ -72,6 +72,10 @@ pub enum Value {
|
||||||
val: Vec<u8>,
|
val: Vec<u8>,
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
CellPath {
|
||||||
|
val: CellPath,
|
||||||
|
span: Span,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
|
@ -99,6 +103,7 @@ impl Value {
|
||||||
Value::Stream { span, .. } => *span,
|
Value::Stream { span, .. } => *span,
|
||||||
Value::Nothing { span, .. } => *span,
|
Value::Nothing { span, .. } => *span,
|
||||||
Value::Binary { span, .. } => *span,
|
Value::Binary { span, .. } => *span,
|
||||||
|
Value::CellPath { span, .. } => *span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +124,7 @@ impl Value {
|
||||||
Value::Nothing { span, .. } => *span = new_span,
|
Value::Nothing { span, .. } => *span = new_span,
|
||||||
Value::Error { .. } => {}
|
Value::Error { .. } => {}
|
||||||
Value::Binary { span, .. } => *span = new_span,
|
Value::Binary { span, .. } => *span = new_span,
|
||||||
|
Value::CellPath { span, .. } => *span = new_span,
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
|
@ -143,6 +149,7 @@ impl Value {
|
||||||
Value::Stream { .. } => Type::ValueStream,
|
Value::Stream { .. } => Type::ValueStream,
|
||||||
Value::Error { .. } => Type::Error,
|
Value::Error { .. } => Type::Error,
|
||||||
Value::Binary { .. } => Type::Binary,
|
Value::Binary { .. } => Type::Binary,
|
||||||
|
Value::CellPath { .. } => Type::CellPath,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +191,7 @@ impl Value {
|
||||||
Value::Nothing { .. } => String::new(),
|
Value::Nothing { .. } => String::new(),
|
||||||
Value::Error { error } => format!("{:?}", error),
|
Value::Error { error } => format!("{:?}", error),
|
||||||
Value::Binary { val, .. } => format!("{:?}", val),
|
Value::Binary { val, .. } => format!("{:?}", val),
|
||||||
|
Value::CellPath { val, .. } => val.into_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,6 +223,7 @@ impl Value {
|
||||||
Value::Nothing { .. } => String::new(),
|
Value::Nothing { .. } => String::new(),
|
||||||
Value::Error { error } => format!("{:?}", error),
|
Value::Error { error } => format!("{:?}", error),
|
||||||
Value::Binary { val, .. } => format!("{:?}", val),
|
Value::Binary { val, .. } => format!("{:?}", val),
|
||||||
|
Value::CellPath { .. } => self.into_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -240,6 +240,10 @@ impl TextStyle {
|
||||||
.bold(Some(true))
|
.bold(Some(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_field() -> TextStyle {
|
||||||
|
TextStyle::new().fg(Color::Green).bold(Some(true))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_attributes(bo: bool, al: Alignment, co: Color) -> TextStyle {
|
pub fn with_attributes(bo: bool, al: Alignment, co: Color) -> TextStyle {
|
||||||
TextStyle::new().alignment(al).fg(co).bold(Some(bo))
|
TextStyle::new().alignment(al).fg(co).bold(Some(bo))
|
||||||
}
|
}
|
||||||
|
|
85
src/main.rs
85
src/main.rs
|
@ -1,20 +1,23 @@
|
||||||
use std::io::Write;
|
use std::{cell::RefCell, io::Write, rc::Rc};
|
||||||
|
|
||||||
use miette::{IntoDiagnostic, Result};
|
use miette::{IntoDiagnostic, Result};
|
||||||
use nu_cli::{report_error, NuCompleter, NuHighlighter, NuValidator};
|
use nu_cli::{report_error, NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
|
||||||
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;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Call,
|
ast::Call,
|
||||||
engine::{EngineState, EvaluationContext, StateWorkingSet},
|
engine::{EngineState, EvaluationContext, Stack, StateWorkingSet},
|
||||||
ShellError, Value,
|
ShellError, Value,
|
||||||
};
|
};
|
||||||
use reedline::DefaultCompletionActionHandler;
|
use reedline::{DefaultPrompt, Prompt};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
// Name of environment variable where the prompt could be stored
|
||||||
|
const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
miette::set_panic_hook();
|
miette::set_panic_hook();
|
||||||
let miette_hook = std::panic::take_hook();
|
let miette_hook = std::panic::take_hook();
|
||||||
|
@ -63,7 +66,7 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
use reedline::{DefaultPrompt, FileBackedHistory, Reedline, Signal};
|
use reedline::{DefaultCompletionActionHandler, FileBackedHistory, Reedline, Signal};
|
||||||
|
|
||||||
let completer = NuCompleter::new(engine_state.clone());
|
let completer = NuCompleter::new(engine_state.clone());
|
||||||
let mut entry_num = 0;
|
let mut entry_num = 0;
|
||||||
|
@ -84,13 +87,22 @@ fn main() -> Result<()> {
|
||||||
engine_state: engine_state.clone(),
|
engine_state: engine_state.clone(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let prompt = DefaultPrompt::new(1);
|
let default_prompt = DefaultPrompt::new(1);
|
||||||
|
let mut nu_prompt = NushellPrompt::new();
|
||||||
let stack = nu_protocol::engine::Stack::new();
|
let stack = nu_protocol::engine::Stack::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
let prompt = update_prompt(
|
||||||
|
PROMPT_COMMAND,
|
||||||
|
engine_state.clone(),
|
||||||
|
&stack,
|
||||||
|
&mut nu_prompt,
|
||||||
|
&default_prompt,
|
||||||
|
);
|
||||||
|
|
||||||
entry_num += 1;
|
entry_num += 1;
|
||||||
|
|
||||||
let input = line_editor.read_line(&prompt);
|
let input = line_editor.read_line(prompt);
|
||||||
match input {
|
match input {
|
||||||
Ok(Signal::Success(s)) => {
|
Ok(Signal::Success(s)) => {
|
||||||
if s.trim() == "exit" {
|
if s.trim() == "exit" {
|
||||||
|
@ -189,3 +201,62 @@ fn print_value(value: Value, state: &EvaluationContext) -> Result<(), ShellError
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_prompt<'prompt>(
|
||||||
|
env_variable: &str,
|
||||||
|
engine_state: Rc<RefCell<EngineState>>,
|
||||||
|
stack: &Stack,
|
||||||
|
nu_prompt: &'prompt mut NushellPrompt,
|
||||||
|
default_prompt: &'prompt DefaultPrompt,
|
||||||
|
) -> &'prompt dyn Prompt {
|
||||||
|
let prompt_command = match stack.get_env_var(env_variable) {
|
||||||
|
Some(prompt) => prompt,
|
||||||
|
None => return default_prompt as &dyn Prompt,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Checking if the PROMPT_COMMAND is the same to avoid evaluating constantly
|
||||||
|
// the same command, thus saturating the contents in the EngineState
|
||||||
|
if !nu_prompt.is_new_prompt(prompt_command.as_str()) {
|
||||||
|
return nu_prompt as &dyn Prompt;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (block, delta) = {
|
||||||
|
let ref_engine_state = engine_state.borrow();
|
||||||
|
let mut working_set = StateWorkingSet::new(&ref_engine_state);
|
||||||
|
let (output, err) = parse(&mut working_set, None, prompt_command.as_bytes(), false);
|
||||||
|
if let Some(err) = err {
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
return default_prompt as &dyn Prompt;
|
||||||
|
}
|
||||||
|
(output, working_set.render())
|
||||||
|
};
|
||||||
|
|
||||||
|
EngineState::merge_delta(&mut *engine_state.borrow_mut(), delta);
|
||||||
|
|
||||||
|
let state = nu_protocol::engine::EvaluationContext {
|
||||||
|
engine_state: engine_state.clone(),
|
||||||
|
stack: stack.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let evaluated_prompt = match eval_block(&state, &block, Value::nothing()) {
|
||||||
|
Ok(value) => match value.as_string() {
|
||||||
|
Ok(prompt) => prompt,
|
||||||
|
Err(err) => {
|
||||||
|
let engine_state = engine_state.borrow();
|
||||||
|
let working_set = StateWorkingSet::new(&*engine_state);
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
return default_prompt as &dyn Prompt;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
let engine_state = engine_state.borrow();
|
||||||
|
let working_set = StateWorkingSet::new(&*engine_state);
|
||||||
|
report_error(&working_set, &err);
|
||||||
|
return default_prompt as &dyn Prompt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nu_prompt.update_prompt(prompt_command, evaluated_prompt);
|
||||||
|
|
||||||
|
nu_prompt as &dyn Prompt
|
||||||
|
}
|
||||||
|
|
105
src/tests.rs
105
src/tests.rs
|
@ -346,7 +346,7 @@ fn better_block_types() -> TestResult {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_imports_1() -> TestResult {
|
fn module_imports_1() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo; foo.a"#,
|
r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo; foo.a"#,
|
||||||
"1",
|
"1",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -354,7 +354,7 @@ fn module_imports_1() -> TestResult {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_imports_2() -> TestResult {
|
fn module_imports_2() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo.a; a"#,
|
r#"module foo { export def a [] { 1 }; def b [] { 2 } }; use foo.a; a"#,
|
||||||
"1",
|
"1",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -362,7 +362,7 @@ fn module_imports_2() -> TestResult {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_imports_3() -> TestResult {
|
fn module_imports_3() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo.*; b"#,
|
r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo.*; b"#,
|
||||||
"2",
|
"2",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -370,7 +370,7 @@ fn module_imports_3() -> TestResult {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_imports_4() -> TestResult {
|
fn module_imports_4() -> TestResult {
|
||||||
fail_test(
|
fail_test(
|
||||||
r#"module foo { def a [] { 1 }; def b [] { 2 } }; use foo.c"#,
|
r#"module foo { export def a [] { 1 }; export def b [] { 2 } }; use foo.c"#,
|
||||||
"not find import",
|
"not find import",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -378,11 +378,77 @@ fn module_imports_4() -> TestResult {
|
||||||
#[test]
|
#[test]
|
||||||
fn module_imports_5() -> TestResult {
|
fn module_imports_5() -> TestResult {
|
||||||
run_test(
|
run_test(
|
||||||
r#"module foo { def a [] { 1 }; def b [] { 2 }; def c [] { 3 } }; use foo.[a, c]; c"#,
|
r#"module foo { export def a [] { 1 }; def b [] { 2 }; export def c [] { 3 } }; use foo.[a, c]; c"#,
|
||||||
"3",
|
"3",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_import_uses_internal_command() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"module foo { def b [] { 2 }; export def a [] { b } }; use foo; foo.a"#,
|
||||||
|
"2",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hides_def() -> TestResult {
|
||||||
|
fail_test(r#"def foo [] { "foo" }; hide foo; foo"#, "not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hides_def_then_redefines() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"def foo [] { "foo" }; hide foo; def foo [] { "bar" }; foo"#,
|
||||||
|
"defined more than once",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hides_def_in_scope_1() -> TestResult {
|
||||||
|
fail_test(r#"def foo [] { "foo" }; do { hide foo; foo }"#, "not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hides_def_in_scope_2() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; foo }"#,
|
||||||
|
"foo",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hides_def_in_scope_3() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"def foo [] { "foo" }; do { hide foo; def foo [] { "bar" }; hide foo; foo }"#,
|
||||||
|
"not found",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hides_def_in_scope_4() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"def foo [] { "foo" }; do { def foo [] { "bar" }; hide foo; hide foo; foo }"#,
|
||||||
|
"not found",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hide_twice_not_allowed() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"def foo [] { "foo" }; hide foo; hide foo"#,
|
||||||
|
"unknown command",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn def_twice_should_fail() -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
r#"def foo [] { "foo" }; def foo [] { "bar" }"#,
|
||||||
|
"defined more than once",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn from_json_1() -> TestResult {
|
fn from_json_1() -> TestResult {
|
||||||
run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred")
|
run_test(r#"('{"name": "Fred"}' | from json).name"#, "Fred")
|
||||||
|
@ -396,3 +462,32 @@ fn from_json_2() -> TestResult {
|
||||||
"Sally",
|
"Sally",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrap() -> TestResult {
|
||||||
|
run_test(r#"([1, 2, 3] | wrap foo).foo.1"#, "2")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"[[name, grade]; [Alice, A], [Betty, B]] | get grade.1"#,
|
||||||
|
"B",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn select() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"([[name, age]; [a, 1], [b, 2]]) | select name | get 1 | get name"#,
|
||||||
|
"b",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn string_cell_path() -> TestResult {
|
||||||
|
run_test(
|
||||||
|
r#"let x = "name"; [["name", "score"]; [a, b], [c, d]] | get $x | get 1"#,
|
||||||
|
"c",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue