Merge remote-tracking branch 'origin/main' into plugin-ctrlc-reset
This commit is contained in:
commit
6ed02ea06a
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3157,6 +3157,7 @@ dependencies = [
|
|||
"nu-path",
|
||||
"nu-protocol",
|
||||
"nu-utils",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -1337,20 +1337,26 @@ fn are_session_ids_in_sync() {
|
|||
#[cfg(test)]
|
||||
mod test_auto_cd {
|
||||
use super::{do_auto_cd, parse_operation, ReplOperation};
|
||||
use nu_path::AbsolutePath;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use std::path::Path;
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// Create a symlink. Works on both Unix and Windows.
|
||||
#[cfg(any(unix, windows))]
|
||||
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
fn symlink(
|
||||
original: impl AsRef<AbsolutePath>,
|
||||
link: impl AsRef<AbsolutePath>,
|
||||
) -> std::io::Result<()> {
|
||||
let original = original.as_ref();
|
||||
let link = link.as_ref();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::os::unix::fs::symlink(original, link)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if original.as_ref().is_dir() {
|
||||
if original.is_dir() {
|
||||
std::os::windows::fs::symlink_dir(original, link)
|
||||
} else {
|
||||
std::os::windows::fs::symlink_file(original, link)
|
||||
|
@ -1362,11 +1368,11 @@ mod test_auto_cd {
|
|||
/// `before`, and after `input` is parsed and evaluated, PWD should be
|
||||
/// changed to `after`.
|
||||
#[track_caller]
|
||||
fn check(before: impl AsRef<Path>, input: &str, after: impl AsRef<Path>) {
|
||||
fn check(before: impl AsRef<AbsolutePath>, input: &str, after: impl AsRef<AbsolutePath>) {
|
||||
// Setup EngineState and Stack.
|
||||
let mut engine_state = EngineState::new();
|
||||
let mut stack = Stack::new();
|
||||
stack.set_cwd(before).unwrap();
|
||||
stack.set_cwd(before.as_ref()).unwrap();
|
||||
|
||||
// Parse the input. It must be an auto-cd operation.
|
||||
let op = parse_operation(input.to_string(), &engine_state, &stack).unwrap();
|
||||
|
@ -1382,54 +1388,66 @@ mod test_auto_cd {
|
|||
// don't have to be byte-wise equal (on Windows, the 8.3 filename
|
||||
// conversion messes things up),
|
||||
let updated_cwd = std::fs::canonicalize(updated_cwd).unwrap();
|
||||
let after = std::fs::canonicalize(after).unwrap();
|
||||
let after = std::fs::canonicalize(after.as_ref()).unwrap();
|
||||
assert_eq!(updated_cwd, after);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_root() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let root = if cfg!(windows) { r"C:\" } else { "/" };
|
||||
check(&tempdir, root, root);
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let input = if cfg!(windows) { r"C:\" } else { "/" };
|
||||
let root = AbsolutePath::try_new(input).unwrap();
|
||||
check(tempdir, input, root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_tilde() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let home = nu_path::home_dir().unwrap();
|
||||
check(&tempdir, "~", home);
|
||||
check(tempdir, "~", home);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_dot() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
check(&tempdir, ".", &tempdir);
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
check(tempdir, ".", tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_double_dot() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
check(dir, "..", &tempdir);
|
||||
check(dir, "..", tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_triple_dot() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo").join("bar");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo").join("bar");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
check(dir, "...", &tempdir);
|
||||
check(dir, "...", tempdir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_relative() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let foo = tempdir.path().join("foo");
|
||||
let bar = tempdir.path().join("bar");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let foo = tempdir.join("foo");
|
||||
let bar = tempdir.join("bar");
|
||||
std::fs::create_dir_all(&foo).unwrap();
|
||||
std::fs::create_dir_all(&bar).unwrap();
|
||||
|
||||
let input = if cfg!(windows) { r"..\bar" } else { "../bar" };
|
||||
check(foo, input, bar);
|
||||
}
|
||||
|
@ -1437,32 +1455,35 @@ mod test_auto_cd {
|
|||
#[test]
|
||||
fn auto_cd_trailing_slash() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
||||
check(&tempdir, input, dir);
|
||||
check(tempdir, input, dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_cd_symlink() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let link = tempdir.path().join("link");
|
||||
symlink(&dir, &link).unwrap();
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let link = tempdir.join("link");
|
||||
symlink(&dir, &link).unwrap();
|
||||
let input = if cfg!(windows) { r".\link" } else { "./link" };
|
||||
check(&tempdir, input, link);
|
||||
check(tempdir, input, link);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "was not parsed into an auto-cd operation")]
|
||||
fn auto_cd_nonexistent_directory() {
|
||||
let tempdir = tempdir().unwrap();
|
||||
let dir = tempdir.path().join("foo");
|
||||
let tempdir = AbsolutePath::try_new(tempdir.path()).unwrap();
|
||||
|
||||
let dir = tempdir.join("foo");
|
||||
let input = if cfg!(windows) { r"foo\" } else { "foo/" };
|
||||
check(&tempdir, input, dir);
|
||||
check(tempdir, input, dir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -177,11 +177,9 @@ fn run_histogram(
|
|||
match v {
|
||||
// parse record, and fill valid value to actual input.
|
||||
Value::Record { val, .. } => {
|
||||
for (c, v) in val.iter() {
|
||||
if c == col_name {
|
||||
if let Ok(v) = HashableValue::from_value(v.clone(), head_span) {
|
||||
inputs.push(v);
|
||||
}
|
||||
if let Some(v) = val.get(col_name) {
|
||||
if let Ok(v) = HashableValue::from_value(v.clone(), head_span) {
|
||||
inputs.push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ impl Command for Cd {
|
|||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Change to the previous working directory ($OLDPWD)",
|
||||
description: r#"Change to the previous working directory (same as "cd $env.OLDPWD")"#,
|
||||
example: r#"cd -"#,
|
||||
result: None,
|
||||
},
|
||||
|
@ -143,6 +143,16 @@ impl Command for Cd {
|
|||
example: r#"def --env gohome [] { cd ~ }"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Move two directories up in the tree (the parent directory's parent). Additional dots can be added for additional levels.",
|
||||
example: r#"cd ..."#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "The cd command itself is often optional. Simply entering a path to a directory will cd to it.",
|
||||
example: r#"/home"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,17 +86,22 @@ impl Command for UCp {
|
|||
},
|
||||
Example {
|
||||
description: "Copy only if source file is newer than target file",
|
||||
example: "cp -u a b",
|
||||
example: "cp -u myfile newfile",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Copy file preserving mode and timestamps attributes",
|
||||
example: "cp --preserve [ mode timestamps ] a b",
|
||||
example: "cp --preserve [ mode timestamps ] myfile newfile",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Copy file erasing all attributes",
|
||||
example: "cp --preserve [] a b",
|
||||
example: "cp --preserve [] myfile newfile",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Copy file to a directory three levels above its current location",
|
||||
example: "cp myfile ....",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
|
|
|
@ -40,6 +40,11 @@ impl Command for UMv {
|
|||
example: "mv *.txt my/subdirectory",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: r#"Move a file into the "my" directory two levels up in the directory tree"#,
|
||||
example: "mv test.txt .../my/",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ impl Command for Default {
|
|||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Sets a default row's column if missing."
|
||||
"Sets a default value if a row's column is missing or null."
|
||||
}
|
||||
|
||||
fn run(
|
||||
|
@ -66,6 +66,20 @@ impl Command for Default {
|
|||
Span::test_data(),
|
||||
)),
|
||||
},
|
||||
Example {
|
||||
description: r#"Replace the missing value in the "a" column of a list"#,
|
||||
example: "[{a:1 b:2} {b:1}] | default 'N/A' a",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_int(1),
|
||||
"b" => Value::test_int(2),
|
||||
}),
|
||||
Value::test_record(record! {
|
||||
"a" => Value::test_string("N/A"),
|
||||
"b" => Value::test_int(1),
|
||||
}),
|
||||
])),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -88,19 +102,13 @@ fn default(
|
|||
val: ref mut record,
|
||||
..
|
||||
} => {
|
||||
let mut found = false;
|
||||
|
||||
for (col, val) in record.to_mut().iter_mut() {
|
||||
if *col == column.item {
|
||||
found = true;
|
||||
if matches!(val, Value::Nothing { .. }) {
|
||||
*val = value.clone();
|
||||
}
|
||||
let record = record.to_mut();
|
||||
if let Some(val) = record.get_mut(&column.item) {
|
||||
if matches!(val, Value::Nothing { .. }) {
|
||||
*val = value.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
record.to_mut().push(column.item.clone(), value.clone());
|
||||
} else {
|
||||
record.push(column.item.clone(), value.clone());
|
||||
}
|
||||
|
||||
item
|
||||
|
|
|
@ -19,12 +19,17 @@ impl Command for SubCommand {
|
|||
Signature::build("random chars")
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
.allow_variants_without_examples(true)
|
||||
.named("length", SyntaxShape::Int, "Number of chars", Some('l'))
|
||||
.named(
|
||||
"length",
|
||||
SyntaxShape::Int,
|
||||
"Number of chars (default 25)",
|
||||
Some('l'),
|
||||
)
|
||||
.category(Category::Random)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Generate random chars."
|
||||
"Generate random chars uniformly distributed over ASCII letters and numbers: a-z, A-Z and 0-9."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
|
@ -44,7 +49,7 @@ impl Command for SubCommand {
|
|||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Generate random chars",
|
||||
description: "Generate a string with 25 random chars",
|
||||
example: "random chars",
|
||||
result: None,
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use nu_path::Path;
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn cd_works_with_in_var() {
|
||||
|
@ -22,7 +22,7 @@ fn filesystem_change_from_current_directory_using_relative_path() {
|
|||
Playground::setup("cd_test_1", |dirs, _| {
|
||||
let actual = nu!( cwd: dirs.root(), "cd cd_test_1; $env.PWD");
|
||||
|
||||
assert_eq!(PathBuf::from(actual.out), *dirs.test());
|
||||
assert_eq!(Path::new(&actual.out), dirs.test());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,7 @@ fn filesystem_change_from_current_directory_using_relative_path_with_trailing_sl
|
|||
// Intentionally not using correct path sep because this should work on Windows
|
||||
let actual = nu!( cwd: dirs.root(), "cd cd_test_1_slash/; $env.PWD");
|
||||
|
||||
assert_eq!(PathBuf::from(actual.out), *dirs.test());
|
||||
assert_eq!(Path::new(&actual.out), *dirs.test());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ fn filesystem_change_from_current_directory_using_absolute_path() {
|
|||
dirs.formats().display()
|
||||
);
|
||||
|
||||
assert_eq!(PathBuf::from(actual.out), dirs.formats());
|
||||
assert_eq!(Path::new(&actual.out), dirs.formats());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ fn filesystem_change_from_current_directory_using_absolute_path_with_trailing_sl
|
|||
std::path::MAIN_SEPARATOR_STR,
|
||||
);
|
||||
|
||||
assert_eq!(PathBuf::from(actual.out), dirs.formats());
|
||||
assert_eq!(Path::new(&actual.out), dirs.formats());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ fn filesystem_switch_back_to_previous_working_directory() {
|
|||
dirs.test().display()
|
||||
);
|
||||
|
||||
assert_eq!(PathBuf::from(actual.out), dirs.test().join("odin"));
|
||||
assert_eq!(Path::new(&actual.out), dirs.test().join("odin"));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -101,10 +101,7 @@ fn filesystem_change_from_current_directory_using_relative_path_and_dash() {
|
|||
"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from(actual.out),
|
||||
dirs.test().join("odin").join("-")
|
||||
);
|
||||
assert_eq!(Path::new(&actual.out), dirs.test().join("odin").join("-"));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -119,7 +116,7 @@ fn filesystem_change_current_directory_to_parent_directory() {
|
|||
"
|
||||
);
|
||||
|
||||
assert_eq!(PathBuf::from(actual.out), *dirs.root());
|
||||
assert_eq!(Path::new(&actual.out), *dirs.root());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -136,7 +133,7 @@ fn filesystem_change_current_directory_to_two_parents_up_using_multiple_dots() {
|
|||
"
|
||||
);
|
||||
|
||||
assert_eq!(PathBuf::from(actual.out), *dirs.test());
|
||||
assert_eq!(Path::new(&actual.out), *dirs.test());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -151,7 +148,7 @@ fn filesystem_change_to_home_directory() {
|
|||
"
|
||||
);
|
||||
|
||||
assert_eq!(Some(PathBuf::from(actual.out)), dirs::home_dir());
|
||||
assert_eq!(Path::new(&actual.out), dirs::home_dir().unwrap());
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -169,7 +166,7 @@ fn filesystem_change_to_a_directory_containing_spaces() {
|
|||
);
|
||||
|
||||
assert_eq!(
|
||||
PathBuf::from(actual.out),
|
||||
Path::new(&actual.out),
|
||||
dirs.test().join("robalino turner katz")
|
||||
);
|
||||
})
|
||||
|
@ -234,7 +231,7 @@ fn filesystem_change_directory_to_symlink_relative() {
|
|||
$env.PWD
|
||||
"
|
||||
);
|
||||
assert_eq!(PathBuf::from(actual.out), dirs.test().join("foo_link"));
|
||||
assert_eq!(Path::new(&actual.out), dirs.test().join("foo_link"));
|
||||
|
||||
let actual = nu!(
|
||||
cwd: dirs.test().join("boo"),
|
||||
|
@ -243,7 +240,7 @@ fn filesystem_change_directory_to_symlink_relative() {
|
|||
$env.PWD
|
||||
"
|
||||
);
|
||||
assert_eq!(PathBuf::from(actual.out), dirs.test().join("foo"));
|
||||
assert_eq!(Path::new(&actual.out), dirs.test().join("foo"));
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::{io::Write, path::PathBuf};
|
||||
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use nu_path::AbsolutePathBuf;
|
||||
use nu_protocol::{ast::PathMember, record, Span, Value};
|
||||
use nu_test_support::{
|
||||
fs::{line_ending, Stub},
|
||||
|
@ -13,6 +12,7 @@ use rand::{
|
|||
rngs::StdRng,
|
||||
Rng, SeedableRng,
|
||||
};
|
||||
use std::io::Write;
|
||||
|
||||
#[test]
|
||||
fn into_sqlite_schema() {
|
||||
|
@ -453,7 +453,7 @@ impl Distribution<TestRow> for Standard {
|
|||
}
|
||||
}
|
||||
|
||||
fn make_sqlite_db(dirs: &Dirs, nu_table: &str) -> PathBuf {
|
||||
fn make_sqlite_db(dirs: &Dirs, nu_table: &str) -> AbsolutePathBuf {
|
||||
let testdir = dirs.test();
|
||||
let testdb_path =
|
||||
testdir.join(testdir.file_name().unwrap().to_str().unwrap().to_owned() + ".db");
|
||||
|
@ -465,7 +465,7 @@ fn make_sqlite_db(dirs: &Dirs, nu_table: &str) -> PathBuf {
|
|||
);
|
||||
|
||||
assert!(nucmd.status.success());
|
||||
testdb_path.into()
|
||||
testdb_path
|
||||
}
|
||||
|
||||
fn insert_test_rows(dirs: &Dirs, nu_table: &str, sql_query: Option<&str>, expected: Vec<TestRow>) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use nu_path::AbsolutePath;
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn creates_temp_file() {
|
||||
|
@ -9,7 +9,7 @@ fn creates_temp_file() {
|
|||
cwd: dirs.test(),
|
||||
"mktemp"
|
||||
);
|
||||
let loc = PathBuf::from(output.out.clone());
|
||||
let loc = AbsolutePath::try_new(&output.out).unwrap();
|
||||
println!("{:?}", loc);
|
||||
assert!(loc.exists());
|
||||
})
|
||||
|
@ -22,7 +22,7 @@ fn creates_temp_file_with_suffix() {
|
|||
cwd: dirs.test(),
|
||||
"mktemp --suffix .txt tempfileXXX"
|
||||
);
|
||||
let loc = PathBuf::from(output.out.clone());
|
||||
let loc = AbsolutePath::try_new(&output.out).unwrap();
|
||||
assert!(loc.exists());
|
||||
assert!(loc.is_file());
|
||||
assert!(output.out.ends_with(".txt"));
|
||||
|
@ -37,8 +37,7 @@ fn creates_temp_directory() {
|
|||
cwd: dirs.test(),
|
||||
"mktemp -d"
|
||||
);
|
||||
|
||||
let loc = PathBuf::from(output.out);
|
||||
let loc = AbsolutePath::try_new(&output.out).unwrap();
|
||||
assert!(loc.exists());
|
||||
assert!(loc.is_dir());
|
||||
})
|
||||
|
|
|
@ -2,7 +2,6 @@ use nu_test_support::fs::{files_exist_at, Stub::EmptyFile, Stub::FileWithContent
|
|||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use rstest::rstest;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn moves_a_file() {
|
||||
|
@ -96,7 +95,7 @@ fn moves_the_directory_inside_directory_if_path_to_move_is_existing_directory()
|
|||
|
||||
assert!(!original_dir.exists());
|
||||
assert!(expected.exists());
|
||||
assert!(files_exist_at(vec!["jttxt"], expected))
|
||||
assert!(files_exist_at(&["jttxt"], expected))
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -125,7 +124,7 @@ fn moves_using_path_with_wildcard() {
|
|||
nu!(cwd: work_dir, "mv ../originals/*.ini ../expected");
|
||||
|
||||
assert!(files_exist_at(
|
||||
vec!["yehuda.ini", "jt.ini", "sample.ini", "andres.ini",],
|
||||
&["yehuda.ini", "jt.ini", "sample.ini", "andres.ini",],
|
||||
expected
|
||||
));
|
||||
})
|
||||
|
@ -152,7 +151,7 @@ fn moves_using_a_glob() {
|
|||
|
||||
assert!(meal_dir.exists());
|
||||
assert!(files_exist_at(
|
||||
vec!["arepa.txt", "empanada.txt", "taquiza.txt",],
|
||||
&["arepa.txt", "empanada.txt", "taquiza.txt",],
|
||||
expected
|
||||
));
|
||||
})
|
||||
|
@ -184,7 +183,7 @@ fn moves_a_directory_with_files() {
|
|||
assert!(!original_dir.exists());
|
||||
assert!(expected_dir.exists());
|
||||
assert!(files_exist_at(
|
||||
vec![
|
||||
&[
|
||||
"car/car1.txt",
|
||||
"car/car2.txt",
|
||||
"bicycle/bicycle1.txt",
|
||||
|
@ -322,7 +321,7 @@ fn move_files_using_glob_two_parents_up_using_multiple_dots() {
|
|||
"#
|
||||
);
|
||||
|
||||
let files = vec![
|
||||
let files = &[
|
||||
"yehuda.yaml",
|
||||
"jtjson",
|
||||
"andres.xml",
|
||||
|
@ -333,7 +332,7 @@ fn move_files_using_glob_two_parents_up_using_multiple_dots() {
|
|||
let original_dir = dirs.test().join("foo/bar");
|
||||
let destination_dir = dirs.test();
|
||||
|
||||
assert!(files_exist_at(files.clone(), destination_dir));
|
||||
assert!(files_exist_at(files, destination_dir));
|
||||
assert!(!files_exist_at(files, original_dir))
|
||||
})
|
||||
}
|
||||
|
@ -440,10 +439,7 @@ fn mv_change_case_of_directory() {
|
|||
);
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
|
||||
assert!(files_exist_at(
|
||||
vec!["somefile.txt",],
|
||||
dirs.test().join(new_dir)
|
||||
));
|
||||
assert!(files_exist_at(&["somefile.txt"], dirs.test().join(new_dir)));
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
|
||||
_actual.err.contains("to a subdirectory of itself");
|
||||
|
@ -647,10 +643,10 @@ fn test_cp_inside_glob_metachars_dir() {
|
|||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!files_exist_at(
|
||||
vec!["test_file.txt"],
|
||||
&["test_file.txt"],
|
||||
dirs.test().join(sub_dir)
|
||||
));
|
||||
assert!(files_exist_at(vec!["test_file.txt"], dirs.test()));
|
||||
assert!(files_exist_at(&["test_file.txt"], dirs.test()));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -667,19 +663,13 @@ fn mv_with_tilde() {
|
|||
// mv file
|
||||
let actual = nu!(cwd: dirs.test(), "mv '~tilde/f1.txt' ./");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!files_exist_at(
|
||||
vec![Path::new("f1.txt")],
|
||||
dirs.test().join("~tilde")
|
||||
));
|
||||
assert!(files_exist_at(vec![Path::new("f1.txt")], dirs.test()));
|
||||
assert!(!files_exist_at(&["f1.txt"], dirs.test().join("~tilde")));
|
||||
assert!(files_exist_at(&["f1.txt"], dirs.test()));
|
||||
|
||||
// pass variable
|
||||
let actual = nu!(cwd: dirs.test(), "let f = '~tilde/f2.txt'; mv $f ./");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!files_exist_at(
|
||||
vec![Path::new("f2.txt")],
|
||||
dirs.test().join("~tilde")
|
||||
));
|
||||
assert!(files_exist_at(vec![Path::new("f1.txt")], dirs.test()));
|
||||
assert!(!files_exist_at(&["f2.txt"], dirs.test().join("~tilde")));
|
||||
assert!(files_exist_at(&["f1.txt"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use nu_path::Path;
|
||||
use nu_test_support::fs::Stub::EmptyFile;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn expands_path_with_dot() {
|
||||
Playground::setup("path_expand_1", |dirs, sandbox| {
|
||||
|
@ -18,7 +17,7 @@ fn expands_path_with_dot() {
|
|||
));
|
||||
|
||||
let expected = dirs.test.join("menu").join("spam.txt");
|
||||
assert_eq!(PathBuf::from(actual.out), expected);
|
||||
assert_eq!(Path::new(&actual.out), expected);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -38,7 +37,7 @@ fn expands_path_without_follow_symlink() {
|
|||
));
|
||||
|
||||
let expected = dirs.test.join("menu").join("spam_link.ln");
|
||||
assert_eq!(PathBuf::from(actual.out), expected);
|
||||
assert_eq!(Path::new(&actual.out), expected);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -56,7 +55,7 @@ fn expands_path_with_double_dot() {
|
|||
));
|
||||
|
||||
let expected = dirs.test.join("menu").join("spam.txt");
|
||||
assert_eq!(PathBuf::from(actual.out), expected);
|
||||
assert_eq!(Path::new(&actual.out), expected);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -74,7 +73,7 @@ fn const_path_expand() {
|
|||
));
|
||||
|
||||
let expected = dirs.test.join("menu").join("spam.txt");
|
||||
assert_eq!(PathBuf::from(actual.out), expected);
|
||||
assert_eq!(Path::new(&actual.out), expected);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -92,7 +91,7 @@ mod windows {
|
|||
"#
|
||||
));
|
||||
|
||||
assert!(!PathBuf::from(actual.out).starts_with("~"));
|
||||
assert!(!Path::new(&actual.out).starts_with("~"));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -106,7 +105,7 @@ mod windows {
|
|||
"#
|
||||
));
|
||||
|
||||
assert!(!PathBuf::from(actual.out).starts_with("~"));
|
||||
assert!(!Path::new(&actual.out).starts_with("~"));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -131,7 +130,7 @@ mod windows {
|
|||
));
|
||||
|
||||
let expected = dirs.test.join("menu").join("spam_link.ln");
|
||||
assert_eq!(PathBuf::from(actual.out), expected);
|
||||
assert_eq!(Path::new(&actual.out), expected);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ use nu_test_support::playground::Playground;
|
|||
use rstest::rstest;
|
||||
#[cfg(not(windows))]
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn removes_a_file() {
|
||||
|
@ -50,7 +49,7 @@ fn removes_files_with_wildcard() {
|
|||
);
|
||||
|
||||
assert!(!files_exist_at(
|
||||
vec![
|
||||
&[
|
||||
"src/parser/parse/token_tree.rs",
|
||||
"src/parser/hir/baseline_parse.rs",
|
||||
"src/parser/hir/baseline_parse_tokens.rs"
|
||||
|
@ -91,7 +90,7 @@ fn removes_deeply_nested_directories_with_wildcard_and_recursive_flag() {
|
|||
);
|
||||
|
||||
assert!(!files_exist_at(
|
||||
vec!["src/parser/parse", "src/parser/hir"],
|
||||
&["src/parser/parse", "src/parser/hir"],
|
||||
dirs.test()
|
||||
));
|
||||
})
|
||||
|
@ -277,7 +276,7 @@ fn remove_files_from_two_parents_up_using_multiple_dots_and_glob() {
|
|||
);
|
||||
|
||||
assert!(!files_exist_at(
|
||||
vec!["yehuda.txt", "jttxt", "kevin.txt"],
|
||||
&["yehuda.txt", "jttxt", "kevin.txt"],
|
||||
dirs.test()
|
||||
));
|
||||
})
|
||||
|
@ -305,8 +304,8 @@ fn rm_wildcard_keeps_dotfiles() {
|
|||
r#"rm *"#
|
||||
);
|
||||
|
||||
assert!(!files_exist_at(vec!["foo"], dirs.test()));
|
||||
assert!(files_exist_at(vec![".bar"], dirs.test()));
|
||||
assert!(!files_exist_at(&["foo"], dirs.test()));
|
||||
assert!(files_exist_at(&[".bar"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -320,8 +319,8 @@ fn rm_wildcard_leading_dot_deletes_dotfiles() {
|
|||
"rm .*"
|
||||
);
|
||||
|
||||
assert!(files_exist_at(vec!["foo"], dirs.test()));
|
||||
assert!(!files_exist_at(vec![".bar"], dirs.test()));
|
||||
assert!(files_exist_at(&["foo"], dirs.test()));
|
||||
assert!(!files_exist_at(&[".bar"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -453,7 +452,7 @@ fn rm_prints_filenames_on_error() {
|
|||
// This rm is expected to fail, and stderr output indicating so is also expected.
|
||||
let actual = nu!(cwd: test_dir, "rm test*.txt");
|
||||
|
||||
assert!(files_exist_at(file_names.clone(), test_dir));
|
||||
assert!(files_exist_at(&file_names, test_dir));
|
||||
for file_name in file_names {
|
||||
let path = test_dir.join(file_name);
|
||||
let substr = format!("Could not delete {}", path.to_string_lossy());
|
||||
|
@ -482,7 +481,7 @@ fn rm_files_inside_glob_metachars_dir() {
|
|||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!files_exist_at(
|
||||
vec!["test_file.txt"],
|
||||
&["test_file.txt"],
|
||||
dirs.test().join(sub_dir)
|
||||
));
|
||||
});
|
||||
|
@ -556,22 +555,16 @@ fn rm_with_tilde() {
|
|||
|
||||
let actual = nu!(cwd: dirs.test(), "rm '~tilde/f1.txt'");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!files_exist_at(
|
||||
vec![Path::new("f1.txt")],
|
||||
dirs.test().join("~tilde")
|
||||
));
|
||||
assert!(!files_exist_at(&["f1.txt"], dirs.test().join("~tilde")));
|
||||
|
||||
// pass variable
|
||||
let actual = nu!(cwd: dirs.test(), "let f = '~tilde/f2.txt'; rm $f");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!files_exist_at(
|
||||
vec![Path::new("f2.txt")],
|
||||
dirs.test().join("~tilde")
|
||||
));
|
||||
assert!(!files_exist_at(&["f2.txt"], dirs.test().join("~tilde")));
|
||||
|
||||
// remove directory
|
||||
let actual = nu!(cwd: dirs.test(), "let f = '~tilde'; rm -r $f");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(!files_exist_at(vec![Path::new("~tilde")], dirs.test()));
|
||||
assert!(!files_exist_at(&["~tilde"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ use chrono::{DateTime, Local};
|
|||
use nu_test_support::fs::{files_exist_at, Stub};
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::Playground;
|
||||
use std::path::Path;
|
||||
|
||||
// Use 1 instead of 0 because 0 has a special meaning in Windows
|
||||
const TIME_ONE: filetime::FileTime = filetime::FileTime::from_unix_time(1, 0);
|
||||
|
@ -494,12 +493,12 @@ fn create_a_file_with_tilde() {
|
|||
Playground::setup("touch with tilde", |dirs, _| {
|
||||
let actual = nu!(cwd: dirs.test(), "touch '~tilde'");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(vec![Path::new("~tilde")], dirs.test()));
|
||||
assert!(files_exist_at(&["~tilde"], dirs.test()));
|
||||
|
||||
// pass variable
|
||||
let actual = nu!(cwd: dirs.test(), "let f = '~tilde2'; touch $f");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(vec![Path::new("~tilde2")], dirs.test()));
|
||||
assert!(files_exist_at(&["~tilde2"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ use nu_test_support::nu;
|
|||
use nu_test_support::playground::Playground;
|
||||
|
||||
use rstest::rstest;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
const PATH_SEPARATOR: &str = "/";
|
||||
|
@ -131,11 +130,7 @@ fn copies_the_directory_inside_directory_if_path_to_copy_is_directory_and_with_r
|
|||
|
||||
assert!(expected_dir.exists());
|
||||
assert!(files_exist_at(
|
||||
vec![
|
||||
Path::new("yehuda.txt"),
|
||||
Path::new("jttxt"),
|
||||
Path::new("andres.txt")
|
||||
],
|
||||
&["yehuda.txt", "jttxt", "andres.txt"],
|
||||
&expected_dir
|
||||
));
|
||||
})
|
||||
|
@ -181,15 +176,15 @@ fn deep_copies_with_recursive_flag_impl(progress: bool) {
|
|||
|
||||
assert!(expected_dir.exists());
|
||||
assert!(files_exist_at(
|
||||
vec![Path::new("errors.txt"), Path::new("multishells.txt")],
|
||||
&["errors.txt", "multishells.txt"],
|
||||
jts_expected_copied_dir
|
||||
));
|
||||
assert!(files_exist_at(
|
||||
vec![Path::new("coverage.txt"), Path::new("commands.txt")],
|
||||
&["coverage.txt", "commands.txt"],
|
||||
andres_expected_copied_dir
|
||||
));
|
||||
assert!(files_exist_at(
|
||||
vec![Path::new("defer-evaluation.txt")],
|
||||
&["defer-evaluation.txt"],
|
||||
yehudas_expected_copied_dir
|
||||
));
|
||||
})
|
||||
|
@ -220,13 +215,13 @@ fn copies_using_path_with_wildcard_impl(progress: bool) {
|
|||
);
|
||||
|
||||
assert!(files_exist_at(
|
||||
vec![
|
||||
Path::new("caco3_plastics.csv"),
|
||||
Path::new("cargo_sample.toml"),
|
||||
Path::new("jt.xml"),
|
||||
Path::new("sample.ini"),
|
||||
Path::new("sgml_description.json"),
|
||||
Path::new("utf16.ini"),
|
||||
&[
|
||||
"caco3_plastics.csv",
|
||||
"cargo_sample.toml",
|
||||
"jt.xml",
|
||||
"sample.ini",
|
||||
"sgml_description.json",
|
||||
"utf16.ini",
|
||||
],
|
||||
dirs.test()
|
||||
));
|
||||
|
@ -265,13 +260,13 @@ fn copies_using_a_glob_impl(progress: bool) {
|
|||
);
|
||||
|
||||
assert!(files_exist_at(
|
||||
vec![
|
||||
Path::new("caco3_plastics.csv"),
|
||||
Path::new("cargo_sample.toml"),
|
||||
Path::new("jt.xml"),
|
||||
Path::new("sample.ini"),
|
||||
Path::new("sgml_description.json"),
|
||||
Path::new("utf16.ini"),
|
||||
&[
|
||||
"caco3_plastics.csv",
|
||||
"cargo_sample.toml",
|
||||
"jt.xml",
|
||||
"sample.ini",
|
||||
"sgml_description.json",
|
||||
"utf16.ini",
|
||||
],
|
||||
dirs.test()
|
||||
));
|
||||
|
@ -341,7 +336,7 @@ fn copy_files_using_glob_two_parents_up_using_multiple_dots_imp(progress: bool)
|
|||
);
|
||||
|
||||
assert!(files_exist_at(
|
||||
vec![
|
||||
&[
|
||||
"yehuda.yaml",
|
||||
"jtjson",
|
||||
"andres.xml",
|
||||
|
@ -377,7 +372,7 @@ fn copy_file_and_dir_from_two_parents_up_using_multiple_dots_to_current_dir_recu
|
|||
|
||||
let expected = dirs.test().join("foo/bar");
|
||||
|
||||
assert!(files_exist_at(vec!["hello_there", "hello_again"], expected));
|
||||
assert!(files_exist_at(&["hello_there", "hello_again"], expected));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -428,7 +423,7 @@ fn copy_dir_contains_symlink_ignored_impl(progress: bool) {
|
|||
|
||||
// check hello_there exists inside `tmp_dir_2`, and `dangle_symlink` don't exists inside `tmp_dir_2`.
|
||||
let expected = sandbox.cwd().join("tmp_dir_2");
|
||||
assert!(files_exist_at(vec!["hello_there"], expected));
|
||||
assert!(files_exist_at(&["hello_there"], expected));
|
||||
// GNU cp will copy the broken symlink, so following their behavior
|
||||
// thus commenting out below
|
||||
// let path = expected.join("dangle_symlink");
|
||||
|
@ -461,7 +456,7 @@ fn copy_dir_contains_symlink_impl(progress: bool) {
|
|||
|
||||
// check hello_there exists inside `tmp_dir_2`, and `dangle_symlink` also exists inside `tmp_dir_2`.
|
||||
let expected = sandbox.cwd().join("tmp_dir_2");
|
||||
assert!(files_exist_at(vec!["hello_there"], expected.clone()));
|
||||
assert!(files_exist_at(&["hello_there"], expected.clone()));
|
||||
let path = expected.join("dangle_symlink");
|
||||
assert!(path.is_symlink());
|
||||
});
|
||||
|
@ -1151,10 +1146,10 @@ fn test_cp_inside_glob_metachars_dir() {
|
|||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(
|
||||
vec!["test_file.txt"],
|
||||
&["test_file.txt"],
|
||||
dirs.test().join(sub_dir)
|
||||
));
|
||||
assert!(files_exist_at(vec!["test_file.txt"], dirs.test()));
|
||||
assert!(files_exist_at(&["test_file.txt"], dirs.test()));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1167,10 +1162,7 @@ fn test_cp_to_customized_home_directory() {
|
|||
let actual = nu!(cwd: dirs.test(), "mkdir test; cp test_file.txt ~/test/");
|
||||
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(
|
||||
vec!["test_file.txt"],
|
||||
dirs.test().join("test")
|
||||
));
|
||||
assert!(files_exist_at(&["test_file.txt"], dirs.test().join("test")));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1193,20 +1185,14 @@ fn cp_with_tilde() {
|
|||
// cp file
|
||||
let actual = nu!(cwd: dirs.test(), "cp '~tilde/f1.txt' ./");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(
|
||||
vec![Path::new("f1.txt")],
|
||||
dirs.test().join("~tilde")
|
||||
));
|
||||
assert!(files_exist_at(vec![Path::new("f1.txt")], dirs.test()));
|
||||
assert!(files_exist_at(&["f1.txt"], dirs.test().join("~tilde")));
|
||||
assert!(files_exist_at(&["f1.txt"], dirs.test()));
|
||||
|
||||
// pass variable
|
||||
let actual = nu!(cwd: dirs.test(), "let f = '~tilde/f2.txt'; cp $f ./");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(
|
||||
vec![Path::new("f2.txt")],
|
||||
dirs.test().join("~tilde")
|
||||
));
|
||||
assert!(files_exist_at(vec![Path::new("f1.txt")], dirs.test()));
|
||||
assert!(files_exist_at(&["f2.txt"], dirs.test().join("~tilde")));
|
||||
assert!(files_exist_at(&["f1.txt"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use nu_test_support::fs::files_exist_at;
|
||||
use nu_test_support::playground::Playground;
|
||||
use nu_test_support::{nu, pipeline};
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn creates_directory() {
|
||||
|
@ -25,10 +24,7 @@ fn accepts_and_creates_directories() {
|
|||
"mkdir dir_1 dir_2 dir_3"
|
||||
);
|
||||
|
||||
assert!(files_exist_at(
|
||||
vec![Path::new("dir_1"), Path::new("dir_2"), Path::new("dir_3")],
|
||||
dirs.test()
|
||||
));
|
||||
assert!(files_exist_at(&["dir_1", "dir_2", "dir_3"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -70,10 +66,7 @@ fn print_created_paths() {
|
|||
pipeline("mkdir -v dir_1 dir_2 dir_3")
|
||||
);
|
||||
|
||||
assert!(files_exist_at(
|
||||
vec![Path::new("dir_1"), Path::new("dir_2"), Path::new("dir_3")],
|
||||
dirs.test()
|
||||
));
|
||||
assert!(files_exist_at(&["dir_1", "dir_2", "dir_3"], dirs.test()));
|
||||
|
||||
assert!(actual.out.contains("dir_1"));
|
||||
assert!(actual.out.contains("dir_2"));
|
||||
|
@ -165,11 +158,11 @@ fn mkdir_with_tilde() {
|
|||
Playground::setup("mkdir with tilde", |dirs, _| {
|
||||
let actual = nu!(cwd: dirs.test(), "mkdir '~tilde'");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(vec![Path::new("~tilde")], dirs.test()));
|
||||
assert!(files_exist_at(&["~tilde"], dirs.test()));
|
||||
|
||||
// pass variable
|
||||
let actual = nu!(cwd: dirs.test(), "let f = '~tilde2'; mkdir $f");
|
||||
assert!(actual.err.is_empty());
|
||||
assert!(files_exist_at(vec![Path::new("~tilde2")], dirs.test()));
|
||||
assert!(files_exist_at(&["~tilde2"], dirs.test()));
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ nu-path = { path = "../nu-path", version = "0.96.2" }
|
|||
nu-glob = { path = "../nu-glob", version = "0.96.2" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.96.2" }
|
||||
log = { workspace = true }
|
||||
terminal_size = { workspace = true }
|
||||
|
||||
[features]
|
||||
plugin = []
|
||||
plugin = []
|
||||
|
|
|
@ -7,6 +7,7 @@ use nu_protocol::{
|
|||
Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::{collections::HashMap, fmt::Write};
|
||||
use terminal_size::{Height, Width};
|
||||
|
||||
pub fn get_full_help(
|
||||
command: &dyn Command,
|
||||
|
@ -234,6 +235,14 @@ fn get_documentation(
|
|||
}
|
||||
}
|
||||
|
||||
fn get_term_width() -> usize {
|
||||
if let Some((Width(w), Height(_))) = terminal_size::terminal_size() {
|
||||
w as usize
|
||||
} else {
|
||||
80
|
||||
}
|
||||
}
|
||||
|
||||
if !is_parser_keyword && !sig.input_output_types.is_empty() {
|
||||
if let Some(decl_id) = engine_state.find_decl(b"table", &[]) {
|
||||
// FIXME: we may want to make this the span of the help command in the future
|
||||
|
@ -256,7 +265,18 @@ fn get_documentation(
|
|||
&Call {
|
||||
decl_id,
|
||||
head: span,
|
||||
arguments: vec![],
|
||||
arguments: vec![Argument::Named((
|
||||
Spanned {
|
||||
item: "width".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
None,
|
||||
Some(Expression::new_unknown(
|
||||
Expr::Int(get_term_width() as i64 - 2), // padding, see below
|
||||
Span::unknown(),
|
||||
Type::Int,
|
||||
)),
|
||||
))],
|
||||
parser_info: HashMap::new(),
|
||||
},
|
||||
PipelineData::Value(Value::list(vals, span), None),
|
||||
|
@ -334,6 +354,19 @@ fn get_documentation(
|
|||
None,
|
||||
))
|
||||
}
|
||||
table_call.add_named((
|
||||
Spanned {
|
||||
item: "width".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
None,
|
||||
Some(Expression::new_unknown(
|
||||
Expr::Int(get_term_width() as i64 - 2),
|
||||
Span::unknown(),
|
||||
Type::Int,
|
||||
)),
|
||||
));
|
||||
|
||||
let table = engine_state
|
||||
.find_decl("table".as_bytes(), &[])
|
||||
.and_then(|decl_id| {
|
||||
|
|
|
@ -177,16 +177,19 @@ pub trait InterfaceManager {
|
|||
) -> Result<PipelineData, ShellError> {
|
||||
self.prepare_pipeline_data(match header {
|
||||
PipelineDataHeader::Empty => PipelineData::Empty,
|
||||
PipelineDataHeader::Value(value) => PipelineData::Value(value, None),
|
||||
PipelineDataHeader::Value(value, metadata) => PipelineData::Value(value, metadata),
|
||||
PipelineDataHeader::ListStream(info) => {
|
||||
let handle = self.stream_manager().get_handle();
|
||||
let reader = handle.read_stream(info.id, self.get_interface())?;
|
||||
ListStream::new(reader, info.span, signals.clone()).into()
|
||||
let ls = ListStream::new(reader, info.span, signals.clone());
|
||||
PipelineData::ListStream(ls, info.metadata)
|
||||
}
|
||||
PipelineDataHeader::ByteStream(info) => {
|
||||
let handle = self.stream_manager().get_handle();
|
||||
let reader = handle.read_stream(info.id, self.get_interface())?;
|
||||
ByteStream::from_result_iter(reader, info.span, signals.clone(), info.type_).into()
|
||||
let bs =
|
||||
ByteStream::from_result_iter(reader, info.span, signals.clone(), info.type_);
|
||||
PipelineData::ByteStream(bs, info.metadata)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -248,26 +251,33 @@ pub trait Interface: Clone + Send {
|
|||
Ok::<_, ShellError>((id, writer))
|
||||
};
|
||||
match self.prepare_pipeline_data(data, context)? {
|
||||
PipelineData::Value(value, ..) => {
|
||||
Ok((PipelineDataHeader::Value(value), PipelineDataWriter::None))
|
||||
}
|
||||
PipelineData::Value(value, metadata) => Ok((
|
||||
PipelineDataHeader::Value(value, metadata),
|
||||
PipelineDataWriter::None,
|
||||
)),
|
||||
PipelineData::Empty => Ok((PipelineDataHeader::Empty, PipelineDataWriter::None)),
|
||||
PipelineData::ListStream(stream, ..) => {
|
||||
PipelineData::ListStream(stream, metadata) => {
|
||||
let (id, writer) = new_stream(LIST_STREAM_HIGH_PRESSURE)?;
|
||||
Ok((
|
||||
PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id,
|
||||
span: stream.span(),
|
||||
metadata,
|
||||
}),
|
||||
PipelineDataWriter::ListStream(writer, stream),
|
||||
))
|
||||
}
|
||||
PipelineData::ByteStream(stream, ..) => {
|
||||
PipelineData::ByteStream(stream, metadata) => {
|
||||
let span = stream.span();
|
||||
let type_ = stream.type_();
|
||||
if let Some(reader) = stream.reader() {
|
||||
let (id, writer) = new_stream(RAW_STREAM_HIGH_PRESSURE)?;
|
||||
let header = PipelineDataHeader::ByteStream(ByteStreamInfo { id, span, type_ });
|
||||
let header = PipelineDataHeader::ByteStream(ByteStreamInfo {
|
||||
id,
|
||||
span,
|
||||
type_,
|
||||
metadata,
|
||||
});
|
||||
Ok((header, PipelineDataWriter::ByteStream(writer, reader)))
|
||||
} else {
|
||||
Ok((PipelineDataHeader::Empty, PipelineDataWriter::None))
|
||||
|
|
|
@ -137,10 +137,16 @@ fn read_pipeline_data_empty() -> Result<(), ShellError> {
|
|||
fn read_pipeline_data_value() -> Result<(), ShellError> {
|
||||
let manager = TestInterfaceManager::new(&TestCase::new());
|
||||
let value = Value::test_int(4);
|
||||
let header = PipelineDataHeader::Value(value.clone());
|
||||
|
||||
let metadata = Some(PipelineMetadata {
|
||||
data_source: DataSource::FilePath("/test/path".into()),
|
||||
content_type: None,
|
||||
});
|
||||
let header = PipelineDataHeader::Value(value.clone(), metadata.clone());
|
||||
match manager.read_pipeline_data(header, &Signals::empty())? {
|
||||
PipelineData::Value(read_value, ..) => assert_eq!(value, read_value),
|
||||
PipelineData::Value(read_value, read_metadata) => {
|
||||
assert_eq!(value, read_value);
|
||||
assert_eq!(metadata, read_metadata);
|
||||
}
|
||||
PipelineData::ListStream(..) => panic!("unexpected ListStream"),
|
||||
PipelineData::ByteStream(..) => panic!("unexpected ByteStream"),
|
||||
PipelineData::Empty => panic!("unexpected Empty"),
|
||||
|
@ -161,9 +167,15 @@ fn read_pipeline_data_list_stream() -> Result<(), ShellError> {
|
|||
}
|
||||
test.add(StreamMessage::End(7));
|
||||
|
||||
let metadata = Some(PipelineMetadata {
|
||||
data_source: DataSource::None,
|
||||
content_type: Some("foobar".into()),
|
||||
});
|
||||
|
||||
let header = PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 7,
|
||||
span: Span::test_data(),
|
||||
metadata,
|
||||
});
|
||||
|
||||
let pipe = manager.read_pipeline_data(header, &Signals::empty())?;
|
||||
|
@ -204,10 +216,17 @@ fn read_pipeline_data_byte_stream() -> Result<(), ShellError> {
|
|||
test.add(StreamMessage::End(12));
|
||||
|
||||
let test_span = Span::new(10, 13);
|
||||
|
||||
let metadata = Some(PipelineMetadata {
|
||||
data_source: DataSource::None,
|
||||
content_type: Some("foobar".into()),
|
||||
});
|
||||
|
||||
let header = PipelineDataHeader::ByteStream(ByteStreamInfo {
|
||||
id: 12,
|
||||
span: test_span,
|
||||
type_: ByteStreamType::Unknown,
|
||||
metadata,
|
||||
});
|
||||
|
||||
let pipe = manager.read_pipeline_data(header, &Signals::empty())?;
|
||||
|
@ -251,9 +270,15 @@ fn read_pipeline_data_byte_stream() -> Result<(), ShellError> {
|
|||
#[test]
|
||||
fn read_pipeline_data_prepared_properly() -> Result<(), ShellError> {
|
||||
let manager = TestInterfaceManager::new(&TestCase::new());
|
||||
let metadata = Some(PipelineMetadata {
|
||||
data_source: DataSource::None,
|
||||
content_type: Some("foobar".into()),
|
||||
});
|
||||
|
||||
let header = PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
metadata,
|
||||
});
|
||||
match manager.read_pipeline_data(header, &Signals::empty())? {
|
||||
PipelineData::ListStream(_, meta) => match meta {
|
||||
|
@ -301,7 +326,7 @@ fn write_pipeline_data_value() -> Result<(), ShellError> {
|
|||
interface.init_write_pipeline_data(PipelineData::Value(value.clone(), None), &())?;
|
||||
|
||||
match header {
|
||||
PipelineDataHeader::Value(read_value) => assert_eq!(value, read_value),
|
||||
PipelineDataHeader::Value(read_value, _) => assert_eq!(value, read_value),
|
||||
_ => panic!("unexpected header: {header:?}"),
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,8 @@ macro_rules! generate_tests {
|
|||
StreamData,
|
||||
};
|
||||
use nu_protocol::{
|
||||
LabeledError, PluginSignature, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
DataSource, LabeledError, PipelineMetadata, PluginSignature, Signature, Span, Spanned,
|
||||
SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[test]
|
||||
|
@ -123,10 +124,15 @@ macro_rules! generate_tests {
|
|||
)],
|
||||
};
|
||||
|
||||
let metadata = Some(PipelineMetadata {
|
||||
data_source: DataSource::None,
|
||||
content_type: Some("foobar".into()),
|
||||
});
|
||||
|
||||
let plugin_call = PluginCall::Run(CallInfo {
|
||||
name: name.clone(),
|
||||
call: call.clone(),
|
||||
input: PipelineDataHeader::Value(input.clone()),
|
||||
input: PipelineDataHeader::Value(input.clone(), metadata.clone()),
|
||||
});
|
||||
|
||||
let plugin_input = PluginInput::Call(1, plugin_call);
|
||||
|
@ -144,7 +150,7 @@ macro_rules! generate_tests {
|
|||
match returned {
|
||||
PluginInput::Call(1, PluginCall::Run(call_info)) => {
|
||||
assert_eq!(name, call_info.name);
|
||||
assert_eq!(PipelineDataHeader::Value(input), call_info.input);
|
||||
assert_eq!(PipelineDataHeader::Value(input, metadata), call_info.input);
|
||||
assert_eq!(call.head, call_info.call.head);
|
||||
assert_eq!(call.positional.len(), call_info.call.positional.len());
|
||||
|
||||
|
@ -305,7 +311,7 @@ macro_rules! generate_tests {
|
|||
match returned {
|
||||
PluginOutput::CallResponse(
|
||||
4,
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::Value(returned_value)),
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::Value(returned_value, _)),
|
||||
) => {
|
||||
assert_eq!(value, returned_value)
|
||||
}
|
||||
|
@ -325,7 +331,7 @@ macro_rules! generate_tests {
|
|||
span,
|
||||
);
|
||||
|
||||
let response = PluginCallResponse::PipelineData(PipelineDataHeader::Value(value));
|
||||
let response = PluginCallResponse::PipelineData(PipelineDataHeader::value(value));
|
||||
let output = PluginOutput::CallResponse(5, response);
|
||||
|
||||
let encoder = $encoder;
|
||||
|
@ -341,7 +347,7 @@ macro_rules! generate_tests {
|
|||
match returned {
|
||||
PluginOutput::CallResponse(
|
||||
5,
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::Value(returned_value)),
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::Value(returned_value, _)),
|
||||
) => {
|
||||
assert_eq!(span, returned_value.span());
|
||||
|
||||
|
|
|
@ -17,8 +17,9 @@ use nu_plugin_protocol::{
|
|||
use nu_protocol::{
|
||||
ast::{Math, Operator},
|
||||
engine::Closure,
|
||||
ByteStreamType, CustomValue, IntoInterruptiblePipelineData, IntoSpanned, PipelineData,
|
||||
PluginMetadata, PluginSignature, ShellError, Signals, Span, Spanned, Value,
|
||||
ByteStreamType, CustomValue, DataSource, IntoInterruptiblePipelineData, IntoSpanned,
|
||||
PipelineData, PipelineMetadata, PluginMetadata, PluginSignature, ShellError, Signals, Span,
|
||||
Spanned, Value,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -52,10 +53,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul
|
|||
|
||||
// Create a stream...
|
||||
let stream = manager.read_pipeline_data(
|
||||
PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
PipelineDataHeader::list_stream(ListStreamInfo::new(0, Span::test_data())),
|
||||
&Signals::empty(),
|
||||
)?;
|
||||
|
||||
|
@ -108,10 +106,7 @@ fn manager_consume_all_propagates_io_error_to_readers() -> Result<(), ShellError
|
|||
test.set_read_error(test_io_error());
|
||||
|
||||
let stream = manager.read_pipeline_data(
|
||||
PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
PipelineDataHeader::list_stream(ListStreamInfo::new(0, Span::test_data())),
|
||||
&Signals::empty(),
|
||||
)?;
|
||||
|
||||
|
@ -154,11 +149,11 @@ fn manager_consume_all_propagates_message_error_to_readers() -> Result<(), Shell
|
|||
test.add(invalid_output());
|
||||
|
||||
let stream = manager.read_pipeline_data(
|
||||
PipelineDataHeader::ByteStream(ByteStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
type_: ByteStreamType::Unknown,
|
||||
}),
|
||||
PipelineDataHeader::byte_stream(ByteStreamInfo::new(
|
||||
0,
|
||||
Span::test_data(),
|
||||
ByteStreamType::Unknown,
|
||||
)),
|
||||
&Signals::empty(),
|
||||
)?;
|
||||
|
||||
|
@ -331,10 +326,10 @@ fn manager_consume_call_response_forwards_to_subscriber_with_pipeline_data(
|
|||
|
||||
manager.consume(PluginOutput::CallResponse(
|
||||
0,
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
})),
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::list_stream(ListStreamInfo::new(
|
||||
0,
|
||||
Span::test_data(),
|
||||
))),
|
||||
))?;
|
||||
|
||||
for i in 0..2 {
|
||||
|
@ -375,18 +370,18 @@ fn manager_consume_call_response_registers_streams() -> Result<(), ShellError> {
|
|||
// Check list streams, byte streams
|
||||
manager.consume(PluginOutput::CallResponse(
|
||||
0,
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
})),
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::list_stream(ListStreamInfo::new(
|
||||
0,
|
||||
Span::test_data(),
|
||||
))),
|
||||
))?;
|
||||
manager.consume(PluginOutput::CallResponse(
|
||||
1,
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::ByteStream(ByteStreamInfo {
|
||||
id: 1,
|
||||
span: Span::test_data(),
|
||||
type_: ByteStreamType::Unknown,
|
||||
})),
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::byte_stream(ByteStreamInfo::new(
|
||||
1,
|
||||
Span::test_data(),
|
||||
ByteStreamType::Unknown,
|
||||
))),
|
||||
))?;
|
||||
|
||||
// ListStream should have one
|
||||
|
@ -442,10 +437,7 @@ fn manager_consume_engine_call_forwards_to_subscriber_with_pipeline_data() -> Re
|
|||
span: Span::test_data(),
|
||||
},
|
||||
positional: vec![],
|
||||
input: PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 2,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
input: PipelineDataHeader::list_stream(ListStreamInfo::new(2, Span::test_data())),
|
||||
redirect_stdout: false,
|
||||
redirect_stderr: false,
|
||||
},
|
||||
|
@ -806,6 +798,11 @@ fn interface_write_plugin_call_writes_run_with_value_input() -> Result<(), Shell
|
|||
let manager = test.plugin("test");
|
||||
let interface = manager.get_interface();
|
||||
|
||||
let metadata0 = PipelineMetadata {
|
||||
data_source: DataSource::None,
|
||||
content_type: Some("baz".into()),
|
||||
};
|
||||
|
||||
let result = interface.write_plugin_call(
|
||||
PluginCall::Run(CallInfo {
|
||||
name: "foo".into(),
|
||||
|
@ -814,7 +811,7 @@ fn interface_write_plugin_call_writes_run_with_value_input() -> Result<(), Shell
|
|||
positional: vec![],
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineData::Value(Value::test_int(-1), None),
|
||||
input: PipelineData::Value(Value::test_int(-1), Some(metadata0.clone())),
|
||||
}),
|
||||
None,
|
||||
)?;
|
||||
|
@ -826,7 +823,10 @@ fn interface_write_plugin_call_writes_run_with_value_input() -> Result<(), Shell
|
|||
PluginCall::Run(CallInfo { name, input, .. }) => {
|
||||
assert_eq!("foo", name);
|
||||
match input {
|
||||
PipelineDataHeader::Value(value) => assert_eq!(-1, value.as_int()?),
|
||||
PipelineDataHeader::Value(value, metadata) => {
|
||||
assert_eq!(-1, value.as_int()?);
|
||||
assert_eq!(metadata0, metadata.expect("there should be metadata"));
|
||||
}
|
||||
_ => panic!("unexpected input header: {input:?}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,8 @@ pub mod test_util;
|
|||
|
||||
use nu_protocol::{
|
||||
ast::Operator, engine::Closure, ByteStreamType, Config, DeclId, LabeledError, PipelineData,
|
||||
PluginMetadata, PluginSignature, ShellError, SignalAction, Span, Spanned, Value,
|
||||
PipelineMetadata, PluginMetadata, PluginSignature, ShellError, SignalAction, Span, Spanned,
|
||||
Value,
|
||||
};
|
||||
use nu_utils::SharedCow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
@ -78,7 +79,7 @@ pub enum PipelineDataHeader {
|
|||
/// No input
|
||||
Empty,
|
||||
/// A single value
|
||||
Value(Value),
|
||||
Value(Value, Option<PipelineMetadata>),
|
||||
/// Initiate [`nu_protocol::PipelineData::ListStream`].
|
||||
///
|
||||
/// Items are sent via [`StreamData`]
|
||||
|
@ -94,11 +95,23 @@ impl PipelineDataHeader {
|
|||
pub fn stream_id(&self) -> Option<StreamId> {
|
||||
match self {
|
||||
PipelineDataHeader::Empty => None,
|
||||
PipelineDataHeader::Value(_) => None,
|
||||
PipelineDataHeader::Value(_, _) => None,
|
||||
PipelineDataHeader::ListStream(info) => Some(info.id),
|
||||
PipelineDataHeader::ByteStream(info) => Some(info.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value(value: Value) -> Self {
|
||||
PipelineDataHeader::Value(value, None)
|
||||
}
|
||||
|
||||
pub fn list_stream(info: ListStreamInfo) -> Self {
|
||||
PipelineDataHeader::ListStream(info)
|
||||
}
|
||||
|
||||
pub fn byte_stream(info: ByteStreamInfo) -> Self {
|
||||
PipelineDataHeader::ByteStream(info)
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional information about list (value) streams
|
||||
|
@ -106,6 +119,18 @@ impl PipelineDataHeader {
|
|||
pub struct ListStreamInfo {
|
||||
pub id: StreamId,
|
||||
pub span: Span,
|
||||
pub metadata: Option<PipelineMetadata>,
|
||||
}
|
||||
|
||||
impl ListStreamInfo {
|
||||
/// Create a new `ListStreamInfo` with a unique ID
|
||||
pub fn new(id: StreamId, span: Span) -> Self {
|
||||
ListStreamInfo {
|
||||
id,
|
||||
span,
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Additional information about byte streams
|
||||
|
@ -115,6 +140,19 @@ pub struct ByteStreamInfo {
|
|||
pub span: Span,
|
||||
#[serde(rename = "type")]
|
||||
pub type_: ByteStreamType,
|
||||
pub metadata: Option<PipelineMetadata>,
|
||||
}
|
||||
|
||||
impl ByteStreamInfo {
|
||||
/// Create a new `ByteStreamInfo` with a unique ID
|
||||
pub fn new(id: StreamId, span: Span, type_: ByteStreamType) -> Self {
|
||||
ByteStreamInfo {
|
||||
id,
|
||||
span,
|
||||
type_,
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls that a plugin can execute. The type parameter determines the input type.
|
||||
|
@ -344,7 +382,7 @@ impl PluginCallResponse<PipelineDataHeader> {
|
|||
if value.is_nothing() {
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::Empty)
|
||||
} else {
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::Value(value))
|
||||
PluginCallResponse::PipelineData(PipelineDataHeader::value(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,10 +55,7 @@ fn manager_consume_all_exits_after_streams_and_interfaces_are_dropped() -> Resul
|
|||
|
||||
// Create a stream...
|
||||
let stream = manager.read_pipeline_data(
|
||||
PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
PipelineDataHeader::list_stream(ListStreamInfo::new(0, Span::test_data())),
|
||||
&Signals::empty(),
|
||||
)?;
|
||||
|
||||
|
@ -111,10 +108,7 @@ fn manager_consume_all_propagates_io_error_to_readers() -> Result<(), ShellError
|
|||
test.set_read_error(test_io_error());
|
||||
|
||||
let stream = manager.read_pipeline_data(
|
||||
PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
PipelineDataHeader::list_stream(ListStreamInfo::new(0, Span::test_data())),
|
||||
&Signals::empty(),
|
||||
)?;
|
||||
|
||||
|
@ -157,11 +151,11 @@ fn manager_consume_all_propagates_message_error_to_readers() -> Result<(), Shell
|
|||
test.add(invalid_input());
|
||||
|
||||
let stream = manager.read_pipeline_data(
|
||||
PipelineDataHeader::ByteStream(ByteStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
type_: ByteStreamType::Unknown,
|
||||
}),
|
||||
PipelineDataHeader::byte_stream(ByteStreamInfo::new(
|
||||
0,
|
||||
Span::test_data(),
|
||||
ByteStreamType::Unknown,
|
||||
)),
|
||||
&Signals::empty(),
|
||||
)?;
|
||||
|
||||
|
@ -414,10 +408,7 @@ fn manager_consume_call_run_forwards_to_receiver_with_pipeline_data() -> Result<
|
|||
positional: vec![],
|
||||
named: vec![],
|
||||
},
|
||||
input: PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 6,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
input: PipelineDataHeader::list_stream(ListStreamInfo::new(6, Span::test_data())),
|
||||
}),
|
||||
))?;
|
||||
|
||||
|
@ -556,10 +547,10 @@ fn manager_consume_engine_call_response_forwards_to_subscriber_with_pipeline_dat
|
|||
|
||||
manager.consume(PluginInput::EngineCallResponse(
|
||||
0,
|
||||
EngineCallResponse::PipelineData(PipelineDataHeader::ListStream(ListStreamInfo {
|
||||
id: 0,
|
||||
span: Span::test_data(),
|
||||
})),
|
||||
EngineCallResponse::PipelineData(PipelineDataHeader::list_stream(ListStreamInfo::new(
|
||||
0,
|
||||
Span::test_data(),
|
||||
))),
|
||||
))?;
|
||||
|
||||
for i in 0..2 {
|
||||
|
@ -707,7 +698,7 @@ fn interface_write_response_with_value() -> Result<(), ShellError> {
|
|||
assert_eq!(33, id, "id");
|
||||
match response {
|
||||
PluginCallResponse::PipelineData(header) => match header {
|
||||
PipelineDataHeader::Value(value) => assert_eq!(6, value.as_int()?),
|
||||
PipelineDataHeader::Value(value, _) => assert_eq!(6, value.as_int()?),
|
||||
_ => panic!("unexpected pipeline data header: {header:?}"),
|
||||
},
|
||||
_ => panic!("unexpected response: {response:?}"),
|
||||
|
|
|
@ -1172,20 +1172,25 @@ mod test_cwd {
|
|||
engine::{EngineState, Stack},
|
||||
Span, Value,
|
||||
};
|
||||
use nu_path::assert_path_eq;
|
||||
use std::path::Path;
|
||||
use nu_path::{assert_path_eq, AbsolutePath, Path};
|
||||
use tempfile::{NamedTempFile, TempDir};
|
||||
|
||||
/// Creates a symlink. Works on both Unix and Windows.
|
||||
#[cfg(any(unix, windows))]
|
||||
fn symlink(original: impl AsRef<Path>, link: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
fn symlink(
|
||||
original: impl AsRef<AbsolutePath>,
|
||||
link: impl AsRef<AbsolutePath>,
|
||||
) -> std::io::Result<()> {
|
||||
let original = original.as_ref();
|
||||
let link = link.as_ref();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::os::unix::fs::symlink(original, link)
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if original.as_ref().is_dir() {
|
||||
if original.is_dir() {
|
||||
std::os::windows::fs::symlink_dir(original, link)
|
||||
} else {
|
||||
std::os::windows::fs::symlink_file(original, link)
|
||||
|
@ -1198,10 +1203,7 @@ mod test_cwd {
|
|||
let mut engine_state = EngineState::new();
|
||||
engine_state.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::String {
|
||||
val: path.as_ref().to_string_lossy().to_string(),
|
||||
internal_span: Span::unknown(),
|
||||
},
|
||||
Value::test_string(path.as_ref().to_str().unwrap()),
|
||||
);
|
||||
engine_state
|
||||
}
|
||||
|
@ -1211,10 +1213,7 @@ mod test_cwd {
|
|||
let mut stack = Stack::new();
|
||||
stack.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::String {
|
||||
val: path.as_ref().to_string_lossy().to_string(),
|
||||
internal_span: Span::unknown(),
|
||||
},
|
||||
Value::test_string(path.as_ref().to_str().unwrap()),
|
||||
);
|
||||
stack
|
||||
}
|
||||
|
@ -1292,9 +1291,12 @@ mod test_cwd {
|
|||
#[test]
|
||||
fn pwd_points_to_symlink_to_file() {
|
||||
let file = NamedTempFile::new().unwrap();
|
||||
let temp_file = AbsolutePath::try_new(file.path()).unwrap();
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(file.path(), &link).unwrap();
|
||||
let temp = AbsolutePath::try_new(dir.path()).unwrap();
|
||||
|
||||
let link = temp.join("link");
|
||||
symlink(temp_file, &link).unwrap();
|
||||
let engine_state = engine_state_with_pwd(&link);
|
||||
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
|
@ -1303,8 +1305,10 @@ mod test_cwd {
|
|||
#[test]
|
||||
fn pwd_points_to_symlink_to_directory() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(dir.path(), &link).unwrap();
|
||||
let temp = AbsolutePath::try_new(dir.path()).unwrap();
|
||||
|
||||
let link = temp.join("link");
|
||||
symlink(temp, &link).unwrap();
|
||||
let engine_state = engine_state_with_pwd(&link);
|
||||
|
||||
let cwd = engine_state.cwd(None).unwrap();
|
||||
|
@ -1314,10 +1318,15 @@ mod test_cwd {
|
|||
#[test]
|
||||
fn pwd_points_to_broken_symlink() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(TempDir::new().unwrap().path(), &link).unwrap();
|
||||
let temp = AbsolutePath::try_new(dir.path()).unwrap();
|
||||
let other_dir = TempDir::new().unwrap();
|
||||
let other_temp = AbsolutePath::try_new(other_dir.path()).unwrap();
|
||||
|
||||
let link = temp.join("link");
|
||||
symlink(other_temp, &link).unwrap();
|
||||
let engine_state = engine_state_with_pwd(&link);
|
||||
|
||||
drop(other_dir);
|
||||
engine_state.cwd(None).unwrap_err();
|
||||
}
|
||||
|
||||
|
@ -1360,12 +1369,14 @@ mod test_cwd {
|
|||
|
||||
#[test]
|
||||
fn stack_pwd_points_to_normal_directory_with_symlink_components() {
|
||||
// `/tmp/dir/link` points to `/tmp/dir`, then we set PWD to `/tmp/dir/link/foo`
|
||||
let dir = TempDir::new().unwrap();
|
||||
let link = dir.path().join("link");
|
||||
symlink(dir.path(), &link).unwrap();
|
||||
let temp = AbsolutePath::try_new(dir.path()).unwrap();
|
||||
|
||||
// `/tmp/dir/link` points to `/tmp/dir`, then we set PWD to `/tmp/dir/link/foo`
|
||||
let link = temp.join("link");
|
||||
symlink(temp, &link).unwrap();
|
||||
let foo = link.join("foo");
|
||||
std::fs::create_dir(dir.path().join("foo")).unwrap();
|
||||
std::fs::create_dir(temp.join("foo")).unwrap();
|
||||
let engine_state = EngineState::new();
|
||||
let stack = stack_with_pwd(&foo);
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Metadata that is valid for the whole [`PipelineData`](crate::PipelineData)
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct PipelineMetadata {
|
||||
pub data_source: DataSource,
|
||||
pub content_type: Option<String>,
|
||||
|
@ -27,7 +29,7 @@ impl PipelineMetadata {
|
|||
///
|
||||
/// This can either be a particular family of commands (useful so downstream commands can adjust
|
||||
/// the presentation e.g. `Ls`) or the opened file to protect against overwrite-attempts properly.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub enum DataSource {
|
||||
Ls,
|
||||
HtmlThemes,
|
||||
|
|
|
@ -1223,7 +1223,8 @@ impl Value {
|
|||
for val in vals.iter_mut() {
|
||||
match val {
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
let record = record.to_mut();
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
val.upsert_data_at_cell_path(path, new_val.clone())?;
|
||||
} else {
|
||||
let new_col = if path.is_empty() {
|
||||
|
@ -1235,7 +1236,7 @@ impl Value {
|
|||
.upsert_data_at_cell_path(path, new_val.clone())?;
|
||||
new_col
|
||||
};
|
||||
record.to_mut().push(col_name, new_col);
|
||||
record.push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error.clone()),
|
||||
|
@ -1250,7 +1251,8 @@ impl Value {
|
|||
}
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
let record = record.to_mut();
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
val.upsert_data_at_cell_path(path, new_val)?;
|
||||
} else {
|
||||
let new_col = if path.is_empty() {
|
||||
|
@ -1260,7 +1262,7 @@ impl Value {
|
|||
new_col.upsert_data_at_cell_path(path, new_val)?;
|
||||
new_col
|
||||
};
|
||||
record.to_mut().push(col_name, new_col);
|
||||
record.push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error.clone()),
|
||||
|
@ -1591,7 +1593,8 @@ impl Value {
|
|||
let v_span = val.span();
|
||||
match val {
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
let record = record.to_mut();
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if path.is_empty() {
|
||||
return Err(ShellError::ColumnAlreadyExists {
|
||||
col_name: col_name.clone(),
|
||||
|
@ -1618,7 +1621,7 @@ impl Value {
|
|||
)?;
|
||||
new_col
|
||||
};
|
||||
record.to_mut().push(col_name, new_col);
|
||||
record.push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error.clone()),
|
||||
|
@ -1634,7 +1637,8 @@ impl Value {
|
|||
}
|
||||
}
|
||||
Value::Record { val: record, .. } => {
|
||||
if let Some(val) = record.to_mut().get_mut(col_name) {
|
||||
let record = record.to_mut();
|
||||
if let Some(val) = record.get_mut(col_name) {
|
||||
if path.is_empty() {
|
||||
return Err(ShellError::ColumnAlreadyExists {
|
||||
col_name: col_name.clone(),
|
||||
|
@ -1652,7 +1656,7 @@ impl Value {
|
|||
new_col.insert_data_at_cell_path(path, new_val, head_span)?;
|
||||
new_col
|
||||
};
|
||||
record.to_mut().push(col_name, new_col);
|
||||
record.push(col_name, new_col);
|
||||
}
|
||||
}
|
||||
other => {
|
||||
|
|
|
@ -35,7 +35,7 @@ pub fn line_ending() -> String {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn files_exist_at(files: Vec<impl AsRef<Path>>, path: impl AsRef<AbsolutePath>) -> bool {
|
||||
pub fn files_exist_at(files: &[impl AsRef<Path>], path: impl AsRef<AbsolutePath>) -> bool {
|
||||
let path = path.as_ref();
|
||||
files.iter().all(|f| path.join(f.as_ref()).exists())
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# language without adding any extra dependencies to our tests.
|
||||
|
||||
const NUSHELL_VERSION = "0.96.2"
|
||||
const PLUGIN_VERSION = "0.1.0" # bump if you change commands!
|
||||
const PLUGIN_VERSION = "0.1.1" # bump if you change commands!
|
||||
|
||||
def main [--stdio] {
|
||||
if ($stdio) {
|
||||
|
@ -133,7 +133,7 @@ def process_call [
|
|||
|
||||
# Create a Value of type List that will be encoded and sent to Nushell
|
||||
let value = {
|
||||
Value: {
|
||||
Value: [{
|
||||
List: {
|
||||
vals: (0..9 | each { |x|
|
||||
{
|
||||
|
@ -157,7 +157,7 @@ def process_call [
|
|||
}),
|
||||
span: $span
|
||||
}
|
||||
}
|
||||
}, null]
|
||||
}
|
||||
|
||||
write_response $id { PipelineData: $value }
|
||||
|
@ -265,4 +265,4 @@ def start_plugin [] {
|
|||
}) |
|
||||
each { from json | handle_input } |
|
||||
ignore
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import json
|
|||
|
||||
|
||||
NUSHELL_VERSION = "0.96.2"
|
||||
PLUGIN_VERSION = "0.1.0" # bump if you change commands!
|
||||
PLUGIN_VERSION = "0.1.1" # bump if you change commands!
|
||||
|
||||
|
||||
def signatures():
|
||||
|
@ -125,31 +125,31 @@ def process_call(id, plugin_call):
|
|||
span = plugin_call["call"]["head"]
|
||||
|
||||
# Creates a Value of type List that will be encoded and sent to Nushell
|
||||
def f(x, y): return {
|
||||
"Int": {
|
||||
"val": x * y,
|
||||
"span": span
|
||||
}
|
||||
}
|
||||
def f(x, y):
|
||||
return {"Int": {"val": x * y, "span": span}}
|
||||
|
||||
value = {
|
||||
"Value": {
|
||||
"List": {
|
||||
"vals": [
|
||||
{
|
||||
"Record": {
|
||||
"val": {
|
||||
"one": f(x, 0),
|
||||
"two": f(x, 1),
|
||||
"three": f(x, 2),
|
||||
},
|
||||
"span": span
|
||||
"Value": [
|
||||
{
|
||||
"List": {
|
||||
"vals": [
|
||||
{
|
||||
"Record": {
|
||||
"val": {
|
||||
"one": f(x, 0),
|
||||
"two": f(x, 1),
|
||||
"three": f(x, 2),
|
||||
},
|
||||
"span": span,
|
||||
}
|
||||
}
|
||||
} for x in range(0, 10)
|
||||
],
|
||||
"span": span
|
||||
}
|
||||
}
|
||||
for x in range(0, 10)
|
||||
],
|
||||
"span": span,
|
||||
}
|
||||
},
|
||||
None,
|
||||
]
|
||||
}
|
||||
|
||||
write_response(id, {"PipelineData": value})
|
||||
|
@ -172,7 +172,7 @@ def tell_nushell_hello():
|
|||
"Hello": {
|
||||
"protocol": "nu-plugin", # always this value
|
||||
"version": NUSHELL_VERSION,
|
||||
"features": []
|
||||
"features": [],
|
||||
}
|
||||
}
|
||||
sys.stdout.write(json.dumps(hello))
|
||||
|
@ -200,22 +200,26 @@ def write_error(id, text, span=None):
|
|||
Use this error format to send errors to nushell in response to a plugin call. The ID of the
|
||||
plugin call is required.
|
||||
"""
|
||||
error = {
|
||||
"Error": {
|
||||
"msg": "ERROR from plugin",
|
||||
"labels": [
|
||||
{
|
||||
"text": text,
|
||||
"span": span,
|
||||
}
|
||||
],
|
||||
error = (
|
||||
{
|
||||
"Error": {
|
||||
"msg": "ERROR from plugin",
|
||||
"labels": [
|
||||
{
|
||||
"text": text,
|
||||
"span": span,
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
} if span is not None else {
|
||||
"Error": {
|
||||
"msg": "ERROR from plugin",
|
||||
"help": text,
|
||||
if span is not None
|
||||
else {
|
||||
"Error": {
|
||||
"msg": "ERROR from plugin",
|
||||
"help": text,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
write_response(id, error)
|
||||
|
||||
|
||||
|
@ -230,11 +234,14 @@ def handle_input(input):
|
|||
elif "Call" in input:
|
||||
[id, plugin_call] = input["Call"]
|
||||
if plugin_call == "Metadata":
|
||||
write_response(id, {
|
||||
"Metadata": {
|
||||
"version": PLUGIN_VERSION,
|
||||
}
|
||||
})
|
||||
write_response(
|
||||
id,
|
||||
{
|
||||
"Metadata": {
|
||||
"version": PLUGIN_VERSION,
|
||||
}
|
||||
},
|
||||
)
|
||||
elif plugin_call == "Signature":
|
||||
write_response(id, signatures())
|
||||
elif "Run" in plugin_call:
|
||||
|
@ -258,4 +265,4 @@ if __name__ == "__main__":
|
|||
if len(sys.argv) == 2 and sys.argv[1] == "--stdio":
|
||||
plugin()
|
||||
else:
|
||||
print("Run me from inside nushell!")
|
||||
print("Run me from inside nushell!")
|
||||
|
|
|
@ -99,29 +99,11 @@ pub fn web_examples() -> Vec<Example<'static>> {
|
|||
}
|
||||
|
||||
pub struct Selector {
|
||||
pub query: String,
|
||||
pub query: Spanned<String>,
|
||||
pub as_html: bool,
|
||||
pub attribute: Value,
|
||||
pub as_table: Value,
|
||||
pub inspect: bool,
|
||||
}
|
||||
|
||||
impl Selector {
|
||||
pub fn new() -> Selector {
|
||||
Selector {
|
||||
query: String::new(),
|
||||
as_html: false,
|
||||
attribute: Value::string("".to_string(), Span::unknown()),
|
||||
as_table: Value::string("".to_string(), Span::unknown()),
|
||||
inspect: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Selector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
pub inspect: Spanned<bool>,
|
||||
}
|
||||
|
||||
pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
|
||||
|
@ -136,43 +118,46 @@ pub fn parse_selector_params(call: &EvaluatedCall, input: &Value) -> Result<Valu
|
|||
.unwrap_or_else(|| Value::nothing(head));
|
||||
|
||||
let inspect = call.has_flag("inspect")?;
|
||||
|
||||
if let Some(query) = &query {
|
||||
if let Err(err) = ScraperSelector::parse(&query.item) {
|
||||
return Err(LabeledError::new("CSS query parse error")
|
||||
.with_label(err.to_string(), query.span)
|
||||
.with_help("cannot parse this query as a valid CSS selector"));
|
||||
}
|
||||
}
|
||||
let inspect_span = call.get_flag_span("inspect").unwrap_or(call.head);
|
||||
|
||||
let selector = Selector {
|
||||
query: query.map(|q| q.item).unwrap_or_default(),
|
||||
query: query.unwrap_or(Spanned {
|
||||
span: call.head,
|
||||
item: "".to_owned(),
|
||||
}),
|
||||
as_html,
|
||||
attribute,
|
||||
as_table,
|
||||
inspect,
|
||||
inspect: Spanned {
|
||||
item: inspect,
|
||||
span: inspect_span,
|
||||
},
|
||||
};
|
||||
|
||||
let span = input.span();
|
||||
match input {
|
||||
Value::String { val, .. } => Ok(begin_selector_query(val.to_string(), selector, span)),
|
||||
Value::String { val, .. } => begin_selector_query(val.to_string(), selector, span),
|
||||
_ => Err(LabeledError::new("Requires text input")
|
||||
.with_label("expected text from pipeline", span)),
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_selector_query(input_html: String, selector: Selector, span: Span) -> Value {
|
||||
fn begin_selector_query(
|
||||
input_html: String,
|
||||
selector: Selector,
|
||||
span: Span,
|
||||
) -> Result<Value, LabeledError> {
|
||||
if let Value::List { .. } = selector.as_table {
|
||||
return retrieve_tables(
|
||||
retrieve_tables(
|
||||
input_html.as_str(),
|
||||
&selector.as_table,
|
||||
selector.inspect,
|
||||
selector.inspect.item,
|
||||
span,
|
||||
);
|
||||
)
|
||||
} else if selector.attribute.is_empty() {
|
||||
execute_selector_query(
|
||||
input_html.as_str(),
|
||||
selector.query.as_str(),
|
||||
selector.query,
|
||||
selector.as_html,
|
||||
selector.inspect,
|
||||
span,
|
||||
|
@ -180,7 +165,7 @@ fn begin_selector_query(input_html: String, selector: Selector, span: Span) -> V
|
|||
} else if let Value::List { .. } = selector.attribute {
|
||||
execute_selector_query_with_attributes(
|
||||
input_html.as_str(),
|
||||
selector.query.as_str(),
|
||||
selector.query,
|
||||
&selector.attribute,
|
||||
selector.inspect,
|
||||
span,
|
||||
|
@ -188,7 +173,7 @@ fn begin_selector_query(input_html: String, selector: Selector, span: Span) -> V
|
|||
} else {
|
||||
execute_selector_query_with_attribute(
|
||||
input_html.as_str(),
|
||||
selector.query.as_str(),
|
||||
selector.query,
|
||||
selector.attribute.as_str().unwrap_or(""),
|
||||
selector.inspect,
|
||||
span,
|
||||
|
@ -201,7 +186,7 @@ pub fn retrieve_tables(
|
|||
columns: &Value,
|
||||
inspect_mode: bool,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
) -> Result<Value, LabeledError> {
|
||||
let html = input_string;
|
||||
let mut cols: Vec<String> = Vec::new();
|
||||
if let Value::List { vals, .. } = &columns {
|
||||
|
@ -228,11 +213,15 @@ pub fn retrieve_tables(
|
|||
};
|
||||
|
||||
if tables.len() == 1 {
|
||||
return retrieve_table(
|
||||
tables.into_iter().next().expect("Error retrieving table"),
|
||||
return Ok(retrieve_table(
|
||||
tables.into_iter().next().ok_or_else(|| {
|
||||
LabeledError::new("Cannot retrieve table")
|
||||
.with_label("Error retrieving table.", span)
|
||||
.with_help("No table found.")
|
||||
})?,
|
||||
columns,
|
||||
span,
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
let vals = tables
|
||||
|
@ -240,7 +229,7 @@ pub fn retrieve_tables(
|
|||
.map(move |table| retrieve_table(table, columns, span))
|
||||
.collect();
|
||||
|
||||
Value::list(vals, span)
|
||||
Ok(Value::list(vals, span))
|
||||
}
|
||||
|
||||
fn retrieve_table(mut table: WebTable, columns: &Value, span: Span) -> Value {
|
||||
|
@ -323,15 +312,15 @@ fn retrieve_table(mut table: WebTable, columns: &Value, span: Span) -> Value {
|
|||
|
||||
fn execute_selector_query_with_attribute(
|
||||
input_string: &str,
|
||||
query_string: &str,
|
||||
query_string: Spanned<String>,
|
||||
attribute: &str,
|
||||
inspect: bool,
|
||||
inspect: Spanned<bool>,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
) -> Result<Value, LabeledError> {
|
||||
let doc = Html::parse_fragment(input_string);
|
||||
|
||||
let vals: Vec<Value> = doc
|
||||
.select(&css(query_string, inspect))
|
||||
.select(&fallible_css(query_string, inspect)?)
|
||||
.map(|selection| {
|
||||
Value::string(
|
||||
selection.value().attr(attribute).unwrap_or("").to_string(),
|
||||
|
@ -339,16 +328,16 @@ fn execute_selector_query_with_attribute(
|
|||
)
|
||||
})
|
||||
.collect();
|
||||
Value::list(vals, span)
|
||||
Ok(Value::list(vals, span))
|
||||
}
|
||||
|
||||
fn execute_selector_query_with_attributes(
|
||||
input_string: &str,
|
||||
query_string: &str,
|
||||
query_string: Spanned<String>,
|
||||
attributes: &Value,
|
||||
inspect: bool,
|
||||
inspect: Spanned<bool>,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
) -> Result<Value, LabeledError> {
|
||||
let doc = Html::parse_fragment(input_string);
|
||||
|
||||
let mut attrs: Vec<String> = Vec::new();
|
||||
|
@ -361,7 +350,7 @@ fn execute_selector_query_with_attributes(
|
|||
}
|
||||
|
||||
let vals: Vec<Value> = doc
|
||||
.select(&css(query_string, inspect))
|
||||
.select(&fallible_css(query_string, inspect)?)
|
||||
.map(|selection| {
|
||||
let mut record = Record::new();
|
||||
for attr in &attrs {
|
||||
|
@ -373,25 +362,25 @@ fn execute_selector_query_with_attributes(
|
|||
Value::record(record, span)
|
||||
})
|
||||
.collect();
|
||||
Value::list(vals, span)
|
||||
Ok(Value::list(vals, span))
|
||||
}
|
||||
|
||||
fn execute_selector_query(
|
||||
input_string: &str,
|
||||
query_string: &str,
|
||||
query_string: Spanned<String>,
|
||||
as_html: bool,
|
||||
inspect: bool,
|
||||
inspect: Spanned<bool>,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
) -> Result<Value, LabeledError> {
|
||||
let doc = Html::parse_fragment(input_string);
|
||||
|
||||
let vals: Vec<Value> = match as_html {
|
||||
true => doc
|
||||
.select(&css(query_string, inspect))
|
||||
.select(&fallible_css(query_string, inspect)?)
|
||||
.map(|selection| Value::string(selection.html(), span))
|
||||
.collect(),
|
||||
false => doc
|
||||
.select(&css(query_string, inspect))
|
||||
.select(&fallible_css(query_string, inspect)?)
|
||||
.map(|selection| {
|
||||
Value::list(
|
||||
selection
|
||||
|
@ -404,7 +393,28 @@ fn execute_selector_query(
|
|||
.collect(),
|
||||
};
|
||||
|
||||
Value::list(vals, span)
|
||||
Ok(Value::list(vals, span))
|
||||
}
|
||||
|
||||
fn fallible_css(
|
||||
selector: Spanned<String>,
|
||||
inspect: Spanned<bool>,
|
||||
) -> Result<ScraperSelector, LabeledError> {
|
||||
if inspect.item {
|
||||
ScraperSelector::parse("html").map_err(|e| {
|
||||
LabeledError::new("CSS query parse error")
|
||||
.with_label(e.to_string(), inspect.span)
|
||||
.with_help(
|
||||
"cannot parse query `html` as a valid CSS selector, possibly an internal error",
|
||||
)
|
||||
})
|
||||
} else {
|
||||
ScraperSelector::parse(&selector.item).map_err(|e| {
|
||||
LabeledError::new("CSS query parse error")
|
||||
.with_label(e.to_string(), selector.span)
|
||||
.with_help("cannot parse query as a valid CSS selector")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn css(selector: &str, inspect: bool) -> ScraperSelector {
|
||||
|
@ -433,15 +443,23 @@ mod tests {
|
|||
<a href="https://example.com" target="_self">Example</a>
|
||||
"#;
|
||||
|
||||
fn null_spanned<T: ToOwned + ?Sized>(input: &T) -> Spanned<T::Owned> {
|
||||
Spanned {
|
||||
item: input.to_owned(),
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_first_child_is_not_empty() {
|
||||
assert!(!execute_selector_query(
|
||||
SIMPLE_LIST,
|
||||
"li:first-child",
|
||||
false,
|
||||
null_spanned("li:first-child"),
|
||||
false,
|
||||
null_spanned(&false),
|
||||
Span::test_data()
|
||||
)
|
||||
.unwrap()
|
||||
.is_empty())
|
||||
}
|
||||
|
||||
|
@ -449,11 +467,12 @@ mod tests {
|
|||
fn test_first_child() {
|
||||
let item = execute_selector_query(
|
||||
SIMPLE_LIST,
|
||||
"li:first-child",
|
||||
false,
|
||||
null_spanned("li:first-child"),
|
||||
false,
|
||||
null_spanned(&false),
|
||||
Span::test_data(),
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
let config = nu_protocol::Config::default();
|
||||
let out = item.to_expanded_string("\n", &config);
|
||||
assert_eq!("[[Coffee]]".to_string(), out)
|
||||
|
@ -463,11 +482,12 @@ mod tests {
|
|||
fn test_nested_text_nodes() {
|
||||
let item = execute_selector_query(
|
||||
NESTED_TEXT,
|
||||
"p:first-child",
|
||||
false,
|
||||
null_spanned("p:first-child"),
|
||||
false,
|
||||
null_spanned(&false),
|
||||
Span::test_data(),
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
let out = item
|
||||
.into_list()
|
||||
.unwrap()
|
||||
|
@ -492,7 +512,7 @@ mod tests {
|
|||
fn test_multiple_attributes() {
|
||||
let item = execute_selector_query_with_attributes(
|
||||
MULTIPLE_ATTRIBUTES,
|
||||
"a",
|
||||
null_spanned("a"),
|
||||
&Value::list(
|
||||
vec![
|
||||
Value::string("href".to_string(), Span::unknown()),
|
||||
|
@ -500,9 +520,10 @@ mod tests {
|
|||
],
|
||||
Span::unknown(),
|
||||
),
|
||||
false,
|
||||
null_spanned(&false),
|
||||
Span::test_data(),
|
||||
);
|
||||
)
|
||||
.unwrap();
|
||||
let out = item
|
||||
.into_list()
|
||||
.unwrap()
|
||||
|
|
|
@ -178,7 +178,7 @@ fn handle_message(
|
|||
id,
|
||||
{
|
||||
"PipelineData": {
|
||||
"Value": return_value
|
||||
"Value": [return_value, null]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use nu_path::canonicalize_with;
|
||||
use nu_path::{AbsolutePath, AbsolutePathBuf, Path};
|
||||
use nu_test_support::nu;
|
||||
use nu_test_support::playground::{Executable, Playground};
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::fs::{self, File};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
|
||||
|
@ -24,21 +23,26 @@ fn adjust_canonicalization<P: AsRef<Path>>(p: P) -> String {
|
|||
/// Make the config directory a symlink that points to a temporary folder, and also makes
|
||||
/// the nushell directory inside a symlink.
|
||||
/// Returns the path to the `nushell` config folder inside, via the symlink.
|
||||
fn setup_fake_config(playground: &mut Playground) -> PathBuf {
|
||||
let config_dir = "config_real";
|
||||
fn setup_fake_config(playground: &mut Playground) -> AbsolutePathBuf {
|
||||
let config_real = "config_real";
|
||||
let config_link = "config_link";
|
||||
let nushell_real = "nushell_real";
|
||||
let nushell_config_dir = Path::new(config_dir).join("nushell").display().to_string();
|
||||
let nushell_link = Path::new(config_real)
|
||||
.join("nushell")
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
let config_home = playground.cwd().join(config_link);
|
||||
|
||||
playground.mkdir(nushell_real);
|
||||
playground.mkdir(config_dir);
|
||||
playground.symlink(nushell_real, &nushell_config_dir);
|
||||
playground.symlink(config_dir, config_link);
|
||||
playground.with_env(
|
||||
"XDG_CONFIG_HOME",
|
||||
&playground.cwd().join(config_link).display().to_string(),
|
||||
);
|
||||
let path = Path::new(config_link).join("nushell");
|
||||
canonicalize_with(&path, playground.cwd()).unwrap_or(path)
|
||||
playground.mkdir(config_real);
|
||||
playground.symlink(nushell_real, &nushell_link);
|
||||
playground.symlink(config_real, config_link);
|
||||
playground.with_env("XDG_CONFIG_HOME", config_home.to_str().unwrap());
|
||||
|
||||
let path = config_home.join("nushell");
|
||||
path.canonicalize().map(Into::into).unwrap_or(path)
|
||||
}
|
||||
|
||||
fn run(playground: &mut Playground, command: &str) -> String {
|
||||
|
@ -79,47 +83,55 @@ fn run_interactive_stderr(xdg_config_home: impl AsRef<Path>) -> String {
|
|||
.to_string();
|
||||
}
|
||||
|
||||
fn test_config_path_helper(playground: &mut Playground, config_dir_nushell: PathBuf) {
|
||||
fn test_config_path_helper(
|
||||
playground: &mut Playground,
|
||||
config_dir_nushell: impl AsRef<AbsolutePath>,
|
||||
) {
|
||||
let config_dir_nushell = config_dir_nushell.as_ref();
|
||||
|
||||
// Create the config dir folder structure if it does not already exist
|
||||
if !config_dir_nushell.exists() {
|
||||
let _ = fs::create_dir_all(&config_dir_nushell);
|
||||
let _ = fs::create_dir_all(config_dir_nushell);
|
||||
}
|
||||
|
||||
let config_dir_nushell =
|
||||
std::fs::canonicalize(&config_dir_nushell).expect("canonicalize config dir failed");
|
||||
let config_dir_nushell = config_dir_nushell
|
||||
.canonicalize()
|
||||
.expect("canonicalize config dir failed");
|
||||
let actual = run(playground, "$nu.default-config-dir");
|
||||
assert_eq!(actual, adjust_canonicalization(&config_dir_nushell));
|
||||
|
||||
let config_path = config_dir_nushell.join("config.nu");
|
||||
// We use canonicalize here in case the config or env is symlinked since $nu.config-path is returning the canonicalized path in #8653
|
||||
let canon_config_path =
|
||||
adjust_canonicalization(std::fs::canonicalize(&config_path).unwrap_or(config_path));
|
||||
adjust_canonicalization(std::fs::canonicalize(&config_path).unwrap_or(config_path.into()));
|
||||
let actual = run(playground, "$nu.config-path");
|
||||
assert_eq!(actual, canon_config_path);
|
||||
|
||||
let env_path = config_dir_nushell.join("env.nu");
|
||||
let canon_env_path =
|
||||
adjust_canonicalization(std::fs::canonicalize(&env_path).unwrap_or(env_path));
|
||||
adjust_canonicalization(std::fs::canonicalize(&env_path).unwrap_or(env_path.into()));
|
||||
let actual = run(playground, "$nu.env-path");
|
||||
assert_eq!(actual, canon_env_path);
|
||||
|
||||
let history_path = config_dir_nushell.join("history.txt");
|
||||
let canon_history_path =
|
||||
adjust_canonicalization(std::fs::canonicalize(&history_path).unwrap_or(history_path));
|
||||
let canon_history_path = adjust_canonicalization(
|
||||
std::fs::canonicalize(&history_path).unwrap_or(history_path.into()),
|
||||
);
|
||||
let actual = run(playground, "$nu.history-path");
|
||||
assert_eq!(actual, canon_history_path);
|
||||
|
||||
let login_path = config_dir_nushell.join("login.nu");
|
||||
let canon_login_path =
|
||||
adjust_canonicalization(std::fs::canonicalize(&login_path).unwrap_or(login_path));
|
||||
adjust_canonicalization(std::fs::canonicalize(&login_path).unwrap_or(login_path.into()));
|
||||
let actual = run(playground, "$nu.loginshell-path");
|
||||
assert_eq!(actual, canon_login_path);
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
{
|
||||
let plugin_path = config_dir_nushell.join("plugin.msgpackz");
|
||||
let canon_plugin_path =
|
||||
adjust_canonicalization(std::fs::canonicalize(&plugin_path).unwrap_or(plugin_path));
|
||||
let canon_plugin_path = adjust_canonicalization(
|
||||
std::fs::canonicalize(&plugin_path).unwrap_or(plugin_path.into()),
|
||||
);
|
||||
let actual = run(playground, "$nu.plugin-path");
|
||||
assert_eq!(actual, canon_plugin_path);
|
||||
}
|
||||
|
@ -129,7 +141,7 @@ fn test_config_path_helper(playground: &mut Playground, config_dir_nushell: Path
|
|||
fn test_default_config_path() {
|
||||
Playground::setup("default_config_path", |_, playground| {
|
||||
let config_dir = nu_path::config_dir().expect("Could not get config directory");
|
||||
test_config_path_helper(playground, config_dir.join("nushell").into());
|
||||
test_config_path_helper(playground, config_dir.join("nushell"));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -152,8 +164,9 @@ fn test_default_symlink_config_path_broken_symlink_config_files() {
|
|||
|_, playground| {
|
||||
let fake_config_dir_nushell = setup_fake_config(playground);
|
||||
|
||||
let fake_dir = PathBuf::from("fake");
|
||||
playground.mkdir(&fake_dir.display().to_string());
|
||||
let fake_dir = "fake";
|
||||
playground.mkdir(fake_dir);
|
||||
let fake_dir = Path::new(fake_dir);
|
||||
|
||||
for config_file in [
|
||||
"config.nu",
|
||||
|
@ -172,7 +185,7 @@ fn test_default_symlink_config_path_broken_symlink_config_files() {
|
|||
// Windows doesn't allow creating a symlink without the file existing,
|
||||
// so we first create original files for the symlinks, then delete them
|
||||
// to break the symlinks
|
||||
std::fs::remove_dir_all(playground.cwd().join(&fake_dir)).unwrap();
|
||||
std::fs::remove_dir_all(playground.cwd().join(fake_dir)).unwrap();
|
||||
|
||||
test_config_path_helper(playground, fake_config_dir_nushell);
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue
Block a user