mirror of
https://github.com/nushell/nushell
synced 2024-12-27 21:43:09 +00:00
Merge branch 'nushell:main' into continue-PWD-per-drive-quote
This commit is contained in:
commit
9078c182e8
32 changed files with 457 additions and 89 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -5830,9 +5830,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.19.0"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
|
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rstest"
|
name = "rstest"
|
||||||
|
|
|
@ -143,7 +143,7 @@ regex = "1.9.5"
|
||||||
rmp = "0.8"
|
rmp = "0.8"
|
||||||
rmp-serde = "1.3"
|
rmp-serde = "1.3"
|
||||||
ropey = "1.6.1"
|
ropey = "1.6.1"
|
||||||
roxmltree = "0.19"
|
roxmltree = "0.20"
|
||||||
rstest = { version = "0.23", default-features = false }
|
rstest = { version = "0.23", default-features = false }
|
||||||
rusqlite = "0.31"
|
rusqlite = "0.31"
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
|
|
|
@ -2,4 +2,4 @@ mod from;
|
||||||
mod to;
|
mod to;
|
||||||
|
|
||||||
pub(crate) use from::url::FromUrl;
|
pub(crate) use from::url::FromUrl;
|
||||||
pub(crate) use to::html::ToHtml;
|
pub use to::html::ToHtml;
|
||||||
|
|
|
@ -9,6 +9,7 @@ mod strings;
|
||||||
pub use bits::{
|
pub use bits::{
|
||||||
Bits, BitsAnd, BitsInto, BitsNot, BitsOr, BitsRol, BitsRor, BitsShl, BitsShr, BitsXor,
|
Bits, BitsAnd, BitsInto, BitsNot, BitsOr, BitsRol, BitsRor, BitsShl, BitsShr, BitsXor,
|
||||||
};
|
};
|
||||||
|
pub use formats::ToHtml;
|
||||||
pub use math::{MathArcCos, MathArcCosH, MathArcSin, MathArcSinH, MathArcTan, MathArcTanH};
|
pub use math::{MathArcCos, MathArcCosH, MathArcSin, MathArcSinH, MathArcTan, MathArcTanH};
|
||||||
pub use math::{MathCos, MathCosH, MathSin, MathSinH, MathTan, MathTanH};
|
pub use math::{MathCos, MathCosH, MathSin, MathSinH, MathTan, MathTanH};
|
||||||
pub use math::{MathExp, MathLn};
|
pub use math::{MathExp, MathLn};
|
||||||
|
@ -54,7 +55,8 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
||||||
strings::str_::case::StrTitleCase
|
strings::str_::case::StrTitleCase
|
||||||
);
|
);
|
||||||
|
|
||||||
bind_command!(formats::ToHtml, formats::FromUrl);
|
bind_command!(ToHtml, formats::FromUrl);
|
||||||
|
|
||||||
// Bits
|
// Bits
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Bits,
|
Bits,
|
||||||
|
|
|
@ -106,6 +106,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
|
||||||
bind_command! {
|
bind_command! {
|
||||||
Path,
|
Path,
|
||||||
PathBasename,
|
PathBasename,
|
||||||
|
PathSelf,
|
||||||
PathDirname,
|
PathDirname,
|
||||||
PathExists,
|
PathExists,
|
||||||
PathExpand,
|
PathExpand,
|
||||||
|
|
|
@ -15,7 +15,7 @@ impl Command for UTouch {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn search_terms(&self) -> Vec<&str> {
|
fn search_terms(&self) -> Vec<&str> {
|
||||||
vec!["create", "file"]
|
vec!["create", "file", "coreutils"]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
|
|
|
@ -6,6 +6,7 @@ mod join;
|
||||||
mod parse;
|
mod parse;
|
||||||
pub mod path_;
|
pub mod path_;
|
||||||
mod relative_to;
|
mod relative_to;
|
||||||
|
mod self_;
|
||||||
mod split;
|
mod split;
|
||||||
mod r#type;
|
mod r#type;
|
||||||
|
|
||||||
|
@ -18,6 +19,7 @@ pub use parse::SubCommand as PathParse;
|
||||||
pub use path_::PathCommand as Path;
|
pub use path_::PathCommand as Path;
|
||||||
pub use r#type::SubCommand as PathType;
|
pub use r#type::SubCommand as PathType;
|
||||||
pub use relative_to::SubCommand as PathRelativeTo;
|
pub use relative_to::SubCommand as PathRelativeTo;
|
||||||
|
pub use self_::SubCommand as PathSelf;
|
||||||
pub use split::SubCommand as PathSplit;
|
pub use split::SubCommand as PathSplit;
|
||||||
|
|
||||||
use nu_protocol::{ShellError, Span, Value};
|
use nu_protocol::{ShellError, Span, Value};
|
||||||
|
|
129
crates/nu-command/src/path/self_.rs
Normal file
129
crates/nu-command/src/path/self_.rs
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
use nu_engine::command_prelude::*;
|
||||||
|
use nu_path::expand_path_with;
|
||||||
|
use nu_protocol::engine::StateWorkingSet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"path self"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("path self")
|
||||||
|
.input_output_type(Type::Nothing, Type::String)
|
||||||
|
.allow_variants_without_examples(true)
|
||||||
|
.optional(
|
||||||
|
"path",
|
||||||
|
SyntaxShape::Filepath,
|
||||||
|
"Path to get instead of the current file.",
|
||||||
|
)
|
||||||
|
.category(Category::Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
"Get the absolute path of the script or module containing this command at parse time."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_const(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
Err(ShellError::GenericError {
|
||||||
|
error: "this command can only run during parse-time".into(),
|
||||||
|
msg: "can't run after parse-time".into(),
|
||||||
|
span: Some(call.head),
|
||||||
|
help: Some("try assigning this command's output to a const variable".into()),
|
||||||
|
inner: vec![],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_const(
|
||||||
|
&self,
|
||||||
|
working_set: &StateWorkingSet,
|
||||||
|
call: &Call,
|
||||||
|
_input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let path: Option<String> = call.opt_const(working_set, 0)?;
|
||||||
|
let cwd = working_set.permanent_state.cwd(None)?;
|
||||||
|
let current_file =
|
||||||
|
working_set
|
||||||
|
.files
|
||||||
|
.top()
|
||||||
|
.ok_or_else(|| ShellError::FileNotFoundCustom {
|
||||||
|
msg: "Couldn't find current file".into(),
|
||||||
|
span: call.head,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let out = if let Some(path) = path {
|
||||||
|
let dir = expand_path_with(
|
||||||
|
current_file
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| ShellError::FileNotFoundCustom {
|
||||||
|
msg: "Couldn't find current file's parent.".into(),
|
||||||
|
span: call.head,
|
||||||
|
})?,
|
||||||
|
&cwd,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
Value::string(
|
||||||
|
expand_path_with(path, dir, false).to_string_lossy(),
|
||||||
|
call.head,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Value::string(
|
||||||
|
expand_path_with(current_file, &cwd, true).to_string_lossy(),
|
||||||
|
call.head,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(out.into_pipeline_data())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get the path of the current file",
|
||||||
|
example: r#"const current_file = path self"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the path of the directory containing the current file",
|
||||||
|
example: r#"const current_file = path self ."#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
#[cfg(windows)]
|
||||||
|
Example {
|
||||||
|
description: "Get the absolute form of a path relative to the current file",
|
||||||
|
example: r#"const current_file = path self ..\foo"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
Example {
|
||||||
|
description: "Get the absolute form of a path relative to the current file",
|
||||||
|
example: r#"const current_file = path self ../foo"#,
|
||||||
|
result: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,8 +70,8 @@ impl Command for Sleep {
|
||||||
result: Some(Value::nothing(Span::test_data())),
|
result: Some(Value::nothing(Span::test_data())),
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
description: "Sleep for 3sec",
|
description: "Use multiple arguments to write a duration with multiple units, which is unsupported by duration literals",
|
||||||
example: "sleep 1sec 1sec 1sec",
|
example: "sleep 1min 30sec",
|
||||||
result: None,
|
result: None,
|
||||||
},
|
},
|
||||||
Example {
|
Example {
|
||||||
|
|
|
@ -131,14 +131,21 @@ fn http_delete_timeout() {
|
||||||
let _mock = server
|
let _mock = server
|
||||||
.mock("DELETE", "/")
|
.mock("DELETE", "/")
|
||||||
.with_chunked_body(|w| {
|
.with_chunked_body(|w| {
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(10));
|
||||||
w.write_all(b"Delayed response!")
|
w.write_all(b"Delayed response!")
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
format!("http delete --max-time 500ms {url}", url = server.url()).as_str()
|
format!("http delete --max-time 100ms {url}", url = server.url()).as_str()
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(&actual.err.contains("nu::shell::io_error"));
|
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
assert!(&actual.err.contains("timed out reading response"));
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
assert!(&actual
|
||||||
|
.err
|
||||||
|
.contains("did not properly respond after a period of time"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -325,14 +325,21 @@ fn http_get_timeout() {
|
||||||
let _mock = server
|
let _mock = server
|
||||||
.mock("GET", "/")
|
.mock("GET", "/")
|
||||||
.with_chunked_body(|w| {
|
.with_chunked_body(|w| {
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(10));
|
||||||
w.write_all(b"Delayed response!")
|
w.write_all(b"Delayed response!")
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
format!("http get --max-time 500ms {url}", url = server.url()).as_str()
|
format!("http get --max-time 100ms {url}", url = server.url()).as_str()
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(&actual.err.contains("nu::shell::io_error"));
|
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
assert!(&actual.err.contains("timed out reading response"));
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
assert!(&actual
|
||||||
|
.err
|
||||||
|
.contains("did not properly respond after a period of time"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,14 +50,21 @@ fn http_options_timeout() {
|
||||||
let _mock = server
|
let _mock = server
|
||||||
.mock("OPTIONS", "/")
|
.mock("OPTIONS", "/")
|
||||||
.with_chunked_body(|w| {
|
.with_chunked_body(|w| {
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(10));
|
||||||
w.write_all(b"Delayed response!")
|
w.write_all(b"Delayed response!")
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
format!("http options --max-time 500ms {url}", url = server.url()).as_str()
|
format!("http options --max-time 100ms {url}", url = server.url()).as_str()
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(&actual.err.contains("nu::shell::io_error"));
|
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
assert!(&actual.err.contains("timed out reading response"));
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
assert!(&actual
|
||||||
|
.err
|
||||||
|
.contains("did not properly respond after a period of time"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,18 +171,25 @@ fn http_patch_timeout() {
|
||||||
let _mock = server
|
let _mock = server
|
||||||
.mock("PATCH", "/")
|
.mock("PATCH", "/")
|
||||||
.with_chunked_body(|w| {
|
.with_chunked_body(|w| {
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(10));
|
||||||
w.write_all(b"Delayed response!")
|
w.write_all(b"Delayed response!")
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
format!(
|
format!(
|
||||||
"http patch --max-time 500ms {url} patchbody",
|
"http patch --max-time 100ms {url} patchbody",
|
||||||
url = server.url()
|
url = server.url()
|
||||||
)
|
)
|
||||||
.as_str()
|
.as_str()
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(&actual.err.contains("nu::shell::io_error"));
|
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
assert!(&actual.err.contains("timed out reading response"));
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
assert!(&actual
|
||||||
|
.err
|
||||||
|
.contains("did not properly respond after a period of time"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,18 +285,25 @@ fn http_post_timeout() {
|
||||||
let _mock = server
|
let _mock = server
|
||||||
.mock("POST", "/")
|
.mock("POST", "/")
|
||||||
.with_chunked_body(|w| {
|
.with_chunked_body(|w| {
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(10));
|
||||||
w.write_all(b"Delayed response!")
|
w.write_all(b"Delayed response!")
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
format!(
|
format!(
|
||||||
"http post --max-time 500ms {url} postbody",
|
"http post --max-time 100ms {url} postbody",
|
||||||
url = server.url()
|
url = server.url()
|
||||||
)
|
)
|
||||||
.as_str()
|
.as_str()
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(&actual.err.contains("nu::shell::io_error"));
|
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
assert!(&actual.err.contains("timed out reading response"));
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
assert!(&actual
|
||||||
|
.err
|
||||||
|
.contains("did not properly respond after a period of time"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,18 +171,25 @@ fn http_put_timeout() {
|
||||||
let _mock = server
|
let _mock = server
|
||||||
.mock("PUT", "/")
|
.mock("PUT", "/")
|
||||||
.with_chunked_body(|w| {
|
.with_chunked_body(|w| {
|
||||||
thread::sleep(Duration::from_secs(1));
|
thread::sleep(Duration::from_secs(10));
|
||||||
w.write_all(b"Delayed response!")
|
w.write_all(b"Delayed response!")
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
let actual = nu!(pipeline(
|
let actual = nu!(pipeline(
|
||||||
format!(
|
format!(
|
||||||
"http put --max-time 500ms {url} putbody",
|
"http put --max-time 100ms {url} putbody",
|
||||||
url = server.url()
|
url = server.url()
|
||||||
)
|
)
|
||||||
.as_str()
|
.as_str()
|
||||||
));
|
));
|
||||||
|
|
||||||
assert!(&actual.err.contains("nu::shell::io_error"));
|
assert!(&actual.err.contains("nu::shell::network_failure"));
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
assert!(&actual.err.contains("timed out reading response"));
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
assert!(&actual
|
||||||
|
.err
|
||||||
|
.contains("did not properly respond after a period of time"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,9 +284,9 @@ fn use_main_def_known_external() {
|
||||||
#[test]
|
#[test]
|
||||||
fn use_main_not_exported() {
|
fn use_main_not_exported() {
|
||||||
let inp = &[
|
let inp = &[
|
||||||
r#"module spam { def main [] { "spam" } }"#,
|
r#"module my-super-cool-and-unique-module-name { def main [] { "hi" } }"#,
|
||||||
r#"use spam"#,
|
r#"use my-super-cool-and-unique-module-name"#,
|
||||||
r#"spam"#,
|
r#"my-super-cool-and-unique-module-name"#,
|
||||||
];
|
];
|
||||||
|
|
||||||
let actual = nu!(&inp.join("; "));
|
let actual = nu!(&inp.join("; "));
|
||||||
|
|
|
@ -488,6 +488,7 @@ impl<'e, 's> ScopeData<'e, 's> {
|
||||||
"description" => Value::string(module_desc, span),
|
"description" => Value::string(module_desc, span),
|
||||||
"extra_description" => Value::string(module_extra_desc, span),
|
"extra_description" => Value::string(module_extra_desc, span),
|
||||||
"module_id" => Value::int(module_id.get() as i64, span),
|
"module_id" => Value::int(module_id.get() as i64, span),
|
||||||
|
"file" => Value::string(module.file.clone().map_or("unknown".to_string(), |(p, _)| p.path().to_string_lossy().to_string()), span),
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
)
|
)
|
||||||
|
|
|
@ -7,7 +7,7 @@ use miette::{IntoDiagnostic, Result};
|
||||||
use nu_parser::parse;
|
use nu_parser::parse;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
engine::{EngineState, StateWorkingSet},
|
engine::{EngineState, StateWorkingSet},
|
||||||
Value,
|
Span, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl LanguageServer {
|
impl LanguageServer {
|
||||||
|
@ -28,6 +28,7 @@ impl LanguageServer {
|
||||||
|
|
||||||
let contents = rope_of_file.bytes().collect::<Vec<u8>>();
|
let contents = rope_of_file.bytes().collect::<Vec<u8>>();
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
|
working_set.files.push(file_path.into(), Span::unknown())?;
|
||||||
parse(
|
parse(
|
||||||
&mut working_set,
|
&mut working_set,
|
||||||
Some(&file_path.to_string_lossy()),
|
Some(&file_path.to_string_lossy()),
|
||||||
|
|
|
@ -274,6 +274,9 @@ impl LanguageServer {
|
||||||
|
|
||||||
// TODO: think about passing down the rope into the working_set
|
// TODO: think about passing down the rope into the working_set
|
||||||
let contents = file.bytes().collect::<Vec<u8>>();
|
let contents = file.bytes().collect::<Vec<u8>>();
|
||||||
|
let _ = working_set
|
||||||
|
.files
|
||||||
|
.push(file_path.as_ref().into(), Span::unknown());
|
||||||
let block = parse(working_set, Some(&file_path), &contents, false);
|
let block = parse(working_set, Some(&file_path), &contents, false);
|
||||||
let flattened = flatten_block(working_set, &block);
|
let flattened = flatten_block(working_set, &block);
|
||||||
|
|
||||||
|
|
|
@ -2122,7 +2122,21 @@ pub fn parse_variable_expr(working_set: &mut StateWorkingSet, span: Span) -> Exp
|
||||||
String::from_utf8_lossy(contents).to_string()
|
String::from_utf8_lossy(contents).to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(id) = parse_variable(working_set, span) {
|
let bytes = working_set.get_span_contents(span);
|
||||||
|
let suggestion = || {
|
||||||
|
DidYouMean::new(
|
||||||
|
&working_set.list_variables(),
|
||||||
|
working_set.get_span_contents(span),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
if !is_variable(bytes) {
|
||||||
|
working_set.error(ParseError::ExpectedWithDidYouMean(
|
||||||
|
"valid variable name",
|
||||||
|
suggestion(),
|
||||||
|
span,
|
||||||
|
));
|
||||||
|
garbage(working_set, span)
|
||||||
|
} else if let Some(id) = working_set.find_variable(bytes) {
|
||||||
Expression::new(
|
Expression::new(
|
||||||
working_set,
|
working_set,
|
||||||
Expr::Var(id),
|
Expr::Var(id),
|
||||||
|
@ -2133,9 +2147,7 @@ pub fn parse_variable_expr(working_set: &mut StateWorkingSet, span: Span) -> Exp
|
||||||
working_set.error(ParseError::EnvVarNotVar(name, span));
|
working_set.error(ParseError::EnvVarNotVar(name, span));
|
||||||
garbage(working_set, span)
|
garbage(working_set, span)
|
||||||
} else {
|
} else {
|
||||||
let ws = &*working_set;
|
working_set.error(ParseError::VariableNotFound(suggestion(), span));
|
||||||
let suggestion = DidYouMean::new(&ws.list_variables(), ws.get_span_contents(span));
|
|
||||||
working_set.error(ParseError::VariableNotFound(suggestion, span));
|
|
||||||
garbage(working_set, span)
|
garbage(working_set, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3349,26 +3361,66 @@ pub fn parse_input_output_types(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_full_signature(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
|
pub fn parse_full_signature(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
|
||||||
let arg_signature = working_set.get_span_contents(spans[0]);
|
match spans.len() {
|
||||||
|
// This case should never happen. It corresponds to declarations like `def foo {}`,
|
||||||
if arg_signature.ends_with(b":") {
|
// which should throw a 'Missing required positional argument.' before getting to this point
|
||||||
let mut arg_signature =
|
0 => {
|
||||||
parse_signature(working_set, Span::new(spans[0].start, spans[0].end - 1));
|
working_set.error(ParseError::InternalError(
|
||||||
|
"failed to catch missing positional arguments".to_string(),
|
||||||
let input_output_types = parse_input_output_types(working_set, &spans[1..]);
|
Span::concat(spans),
|
||||||
|
));
|
||||||
if let Expression {
|
garbage(working_set, Span::concat(spans))
|
||||||
expr: Expr::Signature(sig),
|
}
|
||||||
span: expr_span,
|
|
||||||
..
|
// e.g. `[ b"[foo: string]" ]`
|
||||||
} = &mut arg_signature
|
1 => parse_signature(working_set, spans[0]),
|
||||||
{
|
|
||||||
sig.input_output_types = input_output_types;
|
// This case is needed to distinguish between e.g.
|
||||||
expr_span.end = Span::concat(&spans[1..]).end;
|
// `[ b"[]", b"{ true }" ]` vs `[ b"[]:", b"int" ]`
|
||||||
|
2 if working_set.get_span_contents(spans[1]).starts_with(b"{") => {
|
||||||
|
parse_signature(working_set, spans[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should handle every other case, e.g.
|
||||||
|
// `[ b"[]:", b"int" ]`
|
||||||
|
// `[ b"[]", b":", b"int" ]`
|
||||||
|
// `[ b"[]", b":", b"int", b"->", b"bool" ]`
|
||||||
|
_ => {
|
||||||
|
let (mut arg_signature, input_output_types_pos) =
|
||||||
|
if working_set.get_span_contents(spans[0]).ends_with(b":") {
|
||||||
|
(
|
||||||
|
parse_signature(working_set, Span::new(spans[0].start, spans[0].end - 1)),
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
} else if working_set.get_span_contents(spans[1]) == b":" {
|
||||||
|
(parse_signature(working_set, spans[0]), 2)
|
||||||
|
} else {
|
||||||
|
// This should be an error case, but we call parse_signature anyway
|
||||||
|
// so it can handle the various possible errors
|
||||||
|
// e.g. `[ b"[]", b"int" ]` or `[
|
||||||
|
working_set.error(ParseError::Expected(
|
||||||
|
"colon (:) before type signature",
|
||||||
|
Span::concat(&spans[1..]),
|
||||||
|
));
|
||||||
|
// (garbage(working_set, Span::concat(spans)), 1)
|
||||||
|
|
||||||
|
(parse_signature(working_set, spans[0]), 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_output_types =
|
||||||
|
parse_input_output_types(working_set, &spans[input_output_types_pos..]);
|
||||||
|
|
||||||
|
if let Expression {
|
||||||
|
expr: Expr::Signature(sig),
|
||||||
|
span: expr_span,
|
||||||
|
..
|
||||||
|
} = &mut arg_signature
|
||||||
|
{
|
||||||
|
sig.input_output_types = input_output_types;
|
||||||
|
expr_span.end = Span::concat(&spans[input_output_types_pos..]).end;
|
||||||
|
}
|
||||||
|
arg_signature
|
||||||
}
|
}
|
||||||
arg_signature
|
|
||||||
} else {
|
|
||||||
parse_signature(working_set, spans[0])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5612,18 +5664,6 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_variable(working_set: &mut StateWorkingSet, span: Span) -> Option<VarId> {
|
|
||||||
let bytes = working_set.get_span_contents(span);
|
|
||||||
|
|
||||||
if is_variable(bytes) {
|
|
||||||
working_set.find_variable(bytes)
|
|
||||||
} else {
|
|
||||||
working_set.error(ParseError::Expected("valid variable name", span));
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_builtin_commands(
|
pub fn parse_builtin_commands(
|
||||||
working_set: &mut StateWorkingSet,
|
working_set: &mut StateWorkingSet,
|
||||||
lite_command: &LiteCommand,
|
lite_command: &LiteCommand,
|
||||||
|
|
|
@ -2460,6 +2460,7 @@ mod input_types {
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::input_output(b"def q []: int -> int {1}", false)]
|
#[case::input_output(b"def q []: int -> int {1}", false)]
|
||||||
|
#[case::input_output(b"def q [x: bool]: int -> int {2}", false)]
|
||||||
#[case::input_output(b"def q []: string -> string {'qwe'}", false)]
|
#[case::input_output(b"def q []: string -> string {'qwe'}", false)]
|
||||||
#[case::input_output(b"def q []: nothing -> nothing {null}", false)]
|
#[case::input_output(b"def q []: nothing -> nothing {null}", false)]
|
||||||
#[case::input_output(b"def q []: list<string> -> list<string> {[]}", false)]
|
#[case::input_output(b"def q []: list<string> -> list<string> {[]}", false)]
|
||||||
|
@ -2479,6 +2480,42 @@ mod input_types {
|
||||||
#[case::input_output(b"def q []: nothing -> record<c: int e: int {{c: 1 e: 1}}", true)]
|
#[case::input_output(b"def q []: nothing -> record<c: int e: int {{c: 1 e: 1}}", true)]
|
||||||
#[case::input_output(b"def q []: record<c: int e: int -> record<a: int> {{a: 1}}", true)]
|
#[case::input_output(b"def q []: record<c: int e: int -> record<a: int> {{a: 1}}", true)]
|
||||||
#[case::input_output(b"def q []: nothing -> record<a: record<a: int> {{a: {a: 1}}}", true)]
|
#[case::input_output(b"def q []: nothing -> record<a: record<a: int> {{a: {a: 1}}}", true)]
|
||||||
|
#[case::input_output(b"def q []: int []}", true)]
|
||||||
|
#[case::input_output(b"def q []: bool {[]", true)]
|
||||||
|
// Type signature variants with whitespace between inputs and `:`
|
||||||
|
#[case::input_output(b"def q [] : int -> int {1}", false)]
|
||||||
|
#[case::input_output(b"def q [x: bool] : int -> int {2}", false)]
|
||||||
|
#[case::input_output(b"def q []\t : string -> string {'qwe'}", false)]
|
||||||
|
#[case::input_output(b"def q [] \t : nothing -> nothing {null}", false)]
|
||||||
|
#[case::input_output(b"def q [] \t: list<string> -> list<string> {[]}", false)]
|
||||||
|
#[case::input_output(
|
||||||
|
b"def q []\t: record<a: int b: int> -> record<c: int e: int> {{c: 1 e: 1}}",
|
||||||
|
false
|
||||||
|
)]
|
||||||
|
#[case::input_output(
|
||||||
|
b"def q [] : table<a: int b: int> -> table<c: int e: int> {[{c: 1 e: 1}]}",
|
||||||
|
false
|
||||||
|
)]
|
||||||
|
#[case::input_output(
|
||||||
|
b"def q [] : nothing -> record<c: record<a: int b: int> e: int> {{c: {a: 1 b: 2} e: 1}}",
|
||||||
|
false
|
||||||
|
)]
|
||||||
|
#[case::input_output(b"def q [] : nothing -> list<string {[]}", true)]
|
||||||
|
#[case::input_output(b"def q [] : nothing -> record<c: int e: int {{c: 1 e: 1}}", true)]
|
||||||
|
#[case::input_output(b"def q [] : record<c: int e: int -> record<a: int> {{a: 1}}", true)]
|
||||||
|
#[case::input_output(b"def q [] : nothing -> record<a: record<a: int> {{a: {a: 1}}}", true)]
|
||||||
|
#[case::input_output(b"def q [] : int []}", true)]
|
||||||
|
#[case::input_output(b"def q [] : bool {[]", true)]
|
||||||
|
// No input-output type signature
|
||||||
|
#[case::input_output(b"def qq [] {[]}", false)]
|
||||||
|
#[case::input_output(b"def q [] []}", true)]
|
||||||
|
#[case::input_output(b"def q [] {", true)]
|
||||||
|
#[case::input_output(b"def q []: []}", true)]
|
||||||
|
#[case::input_output(b"def q [] int {}", true)]
|
||||||
|
#[case::input_output(b"def q [x: string, y: int] {{c: 1 e: 1}}", false)]
|
||||||
|
#[case::input_output(b"def q [x: string, y: int]: {}", true)]
|
||||||
|
#[case::input_output(b"def q [x: string, y: int] {a: {a: 1}}", true)]
|
||||||
|
#[case::input_output(b"def foo {3}", true)]
|
||||||
#[case::vardecl(b"let a: int = 1", false)]
|
#[case::vardecl(b"let a: int = 1", false)]
|
||||||
#[case::vardecl(b"let a: string = 'qwe'", false)]
|
#[case::vardecl(b"let a: string = 'qwe'", false)]
|
||||||
#[case::vardecl(b"let a: nothing = null", false)]
|
#[case::vardecl(b"let a: nothing = null", false)]
|
||||||
|
|
|
@ -55,6 +55,10 @@ pub enum ParseError {
|
||||||
#[diagnostic(code(nu::parser::parse_mismatch_with_full_string_msg))]
|
#[diagnostic(code(nu::parser::parse_mismatch_with_full_string_msg))]
|
||||||
ExpectedWithStringMsg(String, #[label("expected {0}")] Span),
|
ExpectedWithStringMsg(String, #[label("expected {0}")] Span),
|
||||||
|
|
||||||
|
#[error("Parse mismatch during operation.")]
|
||||||
|
#[diagnostic(code(nu::parser::parse_mismatch_with_did_you_mean))]
|
||||||
|
ExpectedWithDidYouMean(&'static str, DidYouMean, #[label("expected {0}. {1}")] Span),
|
||||||
|
|
||||||
#[error("Command does not support {0} input.")]
|
#[error("Command does not support {0} input.")]
|
||||||
#[diagnostic(code(nu::parser::input_type_mismatch))]
|
#[diagnostic(code(nu::parser::input_type_mismatch))]
|
||||||
InputMismatch(Type, #[label("command doesn't support {0} input")] Span),
|
InputMismatch(Type, #[label("command doesn't support {0} input")] Span),
|
||||||
|
@ -551,6 +555,7 @@ impl ParseError {
|
||||||
ParseError::Unbalanced(_, _, s) => *s,
|
ParseError::Unbalanced(_, _, s) => *s,
|
||||||
ParseError::Expected(_, s) => *s,
|
ParseError::Expected(_, s) => *s,
|
||||||
ParseError::ExpectedWithStringMsg(_, s) => *s,
|
ParseError::ExpectedWithStringMsg(_, s) => *s,
|
||||||
|
ParseError::ExpectedWithDidYouMean(_, _, s) => *s,
|
||||||
ParseError::Mismatch(_, _, s) => *s,
|
ParseError::Mismatch(_, _, s) => *s,
|
||||||
ParseError::UnsupportedOperationLHS(_, _, s, _) => *s,
|
ParseError::UnsupportedOperationLHS(_, _, s, _) => *s,
|
||||||
ParseError::UnsupportedOperationRHS(_, _, _, _, s, _) => *s,
|
ParseError::UnsupportedOperationRHS(_, _, _, _, s, _) => *s,
|
||||||
|
|
|
@ -1220,10 +1220,10 @@ pub enum ShellError {
|
||||||
span: Span,
|
span: Span,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Return event, which may become an error if used outside of a function
|
/// Return event, which may become an error if used outside of a custom command or closure
|
||||||
#[error("Return used outside of function")]
|
#[error("Return used outside of custom command or closure")]
|
||||||
Return {
|
Return {
|
||||||
#[label("used outside of function")]
|
#[label("used outside of custom command or closure")]
|
||||||
span: Span,
|
span: Span,
|
||||||
value: Box<Value>,
|
value: Box<Value>,
|
||||||
},
|
},
|
||||||
|
@ -1544,8 +1544,8 @@ impl From<io::Error> for ShellError {
|
||||||
impl From<Spanned<io::Error>> for ShellError {
|
impl From<Spanned<io::Error>> for ShellError {
|
||||||
fn from(error: Spanned<io::Error>) -> Self {
|
fn from(error: Spanned<io::Error>) -> Self {
|
||||||
let Spanned { item: error, span } = error;
|
let Spanned { item: error, span } = error;
|
||||||
if error.kind() == io::ErrorKind::Other {
|
match error.kind() {
|
||||||
match error.into_inner() {
|
io::ErrorKind::Other => match error.into_inner() {
|
||||||
Some(err) => match err.downcast() {
|
Some(err) => match err.downcast() {
|
||||||
Ok(err) => *err,
|
Ok(err) => *err,
|
||||||
Err(err) => Self::IOErrorSpanned {
|
Err(err) => Self::IOErrorSpanned {
|
||||||
|
@ -1557,12 +1557,15 @@ impl From<Spanned<io::Error>> for ShellError {
|
||||||
msg: "unknown error".into(),
|
msg: "unknown error".into(),
|
||||||
span,
|
span,
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
} else {
|
io::ErrorKind::TimedOut => Self::NetworkFailure {
|
||||||
Self::IOErrorSpanned {
|
|
||||||
msg: error.to_string(),
|
msg: error.to_string(),
|
||||||
span,
|
span,
|
||||||
}
|
},
|
||||||
|
_ => Self::IOErrorSpanned {
|
||||||
|
msg: error.to_string(),
|
||||||
|
span,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ impl Module {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
vec![(
|
vec![(
|
||||||
final_name.clone(),
|
normalize_module_name(&final_name),
|
||||||
Value::record(
|
Value::record(
|
||||||
const_rows
|
const_rows
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -425,3 +425,32 @@ impl Module {
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// normalize module names for exporting as record constant
|
||||||
|
fn normalize_module_name(bytes: &[u8]) -> Vec<u8> {
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.map(|x| match is_identifier_byte(*x) {
|
||||||
|
true => *x,
|
||||||
|
false => b'_',
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_identifier_byte(b: u8) -> bool {
|
||||||
|
b != b'.'
|
||||||
|
&& b != b'['
|
||||||
|
&& b != b'('
|
||||||
|
&& b != b'{'
|
||||||
|
&& b != b'+'
|
||||||
|
&& b != b'-'
|
||||||
|
&& b != b'*'
|
||||||
|
&& b != b'^'
|
||||||
|
&& b != b'/'
|
||||||
|
&& b != b'='
|
||||||
|
&& b != b'!'
|
||||||
|
&& b != b'<'
|
||||||
|
&& b != b'>'
|
||||||
|
&& b != b'&'
|
||||||
|
&& b != b'|'
|
||||||
|
}
|
||||||
|
|
|
@ -36,5 +36,5 @@ export def "from ndnuon" []: [string -> any] {
|
||||||
|
|
||||||
# Convert structured data to NDNUON, i.e. newline-delimited NUON
|
# Convert structured data to NDNUON, i.e. newline-delimited NUON
|
||||||
export def "to ndnuon" []: [any -> string] {
|
export def "to ndnuon" []: [any -> string] {
|
||||||
each { to nuon --raw } | to text
|
each { to nuon --raw | str replace --all "\n" '\n' } | to text
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ def valid-annotations [] {
|
||||||
# Returns a table containing the list of function names together with their annotations (comments above the declaration)
|
# Returns a table containing the list of function names together with their annotations (comments above the declaration)
|
||||||
def get-annotated [
|
def get-annotated [
|
||||||
file: path
|
file: path
|
||||||
] path -> table<function_name: string, annotation: string> {
|
]: path -> table<function_name: string, annotation: string> {
|
||||||
let raw_file = (
|
let raw_file = (
|
||||||
open $file
|
open $file
|
||||||
| lines
|
| lines
|
||||||
|
@ -59,7 +59,7 @@ def get-annotated [
|
||||||
# Annotations that allow multiple functions are of type list<string>
|
# Annotations that allow multiple functions are of type list<string>
|
||||||
# Other annotations are of type string
|
# Other annotations are of type string
|
||||||
# Result gets merged with the template record so that the output shape remains consistent regardless of the table content
|
# Result gets merged with the template record so that the output shape remains consistent regardless of the table content
|
||||||
def create-test-record [] nothing -> record<before-each: string, after-each: string, before-all: string, after-all: string, test: list<string>, test-skip: list<string>> {
|
def create-test-record []: nothing -> record<before-each: string, after-each: string, before-all: string, after-all: string, test: list<string>, test-skip: list<string>> {
|
||||||
let input = $in
|
let input = $in
|
||||||
|
|
||||||
let template_record = {
|
let template_record = {
|
||||||
|
@ -187,7 +187,7 @@ export def ($test_function_name) [] {
|
||||||
def run-tests-for-module [
|
def run-tests-for-module [
|
||||||
module: record<file: path name: string before-each: string after-each: string before-all: string after-all: string test: list test-skip: list>
|
module: record<file: path name: string before-each: string after-each: string before-all: string after-all: string test: list test-skip: list>
|
||||||
threads: int
|
threads: int
|
||||||
] -> table<file: path, name: string, test: string, result: string> {
|
]: nothing -> table<file: path, name: string, test: string, result: string> {
|
||||||
let global_context = if not ($module.before-all|is-empty) {
|
let global_context = if not ($module.before-all|is-empty) {
|
||||||
log info $"Running before-all for module ($module.name)"
|
log info $"Running before-all for module ($module.name)"
|
||||||
run-test {
|
run-test {
|
||||||
|
|
|
@ -128,3 +128,17 @@ def to_ndnuon_single_object [] {
|
||||||
let expect = "{a: 1}"
|
let expect = "{a: 1}"
|
||||||
assert equal $result $expect "could not convert to NDNUON"
|
assert equal $result $expect "could not convert to NDNUON"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
def to_ndnuon_multiline_strings [] {
|
||||||
|
let result = "foo\n\\n\nbar" | to ndnuon
|
||||||
|
let expect = '"foo\n\\n\nbar"'
|
||||||
|
assert equal $result $expect "could not convert multiline string to NDNUON"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
def from_ndnuon_multiline_strings [] {
|
||||||
|
let result = '"foo\n\\n\nbar"' | from ndnuon
|
||||||
|
let expect = ["foo\n\\n\nbar"]
|
||||||
|
assert equal $result $expect "could not convert multiline string from NDNUON"
|
||||||
|
}
|
||||||
|
|
|
@ -128,3 +128,17 @@ def to_ndnuon_single_object [] {
|
||||||
let expect = "{a: 1}"
|
let expect = "{a: 1}"
|
||||||
assert equal $result $expect "could not convert to NDNUON"
|
assert equal $result $expect "could not convert to NDNUON"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
def to_ndnuon_multiline_strings [] {
|
||||||
|
let result = "foo\n\\n\nbar" | formats to ndnuon
|
||||||
|
let expect = '"foo\n\\n\nbar"'
|
||||||
|
assert equal $result $expect "could not convert multiline string to NDNUON"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
def from_ndnuon_multiline_strings [] {
|
||||||
|
let result = '"foo\n\\n\nbar"' | formats from ndnuon
|
||||||
|
let expect = ["foo\n\\n\nbar"]
|
||||||
|
assert equal $result $expect "could not convert multiline string from NDNUON"
|
||||||
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ use crate::{
|
||||||
};
|
};
|
||||||
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
|
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
|
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned,
|
||||||
Value,
|
SyntaxShape, Type, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -26,7 +26,7 @@ impl PluginCommand for WithColumn {
|
||||||
|
|
||||||
fn signature(&self) -> Signature {
|
fn signature(&self) -> Signature {
|
||||||
Signature::build(self.name())
|
Signature::build(self.name())
|
||||||
.named("name", SyntaxShape::String, "new column name", Some('n'))
|
.named("name", SyntaxShape::String, "New column name. For lazy dataframes and expressions syntax, use a `polars as` expression to name a column.", Some('n'))
|
||||||
.rest(
|
.rest(
|
||||||
"series or expressions",
|
"series or expressions",
|
||||||
SyntaxShape::Any,
|
SyntaxShape::Any,
|
||||||
|
@ -138,6 +138,15 @@ fn command_eager(
|
||||||
let column_span = new_column.span();
|
let column_span = new_column.span();
|
||||||
|
|
||||||
if NuExpression::can_downcast(&new_column) {
|
if NuExpression::can_downcast(&new_column) {
|
||||||
|
if let Some(name) = call.get_flag::<Spanned<String>>("name")? {
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
|
error: "Flag 'name' is unsuppored when used with expressions. Please use the `polars as` expression to name a column".into(),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(name.span),
|
||||||
|
help: Some("Use a `polars as` expression to name a column".into()),
|
||||||
|
inner: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
let vals: Vec<Value> = call.rest(0)?;
|
let vals: Vec<Value> = call.rest(0)?;
|
||||||
let value = Value::list(vals, call.head);
|
let value = Value::list(vals, call.head);
|
||||||
let expressions = NuExpression::extract_exprs(plugin, value)?;
|
let expressions = NuExpression::extract_exprs(plugin, value)?;
|
||||||
|
@ -177,6 +186,16 @@ fn command_lazy(
|
||||||
call: &EvaluatedCall,
|
call: &EvaluatedCall,
|
||||||
lazy: NuLazyFrame,
|
lazy: NuLazyFrame,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if let Some(name) = call.get_flag::<Spanned<String>>("name")? {
|
||||||
|
return Err(ShellError::GenericError {
|
||||||
|
error: "Flag 'name' is unsuppored for lazy dataframes. Please use the `polars as` expression to name a column".into(),
|
||||||
|
msg: "".into(),
|
||||||
|
span: Some(name.span),
|
||||||
|
help: Some("Use a `polars as` expression to name a column".into()),
|
||||||
|
inner: vec![],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let vals: Vec<Value> = call.rest(0)?;
|
let vals: Vec<Value> = call.rest(0)?;
|
||||||
let value = Value::list(vals, call.head);
|
let value = Value::list(vals, call.head);
|
||||||
let expressions = NuExpression::extract_exprs(plugin, value)?;
|
let expressions = NuExpression::extract_exprs(plugin, value)?;
|
||||||
|
|
|
@ -24,6 +24,7 @@ fn find_id(
|
||||||
) -> Option<(Id, usize, Span)> {
|
) -> Option<(Id, usize, Span)> {
|
||||||
let file_id = working_set.add_file(file_path.to_string(), file);
|
let file_id = working_set.add_file(file_path.to_string(), file);
|
||||||
let offset = working_set.get_span_for_file(file_id).start;
|
let offset = working_set.get_span_for_file(file_id).start;
|
||||||
|
let _ = working_set.files.push(file_path.into(), Span::unknown());
|
||||||
let block = parse(working_set, Some(file_path), file, false);
|
let block = parse(working_set, Some(file_path), file, false);
|
||||||
let flattened = flatten_block(working_set, &block);
|
let flattened = flatten_block(working_set, &block);
|
||||||
|
|
||||||
|
@ -88,6 +89,7 @@ pub fn check(engine_state: &mut EngineState, file_path: &str, max_errors: &Value
|
||||||
|
|
||||||
if let Ok(contents) = file {
|
if let Ok(contents) = file {
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
|
let _ = working_set.files.push(file_path.into(), Span::unknown());
|
||||||
let block = parse(&mut working_set, Some(file_path), &contents, false);
|
let block = parse(&mut working_set, Some(file_path), &contents, false);
|
||||||
|
|
||||||
for (idx, err) in working_set.parse_errors.iter().enumerate() {
|
for (idx, err) in working_set.parse_errors.iter().enumerate() {
|
||||||
|
@ -631,6 +633,7 @@ pub fn ast(engine_state: &mut EngineState, file_path: &str) {
|
||||||
|
|
||||||
if let Ok(contents) = file {
|
if let Ok(contents) = file {
|
||||||
let offset = working_set.next_span_start();
|
let offset = working_set.next_span_start();
|
||||||
|
let _ = working_set.files.push(file_path.into(), Span::unknown());
|
||||||
let parsed_block = parse(&mut working_set, Some(file_path), &contents, false);
|
let parsed_block = parse(&mut working_set, Some(file_path), &contents, false);
|
||||||
|
|
||||||
let flat = flatten_block(&working_set, &parsed_block);
|
let flat = flatten_block(&working_set, &parsed_block);
|
||||||
|
|
|
@ -807,10 +807,10 @@ fn overlay_can_add_renamed_overlay() {
|
||||||
#[test]
|
#[test]
|
||||||
fn overlay_hide_renamed_overlay() {
|
fn overlay_hide_renamed_overlay() {
|
||||||
let inp = &[
|
let inp = &[
|
||||||
r#"module spam { export def foo [] { "foo" } }"#,
|
r#"module spam { export def foo-command-which-does-not-conflict [] { "foo" } }"#,
|
||||||
"overlay use spam as eggs",
|
"overlay use spam as eggs",
|
||||||
"overlay hide eggs",
|
"overlay hide eggs",
|
||||||
"foo",
|
"foo-command-which-does-not-conflict",
|
||||||
];
|
];
|
||||||
|
|
||||||
let actual = nu!(&inp.join("; "));
|
let actual = nu!(&inp.join("; "));
|
||||||
|
@ -1243,9 +1243,9 @@ fn overlay_use_main_def_known_external() {
|
||||||
#[test]
|
#[test]
|
||||||
fn overlay_use_main_not_exported() {
|
fn overlay_use_main_not_exported() {
|
||||||
let inp = &[
|
let inp = &[
|
||||||
r#"module foo { def main [] { "foo" } }"#,
|
r#"module my-super-cool-and-unique-module-name { def main [] { "hi" } }"#,
|
||||||
"overlay use foo",
|
"overlay use my-super-cool-and-unique-module-name",
|
||||||
"foo",
|
"my-super-cool-and-unique-module-name",
|
||||||
];
|
];
|
||||||
|
|
||||||
let actual = nu!(&inp.join("; "));
|
let actual = nu!(&inp.join("; "));
|
||||||
|
@ -1257,11 +1257,11 @@ fn overlay_use_main_not_exported() {
|
||||||
fn alias_overlay_hide() {
|
fn alias_overlay_hide() {
|
||||||
let inp = &[
|
let inp = &[
|
||||||
"overlay new spam",
|
"overlay new spam",
|
||||||
"def foo [] { 'foo' }",
|
"def my-epic-command-name [] { 'foo' }",
|
||||||
"overlay new eggs",
|
"overlay new eggs",
|
||||||
"alias oh = overlay hide",
|
"alias oh = overlay hide",
|
||||||
"oh spam",
|
"oh spam",
|
||||||
"foo",
|
"my-epic-command-name",
|
||||||
];
|
];
|
||||||
|
|
||||||
let actual = nu!(&inp.join("; "));
|
let actual = nu!(&inp.join("; "));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::repl::tests::{fail_test, run_test, TestResult};
|
use crate::repl::tests::{fail_test, run_test, TestResult};
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn module_def_imports_1() -> TestResult {
|
fn module_def_imports_1() -> TestResult {
|
||||||
|
@ -145,6 +146,28 @@ fn export_module_which_defined_const() -> TestResult {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("spam-mod")]
|
||||||
|
#[case("spam/mod")]
|
||||||
|
#[case("spam=mod")]
|
||||||
|
fn export_module_with_normalized_var_name(#[case] name: &str) -> TestResult {
|
||||||
|
let def = format!(
|
||||||
|
"module {name} {{ export const b = 3; export module {name}2 {{ export const c = 4 }} }}"
|
||||||
|
);
|
||||||
|
run_test(&format!("{def}; use {name}; $spam_mod.b"), "3")?;
|
||||||
|
run_test(&format!("{def}; use {name} *; $spam_mod2.c"), "4")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("spam-mod")]
|
||||||
|
#[case("spam/mod")]
|
||||||
|
fn use_module_with_invalid_var_name(#[case] name: &str) -> TestResult {
|
||||||
|
fail_test(
|
||||||
|
&format!("module {name} {{ export const b = 3 }}; use {name}; ${name}"),
|
||||||
|
"expected valid variable name. Did you mean '$spam_mod'",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cannot_export_private_const() -> TestResult {
|
fn cannot_export_private_const() -> TestResult {
|
||||||
fail_test(
|
fail_test(
|
||||||
|
|
Loading…
Reference in a new issue