Compare commits

...

23 Commits

Author SHA1 Message Date
4faaa5310e Bump to 0.29 (#3230)
* Bump to 0.29

* fix test
2021-03-30 22:35:21 +13:00
c448abd44e echo $scope.aliases | pivot to see all of your aliases (#3203)
* enable ability to see all aliases, pull in code from scope branch

* add in the alias tests

* add back in my changes to variables.rs after merging in andrasios changes from PR #3217
2021-03-29 21:27:51 +13:00
2517588d7d update date to-timezone usage (#3223) 2021-03-28 18:41:42 -05:00
8fc8fc89aa History, more test coverage improvements, and refactorings. (#3217)
Improvements overall to Nu. Also among the changes here, we can also be more confident towards incorporating `3041`. End to end tests for checking envs properly exported to externals is not added here (since it's in the other PR)

A few things added in this PR (probably forgetting some too)

* no writes happen to history during test runs.
* environment syncing end to end coverage added.
* clean up / refactorings few areas.
* testing API for finer control (can write tests passing more than one pipeline)
* can pass environment variables in tests that nu will inherit when running.

* No longer needed.

* no longer under a module. No need to use super.
2021-03-27 00:08:03 -05:00
b243b3ee1d fixed typo in help text (#3216) 2021-03-26 08:11:41 -05:00
28e08afada add ability to cd to ~/blah (#3210)
* add ability to cd to ~/blah. tested on windows.

* added dirs_next

* put change behind feature for linux-minimal/wasm

* clippy

* holy crap minimal, i'm about done with you!
2021-03-26 22:29:02 +13:00
7e184b58b2 Fix warnings for Rust 1.51 (#3214)
* Fix warnings for Rust 1.51

* More fixes

* More fixes
2021-03-26 21:26:57 +13:00
589fc0b8ad add support for timestamp-based time conversion by specifying timezone (#3207)
* add support for timestamp-based time conversion by specifing timezone or 'UTC/Local'

* [fix] fix the wrong test sample

* code formating

* code formating and import missing mod to test

* code formating again
2021-03-24 08:08:23 -05:00
f0c7c1b500 fix: prompt does not find external commands #3134 (#3189)
the initial setup-commands of nushell were executed without loading environment variables
this resulted in the PATH not being available at this point until an external command was run once
which resulted in env_vars being added
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;

Co-authored-by: alexhk <alexhk@protonmail.com>
2021-03-23 22:38:07 +13:00
840bd98e01 support forward slash for directory completion in Windows (#3201)
While the "main" separator in Windows is the backslash, it supports the
forward slash as a separator too.
Add support for this so that the behavior is similar to the way Windows
PowerShell handles the forward slash: it is recognized as a separator,
and when using <tab> for path completion the slash is reversed.
2021-03-23 16:20:01 +13:00
a5cdd22bfe Add basic support for md5 hashing strings and binary data (#3197) 2021-03-21 07:48:53 +13:00
0c7bcae9b1 Update contributor-book url (#3198) 2021-03-20 23:09:17 +13:00
ab666c170c added the ability to create multi-byte unicode chars like emoji (#3195) 2021-03-18 13:42:39 -05:00
d2213d18fa Playground infraestructure (tests, etc) additions. (#3179)
* Playground infraestructure (tests, etc) additions.

A few things to note:

* Nu can be started with a custom configuration file (`nu --config-file /path/to/sample_config.toml`). Useful for mocking the configuration on test runs.
* When given a custom configuration file Nu will save any changes to the file supplied appropiately.
* The `$nu.config-path` variable either shows the default configuration file (or the custom one, if given)
* We can now run end to end tests with finer grained control (currently, since this is baseline work, standard out) This will allow to check things like exit status, assert the contents with a format, etc)

* Remove (for another PR)
2021-03-15 02:26:30 -05:00
82b6300dcb fix: cargo test failed with --release (#3183) (#3184)
Signed-off-by: nibon7 <nibon7@163.com>
2021-03-15 16:59:04 +13:00
b69cda9e07 Add --signal option to kill command (#3077) (#3079) 2021-03-15 13:10:52 +13:00
56adc7c3c6 imp: bump rustyline to 8.0.0 (#3167)
* imp: bump rustyline to 8.0.0

* fix: rustyline 8 keybindings

* fix: commands count/length test

Co-authored-by: alexhk <alexhk@protonmail.com>
2021-03-14 15:13:31 +13:00
2ace20fade Make opening a directory list its contents (#3118)
* Make opening a directory enter it.

Not sure if this change is wanted, but I'm not sure what else opening a directory could mean.
And I find myself accidentally using `open <dir>` to mean `enter <dir>`

* Add example to open directory

* Open dir should list it's contents

* Update example description and fix style
2021-03-14 10:47:31 +13:00
c13fe83784 Rename count to length (#3166)
* update docs to refer to length instead of count

* rename count to length

* change all occurrences of 'count' to 'length' in tests

* format length command
2021-03-14 10:46:40 +13:00
6cf8df8685 Move script to nu engine (#3092)
* Move run_script to engine

* Add which dep and feature to engine

* Change unwrap to expect

* Add wasm specification

* Remove which from default, add specification correctly

* Add nu-platform-specifics

* Move is_external_cmd to platform_specifics

* Add is_external_cmd to host and use it instead of nu_platform directly

* Clean up if else logic in is_external_cmd

* Bump nu-platform-specifics version

* Pass context to print_err

* Commit cargo.lock

* Move print functions to own module inside nu-engine

* Hypocratic change to run windows-nightly again

* Add import for Ordering

* Move printing of error to host

* Move platform specific which functionality to basic host

* Allow no use of cmd_name

* Fix windows compile issue
2021-03-12 18:20:54 +13:00
86a89404be fix: unicode byte counting error #3150 (#3159)
Co-authored-by: hk <alexhaka10@protonmail.com>
2021-03-12 07:11:07 +13:00
0d305d7c3e Lines no longer treats a text buffer as a line (#3153) 2021-03-11 11:35:15 +13:00
ee5bd2b4b3 move from h1-client-rustls to hyper-client (#3154) 2021-03-10 15:45:53 -06:00
203 changed files with 3601 additions and 2246 deletions

View File

@ -2,7 +2,7 @@
Welcome to nushell! Welcome to nushell!
*Note: for a more complete guide see [The nu contributor book](https://github.com/nushell/contributor-book)* *Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser. For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.

1211
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu" name = "nu"
readme = "README.md" readme = "README.md"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
version = "0.28.0" version = "0.29.0"
[workspace] [workspace]
members = ["crates/*/"] members = ["crates/*/"]
@ -18,35 +18,35 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-cli = { version = "0.28.0", path = "./crates/nu-cli", default-features = false } nu-cli = { version = "0.29.0", path = "./crates/nu-cli", default-features = false }
nu-command = { version = "0.28.0", path = "./crates/nu-command" } nu-command = { version = "0.29.0", path = "./crates/nu-command" }
nu-data = { version = "0.28.0", path = "./crates/nu-data" } nu-data = { version = "0.29.0", path = "./crates/nu-data" }
nu-engine = { version = "0.28.0", path = "./crates/nu-engine" } nu-engine = { version = "0.29.0", path = "./crates/nu-engine" }
nu-errors = { version = "0.28.0", path = "./crates/nu-errors" } nu-errors = { version = "0.29.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.28.0", path = "./crates/nu-parser" } nu-parser = { version = "0.29.0", path = "./crates/nu-parser" }
nu-plugin = { version = "0.28.0", path = "./crates/nu-plugin" } nu-plugin = { version = "0.29.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.28.0", path = "./crates/nu-protocol" } nu-protocol = { version = "0.29.0", path = "./crates/nu-protocol" }
nu-source = { version = "0.28.0", path = "./crates/nu-source" } nu-source = { version = "0.29.0", path = "./crates/nu-source" }
nu-value-ext = { version = "0.28.0", path = "./crates/nu-value-ext" } nu-value-ext = { version = "0.29.0", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = { version = "0.28.0", path = "./crates/nu_plugin_binaryview", optional = true } nu_plugin_binaryview = { version = "0.29.0", path = "./crates/nu_plugin_binaryview", optional = true }
nu_plugin_chart = { version = "0.28.0", path = "./crates/nu_plugin_chart", optional = true } nu_plugin_chart = { version = "0.29.0", path = "./crates/nu_plugin_chart", optional = true }
nu_plugin_fetch = { version = "0.28.0", path = "./crates/nu_plugin_fetch", optional = true } nu_plugin_fetch = { version = "0.29.0", path = "./crates/nu_plugin_fetch", optional = true }
nu_plugin_from_bson = { version = "0.28.0", path = "./crates/nu_plugin_from_bson", optional = true } nu_plugin_from_bson = { version = "0.29.0", path = "./crates/nu_plugin_from_bson", optional = true }
nu_plugin_from_sqlite = { version = "0.28.0", path = "./crates/nu_plugin_from_sqlite", optional = true } nu_plugin_from_sqlite = { version = "0.29.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
nu_plugin_inc = { version = "0.28.0", path = "./crates/nu_plugin_inc", optional = true } nu_plugin_inc = { version = "0.29.0", path = "./crates/nu_plugin_inc", optional = true }
nu_plugin_match = { version = "0.28.0", path = "./crates/nu_plugin_match", optional = true } nu_plugin_match = { version = "0.29.0", path = "./crates/nu_plugin_match", optional = true }
nu_plugin_post = { version = "0.28.0", path = "./crates/nu_plugin_post", optional = true } nu_plugin_post = { version = "0.29.0", path = "./crates/nu_plugin_post", optional = true }
nu_plugin_ps = { version = "0.28.0", path = "./crates/nu_plugin_ps", optional = true } nu_plugin_ps = { version = "0.29.0", path = "./crates/nu_plugin_ps", optional = true }
nu_plugin_s3 = { version = "0.28.0", path = "./crates/nu_plugin_s3", optional = true } nu_plugin_s3 = { version = "0.29.0", path = "./crates/nu_plugin_s3", optional = true }
nu_plugin_selector = { version = "0.28.0", path = "./crates/nu_plugin_selector", optional = true } nu_plugin_selector = { version = "0.29.0", path = "./crates/nu_plugin_selector", optional = true }
nu_plugin_start = { version = "0.28.0", path = "./crates/nu_plugin_start", optional = true } nu_plugin_start = { version = "0.29.0", path = "./crates/nu_plugin_start", optional = true }
nu_plugin_sys = { version = "0.28.0", path = "./crates/nu_plugin_sys", optional = true } nu_plugin_sys = { version = "0.29.0", path = "./crates/nu_plugin_sys", optional = true }
nu_plugin_textview = { version = "0.28.0", path = "./crates/nu_plugin_textview", optional = true } nu_plugin_textview = { version = "0.29.0", path = "./crates/nu_plugin_textview", optional = true }
nu_plugin_to_bson = { version = "0.28.0", path = "./crates/nu_plugin_to_bson", optional = true } nu_plugin_to_bson = { version = "0.29.0", path = "./crates/nu_plugin_to_bson", optional = true }
nu_plugin_to_sqlite = { version = "0.28.0", path = "./crates/nu_plugin_to_sqlite", optional = true } nu_plugin_to_sqlite = { version = "0.29.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
nu_plugin_tree = { version = "0.28.0", path = "./crates/nu_plugin_tree", optional = true } nu_plugin_tree = { version = "0.29.0", path = "./crates/nu_plugin_tree", optional = true }
nu_plugin_xpath = { version = "0.28.0", path = "./crates/nu_plugin_xpath", optional = true } nu_plugin_xpath = { version = "0.29.0", path = "./crates/nu_plugin_xpath", optional = true }
# Required to bootstrap the main binary # Required to bootstrap the main binary
clap = "2.33.3" clap = "2.33.3"
@ -57,9 +57,10 @@ log = "0.4.14"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
[dev-dependencies] [dev-dependencies]
nu-test-support = { version = "0.28.0", path = "./crates/nu-test-support" } nu-test-support = { version = "0.29.0", path = "./crates/nu-test-support" }
dunce = "1.0.1" dunce = "1.0.1"
serial_test = "0.5.1" serial_test = "0.5.1"
hamcrest2 = "0.3.0"
[build-dependencies] [build-dependencies]
@ -84,6 +85,7 @@ which-support = [
"nu-cli/which", "nu-cli/which",
"nu-command/ichwh", "nu-command/ichwh",
"nu-command/which", "nu-command/which",
"nu-engine/which",
] ]
default = [ default = [

View File

@ -9,7 +9,7 @@ description = "Library for ANSI terminal colors and styles (bold, underline)"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.28.0" version = "0.29.0"
[lib] [lib]
doctest = false doctest = false

View File

@ -15,7 +15,7 @@ fn main() {
let g = (col * 255 / WIDTH) as u8; let g = (col * 255 / WIDTH) as u8;
let b = 128; let b = 128;
print!("{}", Style::default().on(Color::RGB(r, g, b)).paint(" ")); print!("{}", Style::default().on(Color::Rgb(r, g, b)).paint(" "));
} }
println!(); println!();

View File

@ -103,7 +103,7 @@ impl Color {
Color::Cyan => write!(f, "36"), Color::Cyan => write!(f, "36"),
Color::White => write!(f, "37"), Color::White => write!(f, "37"),
Color::Fixed(num) => write!(f, "38;5;{}", &num), Color::Fixed(num) => write!(f, "38;5;{}", &num),
Color::RGB(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b), Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
Color::DarkGray => write!(f, "90"), Color::DarkGray => write!(f, "90"),
Color::LightRed => write!(f, "91"), Color::LightRed => write!(f, "91"),
Color::LightGreen => write!(f, "92"), Color::LightGreen => write!(f, "92"),
@ -128,7 +128,7 @@ impl Color {
Color::Cyan => write!(f, "46"), Color::Cyan => write!(f, "46"),
Color::White => write!(f, "47"), Color::White => write!(f, "47"),
Color::Fixed(num) => write!(f, "48;5;{}", &num), Color::Fixed(num) => write!(f, "48;5;{}", &num),
Color::RGB(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b), Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
Color::DarkGray => write!(f, "100"), Color::DarkGray => write!(f, "100"),
Color::LightRed => write!(f, "101"), Color::LightRed => write!(f, "101"),
Color::LightGreen => write!(f, "102"), Color::LightGreen => write!(f, "102"),
@ -372,10 +372,10 @@ mod test {
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m"); test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m");
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m"); test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m"); test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
test!(rgb: RGB(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m"); test!(rgb: Rgb(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
test!(rgb_on_blue: RGB(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m"); test!(rgb_on_blue: Rgb(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
test!(blue_on_rgb: Blue.on(RGB(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m"); test!(blue_on_rgb: Blue.on(Rgb(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
test!(rgb_on_rgb: RGB(70,130,180).on(RGB(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m"); test!(rgb_on_rgb: Rgb(70,130,180).on(Rgb(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m"); test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m");
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m"); test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m");
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m"); test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m");

View File

@ -112,7 +112,7 @@ mod test {
test!(both: style().bold().italic() => "Style { bold, italic }"); test!(both: style().bold().italic() => "Style { bold, italic }");
test!(red: Red.normal() => "Style { fg(Red) }"); test!(red: Red.normal() => "Style { fg(Red) }");
test!(redblue: Red.normal().on(RGB(3, 2, 4)) => "Style { fg(Red), on(RGB(3, 2, 4)) }"); test!(redblue: Red.normal().on(Rgb(3, 2, 4)) => "Style { fg(Red), on(Rgb(3, 2, 4)) }");
test!(everything: test!(everything:
Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() => Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() =>

View File

@ -11,7 +11,7 @@ use std::ops::Deref;
/// display that string. `ANSIString` and `ANSIByteString` are aliases for /// display that string. `ANSIString` and `ANSIByteString` are aliases for
/// this type on `str` and `\[u8]`, respectively. /// this type on `str` and `\[u8]`, respectively.
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub struct ANSIGenericString<'a, S: 'a + ToOwned + ?Sized> pub struct AnsiGenericString<'a, S: 'a + ToOwned + ?Sized>
where where
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
{ {
@ -30,12 +30,12 @@ where
/// let clone_string = plain_string.clone(); /// let clone_string = plain_string.clone();
/// assert_eq!(clone_string, plain_string); /// assert_eq!(clone_string, plain_string);
/// ``` /// ```
impl<'a, S: 'a + ToOwned + ?Sized> Clone for ANSIGenericString<'a, S> impl<'a, S: 'a + ToOwned + ?Sized> Clone for AnsiGenericString<'a, S>
where where
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
{ {
fn clone(&self) -> ANSIGenericString<'a, S> { fn clone(&self) -> AnsiGenericString<'a, S> {
ANSIGenericString { AnsiGenericString {
style: self.style, style: self.style,
string: self.string.clone(), string: self.string.clone(),
} }
@ -85,26 +85,26 @@ where
/// let plain_string = ANSIString::from("a plain string"); /// let plain_string = ANSIString::from("a plain string");
/// assert_eq!(&*plain_string, "a plain string"); /// assert_eq!(&*plain_string, "a plain string");
/// ``` /// ```
pub type ANSIString<'a> = ANSIGenericString<'a, str>; pub type AnsiString<'a> = AnsiGenericString<'a, str>;
/// An `ANSIByteString` represents a formatted series of bytes. Use /// An `AnsiByteString` represents a formatted series of bytes. Use
/// `ANSIByteString` when styling text with an unknown encoding. /// `AnsiByteString` when styling text with an unknown encoding.
pub type ANSIByteString<'a> = ANSIGenericString<'a, [u8]>; pub type AnsiByteString<'a> = AnsiGenericString<'a, [u8]>;
impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for ANSIGenericString<'a, S> impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for AnsiGenericString<'a, S>
where where
I: Into<Cow<'a, S>>, I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
{ {
fn from(input: I) -> ANSIGenericString<'a, S> { fn from(input: I) -> AnsiGenericString<'a, S> {
ANSIGenericString { AnsiGenericString {
string: input.into(), string: input.into(),
style: Style::default(), style: Style::default(),
} }
} }
} }
impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
where where
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
{ {
@ -119,7 +119,7 @@ where
} }
} }
impl<'a, S: 'a + ToOwned + ?Sized> Deref for ANSIGenericString<'a, S> impl<'a, S: 'a + ToOwned + ?Sized> Deref for AnsiGenericString<'a, S>
where where
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
{ {
@ -130,32 +130,32 @@ where
} }
} }
/// A set of `ANSIGenericString`s collected together, in order to be /// A set of `AnsiGenericStrings`s collected together, in order to be
/// written with a minimum of control characters. /// written with a minimum of control characters.
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct ANSIGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [ANSIGenericString<'a, S>]) pub struct AnsiGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [AnsiGenericString<'a, S>])
where where
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
S: PartialEq; S: PartialEq;
/// A set of `ANSIString`s collected together, in order to be written with a /// A set of `AnsiString`s collected together, in order to be written with a
/// minimum of control characters. /// minimum of control characters.
pub type ANSIStrings<'a> = ANSIGenericStrings<'a, str>; pub type AnsiStrings<'a> = AnsiGenericStrings<'a, str>;
/// A function to construct an `ANSIStrings` instance. /// A function to construct an `AnsiStrings` instance.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ANSIStrings<'a>(arg: &'a [ANSIString<'a>]) -> ANSIStrings<'a> { pub fn AnsiStrings<'a>(arg: &'a [AnsiString<'a>]) -> AnsiStrings<'a> {
ANSIGenericStrings(arg) AnsiGenericStrings(arg)
} }
/// A set of `ANSIByteString`s collected together, in order to be /// A set of `AnsiByteString`s collected together, in order to be
/// written with a minimum of control characters. /// written with a minimum of control characters.
pub type ANSIByteStrings<'a> = ANSIGenericStrings<'a, [u8]>; pub type AnsiByteStrings<'a> = AnsiGenericStrings<'a, [u8]>;
/// A function to construct an `ANSIByteStrings` instance. /// A function to construct an `ANSIByteStrings` instance.
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn ANSIByteStrings<'a>(arg: &'a [ANSIByteString<'a>]) -> ANSIByteStrings<'a> { pub fn ANSIByteStrings<'a>(arg: &'a [AnsiByteString<'a>]) -> AnsiByteStrings<'a> {
ANSIGenericStrings(arg) AnsiGenericStrings(arg)
} }
// ---- paint functions ---- // ---- paint functions ----
@ -163,12 +163,12 @@ pub fn ANSIByteStrings<'a>(arg: &'a [ANSIByteString<'a>]) -> ANSIByteStrings<'a>
impl Style { impl Style {
/// Paints the given text with this color, returning an ANSI string. /// Paints the given text with this color, returning an ANSI string.
#[must_use] #[must_use]
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S> pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
where where
I: Into<Cow<'a, S>>, I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
{ {
ANSIGenericString { AnsiGenericString {
string: input.into(), string: input.into(),
style: self, style: self,
} }
@ -185,12 +185,12 @@ impl Color {
/// println!("{}", Blue.paint("da ba dee")); /// println!("{}", Blue.paint("da ba dee"));
/// ``` /// ```
#[must_use] #[must_use]
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S> pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> AnsiGenericString<'a, S>
where where
I: Into<Cow<'a, S>>, I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
{ {
ANSIGenericString { AnsiGenericString {
string: input.into(), string: input.into(),
style: self.normal(), style: self.normal(),
} }
@ -199,14 +199,14 @@ impl Color {
// ---- writers for individual ANSI strings ---- // ---- writers for individual ANSI strings ----
impl<'a> fmt::Display for ANSIString<'a> { impl<'a> fmt::Display for AnsiString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let w: &mut dyn fmt::Write = f; let w: &mut dyn fmt::Write = f;
self.write_to_any(w) self.write_to_any(w)
} }
} }
impl<'a> ANSIByteString<'a> { impl<'a> AnsiByteString<'a> {
/// Write an `ANSIByteString` to an `io::Write`. This writes the escape /// Write an `ANSIByteString` to an `io::Write`. This writes the escape
/// sequences for the associated `Style` around the bytes. /// sequences for the associated `Style` around the bytes.
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> { pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
@ -215,7 +215,7 @@ impl<'a> ANSIByteString<'a> {
} }
} }
impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> impl<'a, S: 'a + ToOwned + ?Sized> AnsiGenericString<'a, S>
where where
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
&'a S: AsRef<[u8]>, &'a S: AsRef<[u8]>,
@ -229,14 +229,14 @@ where
// ---- writers for combined ANSI strings ---- // ---- writers for combined ANSI strings ----
impl<'a> fmt::Display for ANSIStrings<'a> { impl<'a> fmt::Display for AnsiStrings<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let f: &mut dyn fmt::Write = f; let f: &mut dyn fmt::Write = f;
self.write_to_any(f) self.write_to_any(f)
} }
} }
impl<'a> ANSIByteStrings<'a> { impl<'a> AnsiByteStrings<'a> {
/// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal /// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal
/// escape sequences for the associated `Style`s around each set of /// escape sequences for the associated `Style`s around each set of
/// bytes. /// bytes.
@ -246,7 +246,7 @@ impl<'a> ANSIByteStrings<'a> {
} }
} }
impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> ANSIGenericStrings<'a, S> impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> AnsiGenericStrings<'a, S>
where where
<S as ToOwned>::Owned: fmt::Debug, <S as ToOwned>::Owned: fmt::Debug,
&'a S: AsRef<[u8]>, &'a S: AsRef<[u8]>,
@ -289,7 +289,7 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
pub use super::super::ANSIStrings; pub use super::super::AnsiStrings;
pub use crate::style::Color::*; pub use crate::style::Color::*;
pub use crate::style::Style; pub use crate::style::Style;
@ -297,7 +297,7 @@ mod tests {
fn no_control_codes_for_plain() { fn no_control_codes_for_plain() {
let one = Style::default().paint("one"); let one = Style::default().paint("one");
let two = Style::default().paint("two"); let two = Style::default().paint("two");
let output = format!("{}", ANSIStrings(&[one, two])); let output = format!("{}", AnsiStrings(&[one, two]));
assert_eq!(&*output, "onetwo"); assert_eq!(&*output, "onetwo");
} }
} }

View File

@ -365,7 +365,7 @@ pub enum Color {
Fixed(u8), Fixed(u8),
/// A 24-bit RGB color, as specified by ISO-8613-3. /// A 24-bit RGB color, as specified by ISO-8613-3.
RGB(u8, u8, u8), Rgb(u8, u8, u8),
} }
impl Color { impl Color {

View File

@ -1,12 +1,12 @@
use crate::display::{ANSIString, ANSIStrings}; use crate::display::{AnsiString, AnsiStrings};
use std::ops::Deref; use std::ops::Deref;
/// Return a substring of the given ANSIStrings sequence, while keeping the formatting. /// Return a substring of the given ANSIStrings sequence, while keeping the formatting.
pub fn sub_string<'a>( pub fn sub_string<'a>(
start: usize, start: usize,
len: usize, len: usize,
strs: &ANSIStrings<'a>, strs: &AnsiStrings<'a>,
) -> Vec<ANSIString<'static>> { ) -> Vec<AnsiString<'static>> {
let mut vec = Vec::new(); let mut vec = Vec::new();
let mut pos = start; let mut pos = start;
let mut len_rem = len; let mut len_rem = len;
@ -39,7 +39,7 @@ pub fn sub_string<'a>(
} }
/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`. /// Return a concatenated copy of `strs` without the formatting, as an allocated `String`.
pub fn unstyle(strs: &ANSIStrings) -> String { pub fn unstyle(strs: &AnsiStrings) -> String {
let mut s = String::new(); let mut s = String::new();
for i in strs.0.iter() { for i in strs.0.iter() {
@ -50,7 +50,7 @@ pub fn unstyle(strs: &ANSIStrings) -> String {
} }
/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`. /// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`.
pub fn unstyled_len(strs: &ANSIStrings) -> usize { pub fn unstyled_len(strs: &AnsiStrings) -> usize {
let mut l = 0; let mut l = 0;
for i in strs.0.iter() { for i in strs.0.iter() {
l += i.deref().len(); l += i.deref().len();
@ -70,7 +70,7 @@ mod test {
Red.paint("-second"), Red.paint("-second"),
White.paint("-third"), White.paint("-third"),
]; ];
let a = ANSIStrings(&l); let a = AnsiStrings(&l);
assert_eq!(unstyle(&a), "first-second-third"); assert_eq!(unstyle(&a), "first-second-third");
assert_eq!(unstyled_len(&a), 18); assert_eq!(unstyled_len(&a), 18);

View File

@ -5,26 +5,26 @@ description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.28.0" version = "0.29.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-command = { version = "0.28.0", path = "../nu-command" } nu-command = { version = "0.29.0", path = "../nu-command" }
nu-data = { version = "0.28.0", path = "../nu-data" } nu-data = { version = "0.29.0", path = "../nu-data" }
nu-engine = { version = "0.28.0", path = "../nu-engine" } nu-engine = { version = "0.29.0", path = "../nu-engine" }
nu-errors = { version = "0.28.0", path = "../nu-errors" } nu-errors = { version = "0.29.0", path = "../nu-errors" }
nu-json = { version = "0.28.0", path = "../nu-json" } nu-json = { version = "0.29.0", path = "../nu-json" }
nu-parser = { version = "0.28.0", path = "../nu-parser" } nu-parser = { version = "0.29.0", path = "../nu-parser" }
nu-plugin = { version = "0.28.0", path = "../nu-plugin" } nu-plugin = { version = "0.29.0", path = "../nu-plugin" }
nu-protocol = { version = "0.28.0", path = "../nu-protocol" } nu-protocol = { version = "0.29.0", path = "../nu-protocol" }
nu-source = { version = "0.28.0", path = "../nu-source" } nu-source = { version = "0.29.0", path = "../nu-source" }
nu-stream = { version = "0.28.0", path = "../nu-stream" } nu-stream = { version = "0.29.0", path = "../nu-stream" }
nu-table = { version = "0.28.0", path = "../nu-table" } nu-table = { version = "0.29.0", path = "../nu-table" }
nu-test-support = { version = "0.28.0", path = "../nu-test-support" } nu-test-support = { version = "0.29.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.28.0", path = "../nu-value-ext" } nu-value-ext = { version = "0.29.0", path = "../nu-value-ext" }
nu-ansi-term = { version = "0.28.0", path = "../nu-ansi-term" } nu-ansi-term = { version = "0.29.0", path = "../nu-ansi-term" }
Inflector = "0.11" Inflector = "0.11"
arboard = { version = "1.1.0", optional = true } arboard = { version = "1.1.0", optional = true }
@ -77,7 +77,7 @@ rayon = "1.5.0"
regex = "1.4.3" regex = "1.4.3"
roxmltree = "0.14.0" roxmltree = "0.14.0"
rust-embed = "5.9.0" rust-embed = "5.9.0"
rustyline = { version = "6.3.0", optional = true } rustyline = { version = "8.0.0", optional = true }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
serde_bytes = "0.11.5" serde_bytes = "0.11.5"
serde_ini = "0.2.0" serde_ini = "0.2.0"

View File

@ -1,12 +1,9 @@
use crate::line_editor::configure_ctrl_c; use crate::line_editor::configure_ctrl_c;
use nu_command::commands::default_context::create_default_context; use nu_command::commands::default_context::create_default_context;
#[allow(unused_imports)] use nu_engine::{evaluation_context, run_block, script::run_script_standalone, EvaluationContext};
use nu_command::maybe_print_errors;
use nu_engine::run_block;
use nu_engine::EvaluationContext;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use nu_command::script::{process_script, LineResult}; pub(crate) use nu_engine::script::{process_script, LineResult};
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
use crate::line_editor::{ use crate::line_editor::{
@ -16,13 +13,13 @@ use crate::line_editor::{
#[allow(unused_imports)] #[allow(unused_imports)]
use nu_data::config; use nu_data::config;
use nu_source::{Tag, Text}; use nu_data::config::{Conf, NuConfig};
use nu_source::{AnchorLocation, Tag, Text};
use nu_stream::InputStream; use nu_stream::InputStream;
use std::ffi::{OsStr, OsString};
#[allow(unused_imports)] #[allow(unused_imports)]
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use nu_command::script::{print_err, run_script_standalone};
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
use rustyline::{self, error::ReadlineError}; use rustyline::{self, error::ReadlineError};
@ -36,6 +33,81 @@ use std::error::Error;
use std::iter::Iterator; use std::iter::Iterator;
use std::path::PathBuf; use std::path::PathBuf;
pub struct Options {
pub config: Option<OsString>,
pub history: Option<PathBuf>,
pub save_history: bool,
pub stdin: bool,
pub scripts: Vec<NuScript>,
}
impl Default for Options {
fn default() -> Self {
Self::new()
}
}
impl Options {
pub fn new() -> Self {
Self {
config: None,
history: None,
save_history: true,
stdin: false,
scripts: vec![],
}
}
pub fn history(&self, block: impl FnOnce(&std::path::Path)) {
if !self.save_history {
return;
}
if let Some(file) = &self.history {
block(&file)
}
}
}
pub struct NuScript {
pub filepath: Option<OsString>,
pub contents: String,
}
impl NuScript {
pub fn code<'a>(content: impl Iterator<Item = &'a str>) -> Result<Self, ShellError> {
let text = content
.map(|x| x.to_string())
.collect::<Vec<String>>()
.join("\n");
Ok(Self {
filepath: None,
contents: text,
})
}
pub fn get_code(&self) -> &str {
&self.contents
}
pub fn source_file(path: &OsStr) -> Result<Self, ShellError> {
use std::fs::File;
use std::io::Read;
let path = path.to_os_string();
let mut file = File::open(&path)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer)?;
Ok(Self {
filepath: Some(path),
contents: buffer,
})
}
}
pub fn search_paths() -> Vec<std::path::PathBuf> { pub fn search_paths() -> Vec<std::path::PathBuf> {
use std::env; use std::env;
@ -65,12 +137,9 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
search_paths search_paths
} }
pub async fn run_script_file( pub async fn run_script_file(mut options: Options) -> Result<(), Box<dyn Error>> {
file_contents: String,
redirect_stdin: bool,
) -> Result<(), Box<dyn Error>> {
let mut syncer = EnvironmentSyncer::new();
let mut context = create_default_context(false)?; let mut context = create_default_context(false)?;
let mut syncer = create_environment_syncer(&context, &mut options);
let config = syncer.get_config(); let config = syncer.get_config();
context.configure(&config, |_, ctx| { context.configure(&config, |_, ctx| {
@ -79,7 +148,7 @@ pub async fn run_script_file(
syncer.sync_path_vars(ctx); syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) { if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""), ctx); ctx.with_host(|host| host.print_err(reason, &Text::from("")));
} }
let _ = register_plugins(ctx); let _ = register_plugins(ctx);
@ -88,15 +157,62 @@ pub async fn run_script_file(
let _ = run_startup_commands(&mut context, &config).await; let _ = run_startup_commands(&mut context, &config).await;
run_script_standalone(file_contents, redirect_stdin, &context, true).await?; let script = options
.scripts
.get(0)
.ok_or_else(|| ShellError::unexpected("Nu source code not available"))?;
run_script_standalone(script.get_code().to_string(), options.stdin, &context, true).await?;
Ok(()) Ok(())
} }
/// The entry point for the CLI. Will register all known internal commands, load experimental commands, load plugins, then prepare the prompt and line reader for input. fn create_environment_syncer(
context: &EvaluationContext,
options: &mut Options,
) -> EnvironmentSyncer {
let configuration = match &options.config {
Some(config_file) => {
let location = Some(AnchorLocation::File(
config_file.to_string_lossy().to_string(),
));
let tag = Tag::unknown().anchored(location);
context.scope.add_var(
"config-path",
UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag),
);
NuConfig::with(Some(config_file.into()))
}
None => NuConfig::new(),
};
let history_path = configuration.history_path();
options.history = Some(history_path.clone());
let location = Some(AnchorLocation::File(
history_path.to_string_lossy().to_string(),
));
let tag = Tag::unknown().anchored(location);
context.scope.add_var(
"history-path",
UntaggedValue::filepath(history_path).into_value(tag),
);
EnvironmentSyncer::with_config(Box::new(configuration))
}
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> { pub async fn cli(
let mut syncer = EnvironmentSyncer::new(); mut context: EvaluationContext,
mut options: Options,
) -> Result<(), Box<dyn Error>> {
let mut syncer = create_environment_syncer(&context, &mut options);
let configuration = syncer.get_config(); let configuration = syncer.get_config();
let mut rl = default_rustyline_editor_configuration(); let mut rl = default_rustyline_editor_configuration();
@ -107,7 +223,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
syncer.sync_path_vars(ctx); syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) { if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""), ctx); ctx.with_host(|host| host.print_err(reason, &Text::from("")));
} }
let _ = configure_ctrl_c(ctx); let _ = configure_ctrl_c(ctx);
@ -134,8 +250,9 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
// Give ourselves a scope to work in // Give ourselves a scope to work in
context.scope.enter_scope(); context.scope.enter_scope();
let history_path = nu_engine::history_path(&configuration); options.history(|file| {
let _ = rl.load_history(&history_path); let _ = rl.load_history(&file);
});
let mut session_text = String::new(); let mut session_text = String::new();
let mut line_start: usize = 0; let mut line_start: usize = 0;
@ -171,6 +288,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
let prompt_line = prompt.as_string()?; let prompt_line = prompt.as_string()?;
context.scope.enter_scope(); context.scope.enter_scope();
let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope); let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
prompt_block.set_redirect(ExternalRedirection::Stdout); prompt_block.set_redirect(ExternalRedirection::Stdout);
@ -180,8 +298,6 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch()) format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
} else { } else {
// let env = context.get_env();
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await; let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;
context.scope.exit_scope(); context.scope.exit_scope();
@ -189,7 +305,10 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
Ok(result) => match result.collect_string(Tag::unknown()).await { Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => { Ok(string_result) => {
let errors = context.get_errors(); let errors = context.get_errors();
maybe_print_errors(&context, Text::from(prompt_line)); evaluation_context::maybe_print_errors(
&context,
Text::from(prompt_line),
);
context.clear_errors(); context.clear_errors();
if !errors.is_empty() { if !errors.is_empty() {
@ -199,14 +318,14 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
} }
} }
Err(e) => { Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line), &context); context.host.lock().print_err(e, &Text::from(prompt_line));
context.clear_errors(); context.clear_errors();
"> ".to_string() "> ".to_string()
} }
}, },
Err(e) => { Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line), &context); context.host.lock().print_err(e, &Text::from(prompt_line));
context.clear_errors(); context.clear_errors();
"> ".to_string() "> ".to_string()
@ -274,7 +393,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
} }
if let Err(reason) = syncer.autoenv(ctx) { if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""), ctx); ctx.with_host(|host| host.print_err(reason, &Text::from("")));
} }
let _ = configure_rustyline_editor(&mut rl, config); let _ = configure_rustyline_editor(&mut rl, config);
@ -282,28 +401,33 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
match line { match line {
LineResult::Success(line) => { LineResult::Success(line) => {
rl.add_history_entry(&line); options.history(|file| {
let _ = rl.save_history(&history_path); rl.add_history_entry(&line);
maybe_print_errors(&context, Text::from(session_text.clone())); let _ = rl.save_history(&file);
});
evaluation_context::maybe_print_errors(&context, Text::from(session_text.clone()));
} }
LineResult::ClearHistory => { LineResult::ClearHistory => {
rl.clear_history(); options.history(|file| {
let _ = rl.save_history(&history_path); rl.clear_history();
let _ = rl.save_history(&file);
});
} }
LineResult::Error(line, err) => { LineResult::Error(line, reason) => {
rl.add_history_entry(&line); options.history(|file| {
let _ = rl.save_history(&history_path); rl.add_history_entry(&line);
let _ = rl.save_history(&file);
});
print_err(err, &Text::from(session_text.clone()), &context); context.with_host(|host| host.print_err(reason, &Text::from(session_text.clone())));
maybe_print_errors(&context, Text::from(session_text.clone()));
} }
LineResult::CtrlC => { LineResult::CtrlC => {
let config_ctrlc_exit = config::config(Tag::unknown())? let config_ctrlc_exit = configuration
.get("ctrlc_exit") .var("ctrlc_exit")
.map(|s| s.value.is_true()) .map(|s| s.value.is_true())
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells .unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
@ -312,7 +436,10 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
} }
if ctrlcbreak { if ctrlcbreak {
let _ = rl.save_history(&history_path); options.history(|file| {
let _ = rl.save_history(&file);
});
std::process::exit(0); std::process::exit(0);
} else { } else {
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)")); context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
@ -336,7 +463,9 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
} }
// we are ok if we can not save history // we are ok if we can not save history
let _ = rl.save_history(&history_path); options.history(|file| {
let _ = rl.save_history(&file);
});
Ok(()) Ok(())
} }
@ -426,14 +555,14 @@ fn current_branch() -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use nu_engine::basic_evaluation_context; use nu_engine::EvaluationContext;
#[quickcheck] #[quickcheck]
fn quickcheck_parse(data: String) -> bool { fn quickcheck_parse(data: String) -> bool {
let (tokens, err) = nu_parser::lex(&data, 0); let (tokens, err) = nu_parser::lex(&data, 0);
let (lite_block, err2) = nu_parser::parse_block(tokens); let (lite_block, err2) = nu_parser::parse_block(tokens);
if err.is_none() && err2.is_none() { if err.is_none() && err2.is_none() {
let context = basic_evaluation_context().unwrap(); let context = EvaluationContext::basic().unwrap();
let _ = nu_parser::classify_block(&lite_block, &context.scope); let _ = nu_parser::classify_block(&lite_block, &context.scope);
} }
true true

View File

@ -15,7 +15,8 @@ pub struct PathSuggestion {
impl PathCompleter { impl PathCompleter {
pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> { pub fn path_suggestions(&self, partial: &str, matcher: &dyn Matcher) -> Vec<PathSuggestion> {
let expanded = nu_parser::expand_ndots(partial); let expanded = nu_parser::expand_ndots(partial);
let expanded = expanded.as_ref(); let expanded = expanded.replace(std::path::is_separator, &SEP.to_string());
let expanded: &str = expanded.as_ref();
let (base_dir_name, partial) = match expanded.rfind(SEP) { let (base_dir_name, partial) = match expanded.rfind(SEP) {
Some(pos) => expanded.split_at(pos + SEP.len_utf8()), Some(pos) => expanded.split_at(pos + SEP.len_utf8()),

View File

@ -163,8 +163,8 @@ mod tests {
use super::EnvironmentSyncer; use super::EnvironmentSyncer;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_data::config::tests::FakeConfig; use nu_data::config::tests::FakeConfig;
use nu_engine::basic_evaluation_context;
use nu_engine::Env; use nu_engine::Env;
use nu_engine::EvaluationContext;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_test_support::fs::Stub::FileWithContent; use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground; use nu_test_support::playground::Playground;
@ -179,7 +179,7 @@ mod tests {
#[test] #[test]
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError> fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
{ {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new(); let mut expected = IndexMap::new();
@ -282,7 +282,7 @@ mod tests {
#[test] #[test]
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError> fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
{ {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new(); let mut expected = IndexMap::new();
@ -381,7 +381,7 @@ mod tests {
#[test] #[test]
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> { fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new(); let mut expected = IndexMap::new();
@ -457,7 +457,7 @@ mod tests {
#[test] #[test]
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file( fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![ let expected = std::env::join_paths(vec![
@ -544,7 +544,7 @@ mod tests {
#[test] #[test]
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end( fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![ let expected = std::env::join_paths(vec![

View File

@ -1,38 +1,64 @@
use rustyline::{KeyCode, Modifiers};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
fn convert_keypress(keypress: KeyPress) -> rustyline::KeyPress { pub fn convert_keyevent(key_event: KeyEvent) -> rustyline::KeyEvent {
match keypress { match key_event {
KeyPress::UnknownEscSeq => rustyline::KeyPress::UnknownEscSeq, KeyEvent::UnknownEscSeq => convert_to_rl_keyevent(rustyline::KeyCode::UnknownEscSeq, None),
KeyPress::Backspace => rustyline::KeyPress::Backspace, KeyEvent::Backspace => convert_to_rl_keyevent(rustyline::KeyCode::Backspace, None),
KeyPress::BackTab => rustyline::KeyPress::BackTab, KeyEvent::BackTab => convert_to_rl_keyevent(rustyline::KeyCode::BackTab, None),
KeyPress::BracketedPasteStart => rustyline::KeyPress::BracketedPasteStart, KeyEvent::BracketedPasteStart => {
KeyPress::BracketedPasteEnd => rustyline::KeyPress::BracketedPasteEnd, convert_to_rl_keyevent(rustyline::KeyCode::BracketedPasteStart, None)
KeyPress::Char(c) => rustyline::KeyPress::Char(c), }
KeyPress::ControlDown => rustyline::KeyPress::ControlDown, KeyEvent::BracketedPasteEnd => {
KeyPress::ControlLeft => rustyline::KeyPress::ControlLeft, convert_to_rl_keyevent(rustyline::KeyCode::BracketedPasteEnd, None)
KeyPress::ControlRight => rustyline::KeyPress::ControlRight, }
KeyPress::ControlUp => rustyline::KeyPress::ControlUp, KeyEvent::Char(c) => convert_to_rl_keyevent(rustyline::KeyCode::Char(c), None),
KeyPress::Ctrl(c) => rustyline::KeyPress::Ctrl(c), KeyEvent::ControlDown => {
KeyPress::Delete => rustyline::KeyPress::Delete, convert_to_rl_keyevent(rustyline::KeyCode::Down, Some(Modifiers::CTRL))
KeyPress::Down => rustyline::KeyPress::Down, }
KeyPress::End => rustyline::KeyPress::End, KeyEvent::ControlLeft => {
KeyPress::Enter => rustyline::KeyPress::Enter, convert_to_rl_keyevent(rustyline::KeyCode::Left, Some(Modifiers::CTRL))
KeyPress::Esc => rustyline::KeyPress::Esc, }
KeyPress::F(u) => rustyline::KeyPress::F(u), KeyEvent::ControlRight => {
KeyPress::Home => rustyline::KeyPress::Home, convert_to_rl_keyevent(rustyline::KeyCode::Right, Some(Modifiers::CTRL))
KeyPress::Insert => rustyline::KeyPress::Insert, }
KeyPress::Left => rustyline::KeyPress::Left, KeyEvent::ControlUp => {
KeyPress::Meta(c) => rustyline::KeyPress::Meta(c), convert_to_rl_keyevent(rustyline::KeyCode::Up, Some(Modifiers::CTRL))
KeyPress::Null => rustyline::KeyPress::Null, }
KeyPress::PageDown => rustyline::KeyPress::PageDown, KeyEvent::Ctrl(c) => rustyline::KeyEvent::ctrl(c),
KeyPress::PageUp => rustyline::KeyPress::PageUp, KeyEvent::Delete => convert_to_rl_keyevent(rustyline::KeyCode::Delete, None),
KeyPress::Right => rustyline::KeyPress::Right, KeyEvent::Down => convert_to_rl_keyevent(rustyline::KeyCode::Down, None),
KeyPress::ShiftDown => rustyline::KeyPress::ShiftDown, KeyEvent::End => convert_to_rl_keyevent(rustyline::KeyCode::End, None),
KeyPress::ShiftLeft => rustyline::KeyPress::ShiftLeft, KeyEvent::Enter => convert_to_rl_keyevent(rustyline::KeyCode::Enter, None),
KeyPress::ShiftRight => rustyline::KeyPress::ShiftRight, KeyEvent::Esc => convert_to_rl_keyevent(rustyline::KeyCode::Esc, None),
KeyPress::ShiftUp => rustyline::KeyPress::ShiftUp, KeyEvent::F(u) => convert_to_rl_keyevent(rustyline::KeyCode::F(u), None),
KeyPress::Tab => rustyline::KeyPress::Tab, KeyEvent::Home => convert_to_rl_keyevent(rustyline::KeyCode::Home, None),
KeyPress::Up => rustyline::KeyPress::Up, KeyEvent::Insert => convert_to_rl_keyevent(rustyline::KeyCode::Insert, None),
KeyEvent::Left => convert_to_rl_keyevent(rustyline::KeyCode::Left, None),
KeyEvent::Meta(c) => rustyline::KeyEvent::new(c, Modifiers::NONE),
KeyEvent::Null => convert_to_rl_keyevent(rustyline::KeyCode::Null, None),
KeyEvent::PageDown => convert_to_rl_keyevent(rustyline::KeyCode::PageDown, None),
KeyEvent::PageUp => convert_to_rl_keyevent(rustyline::KeyCode::PageUp, None),
KeyEvent::Right => convert_to_rl_keyevent(rustyline::KeyCode::Right, None),
KeyEvent::ShiftDown => {
convert_to_rl_keyevent(rustyline::KeyCode::Down, Some(Modifiers::SHIFT))
}
KeyEvent::ShiftLeft => {
convert_to_rl_keyevent(rustyline::KeyCode::Left, Some(Modifiers::SHIFT))
}
KeyEvent::ShiftRight => {
convert_to_rl_keyevent(rustyline::KeyCode::Right, Some(Modifiers::SHIFT))
}
KeyEvent::ShiftUp => convert_to_rl_keyevent(rustyline::KeyCode::Up, Some(Modifiers::SHIFT)),
KeyEvent::Tab => convert_to_rl_keyevent(rustyline::KeyCode::Tab, None),
KeyEvent::Up => convert_to_rl_keyevent(rustyline::KeyCode::Up, None),
}
}
fn convert_to_rl_keyevent(key_event: KeyCode, modifier: Option<Modifiers>) -> rustyline::KeyEvent {
rustyline::KeyEvent {
0: key_event,
1: modifier.unwrap_or(Modifiers::NONE),
} }
} }
@ -97,7 +123,9 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
match cmd { match cmd {
Cmd::Abort => rustyline::Cmd::Abort, Cmd::Abort => rustyline::Cmd::Abort,
Cmd::AcceptLine => rustyline::Cmd::AcceptLine, Cmd::AcceptLine => rustyline::Cmd::AcceptLine,
Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine, Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine {
accept_in_the_middle: false,
},
Cmd::BeginningOfHistory => rustyline::Cmd::BeginningOfHistory, Cmd::BeginningOfHistory => rustyline::Cmd::BeginningOfHistory,
Cmd::CapitalizeWord => rustyline::Cmd::CapitalizeWord, Cmd::CapitalizeWord => rustyline::Cmd::CapitalizeWord,
Cmd::ClearScreen => rustyline::Cmd::ClearScreen, Cmd::ClearScreen => rustyline::Cmd::ClearScreen,
@ -140,18 +168,18 @@ fn convert_cmd(cmd: Cmd) -> rustyline::Cmd {
} }
} }
fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyPress, rustyline::Cmd) { fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyEvent, rustyline::Cmd) {
( (
convert_keypress(keybinding.key), convert_keyevent(keybinding.key),
convert_cmd(keybinding.binding), convert_cmd(keybinding.binding),
) )
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum KeyPress { pub enum KeyEvent {
/// Unsupported escape sequence (on unix platform) /// Unsupported escape sequence (on unix platform)
UnknownEscSeq, UnknownEscSeq,
/// ⌫ or `KeyPress::Ctrl('H')` /// ⌫ or `KeyEvent::Ctrl('H')`
Backspace, Backspace,
/// ⇤ (usually Shift-Tab) /// ⇤ (usually Shift-Tab)
BackTab, BackTab,
@ -177,9 +205,9 @@ pub enum KeyPress {
Down, Down,
/// ⇲ /// ⇲
End, End,
/// ↵ or `KeyPress::Ctrl('M')` /// ↵ or `KeyEvent::Ctrl('M')`
Enter, Enter,
/// Escape or `KeyPress::Ctrl('[')` /// Escape or `KeyEvent::Ctrl('[')`
Esc, Esc,
/// Function key /// Function key
F(u8), F(u8),
@ -191,7 +219,7 @@ pub enum KeyPress {
Left, Left,
/// Escape-char or Alt-char /// Escape-char or Alt-char
Meta(char), Meta(char),
/// `KeyPress::Char('\0')` /// `KeyEvent::Char('\0')`
Null, Null,
/// ⇟ /// ⇟
PageDown, PageDown,
@ -207,7 +235,7 @@ pub enum KeyPress {
ShiftRight, ShiftRight,
/// Shift-↑ /// Shift-↑
ShiftUp, ShiftUp,
/// ⇥ or `KeyPress::Ctrl('I')` /// ⇥ or `KeyEvent::Ctrl('I')`
Tab, Tab,
/// ↑ arrow key /// ↑ arrow key
Up, Up,
@ -399,7 +427,7 @@ pub type RepeatCount = usize;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Keybinding { pub struct Keybinding {
key: KeyPress, key: KeyEvent,
binding: Cmd, binding: Cmd,
} }

View File

@ -28,6 +28,7 @@ pub mod types;
pub use crate::cli::cli; pub use crate::cli::cli;
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file}; pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
pub use crate::cli::{NuScript, Options};
pub use crate::env::environment_syncer::EnvironmentSyncer; pub use crate::env::environment_syncer::EnvironmentSyncer;
pub use nu_command::commands::default_context::create_default_context; pub use nu_command::commands::default_context::create_default_context;

View File

@ -5,7 +5,10 @@ use std::error::Error;
use crate::prelude::*; use crate::prelude::*;
#[allow(unused_imports)] #[allow(unused_imports)]
use nu_command::script::LineResult; use nu_engine::script::LineResult;
#[cfg(feature = "rustyline-support")]
use crate::keybinding::{convert_keyevent, KeyEvent};
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
use crate::shell::Helper; use crate::shell::Helper;
@ -16,7 +19,7 @@ use rustyline::{
config::Configurer, config::Configurer,
config::{ColorMode, CompletionType, Config}, config::{ColorMode, CompletionType, Config},
error::ReadlineError, error::ReadlineError,
At, Cmd, Editor, KeyPress, Movement, Word, At, Cmd, Editor, Movement, Word,
}; };
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
@ -40,22 +43,27 @@ pub fn default_rustyline_editor_configuration() -> Editor<Helper> {
#[cfg(not(windows))] #[cfg(not(windows))]
const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List; const DEFAULT_COMPLETION_MODE: CompletionType = CompletionType::List;
let config = Config::builder().color_mode(ColorMode::Forced).build(); let config = Config::builder()
.check_cursor_position(true)
.color_mode(ColorMode::Forced)
.build();
let mut rl: Editor<_> = Editor::with_config(config); let mut rl: Editor<_> = Editor::with_config(config);
// add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight // add key bindings to move over a whole word with Ctrl+ArrowLeft and Ctrl+ArrowRight
rl.bind_sequence( rl.bind_sequence(
KeyPress::ControlLeft, convert_keyevent(KeyEvent::ControlLeft),
Cmd::Move(Movement::BackwardWord(1, Word::Vi)), Cmd::Move(Movement::BackwardWord(1, Word::Vi)),
); );
rl.bind_sequence( rl.bind_sequence(
KeyPress::ControlRight, convert_keyevent(KeyEvent::ControlRight),
Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)), Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)),
); );
// workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202) // workaround for multiline-paste hang in rustyline (see https://github.com/kkawakam/rustyline/issues/202)
rl.bind_sequence(KeyPress::BracketedPasteStart, rustyline::Cmd::Noop); rl.bind_sequence(
convert_keyevent(KeyEvent::BracketedPasteStart),
rustyline::Cmd::Noop,
);
// Let's set the defaults up front and then override them later if the user indicates // Let's set the defaults up front and then override them later if the user indicates
// defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167 // defaults taken from here https://github.com/kkawakam/rustyline/blob/2fe886c9576c1ea13ca0e5808053ad491a6fe049/src/config.rs#L150-L167
rl.set_max_history_size(100); rl.set_max_history_size(100);

View File

@ -57,6 +57,7 @@ impl rustyline::completion::Completer for Helper {
} }
impl rustyline::hint::Hinter for Helper { impl rustyline::hint::Hinter for Helper {
type Hint = String;
fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> { fn hint(&self, line: &str, pos: usize, ctx: &rustyline::Context<'_>) -> Option<String> {
self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx)) self.hinter.as_ref().and_then(|h| h.hint(line, pos, &ctx))
} }
@ -148,7 +149,6 @@ impl rustyline::Helper for Helper {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use nu_engine::basic_evaluation_context;
use rustyline::completion::Completer; use rustyline::completion::Completer;
use rustyline::line_buffer::LineBuffer; use rustyline::line_buffer::LineBuffer;
@ -162,7 +162,7 @@ mod tests {
buffer.insert_str(0, text); buffer.insert_str(0, text);
buffer.set_pos(text.len() - 1); buffer.set_pos(text.len() - 1);
let helper = Helper::new(basic_evaluation_context().unwrap(), None); let helper = Helper::new(EvaluationContext::basic().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement); helper.update(&mut buffer, "cd ".len(), &replacement);
@ -182,7 +182,7 @@ mod tests {
buffer.insert_str(0, text); buffer.insert_str(0, text);
buffer.set_pos(text.len() - 30); buffer.set_pos(text.len() - 30);
let helper = Helper::new(basic_evaluation_context().unwrap(), None); let helper = Helper::new(EvaluationContext::basic().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement); helper.update(&mut buffer, "cd ".len(), &replacement);

View File

@ -5,25 +5,25 @@ description = "CLI for nushell"
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"
name = "nu-command" name = "nu-command"
version = "0.28.0" version = "0.29.0"
[lib] [lib]
doctest = false doctest = false
[dependencies] [dependencies]
nu-data = { version = "0.28.0", path = "../nu-data" } nu-data = { version = "0.29.0", path = "../nu-data" }
nu-engine = { version = "0.28.0", path = "../nu-engine" } nu-engine = { version = "0.29.0", path = "../nu-engine" }
nu-errors = { version = "0.28.0", path = "../nu-errors" } nu-errors = { version = "0.29.0", path = "../nu-errors" }
nu-json = { version = "0.28.0", path = "../nu-json" } nu-json = { version = "0.29.0", path = "../nu-json" }
nu-parser = { version = "0.28.0", path = "../nu-parser" } nu-parser = { version = "0.29.0", path = "../nu-parser" }
nu-plugin = { version = "0.28.0", path = "../nu-plugin" } nu-plugin = { version = "0.29.0", path = "../nu-plugin" }
nu-protocol = { version = "0.28.0", path = "../nu-protocol" } nu-protocol = { version = "0.29.0", path = "../nu-protocol" }
nu-source = { version = "0.28.0", path = "../nu-source" } nu-source = { version = "0.29.0", path = "../nu-source" }
nu-stream = { version = "0.28.0", path = "../nu-stream" } nu-stream = { version = "0.29.0", path = "../nu-stream" }
nu-table = { version = "0.28.0", path = "../nu-table" } nu-table = { version = "0.29.0", path = "../nu-table" }
nu-test-support = { version = "0.28.0", path = "../nu-test-support" } nu-test-support = { version = "0.29.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.28.0", path = "../nu-value-ext" } nu-value-ext = { version = "0.29.0", path = "../nu-value-ext" }
nu-ansi-term = { version = "0.28.0", path = "../nu-ansi-term" } nu-ansi-term = { version = "0.29.0", path = "../nu-ansi-term" }
Inflector = "0.11" Inflector = "0.11"
arboard = { version = "1.1.0", optional = true } arboard = { version = "1.1.0", optional = true }
@ -62,6 +62,7 @@ indexmap = { version = "1.6.1", features = ["serde-1"] }
itertools = "0.10.0" itertools = "0.10.0"
lazy_static = "1.*" lazy_static = "1.*"
log = "0.4.14" log = "0.4.14"
md5 = "0.7.0"
meval = "0.2.0" meval = "0.2.0"
minus = { version = "3.3.0", optional = true, features = ["async_std_lib", "search"] } minus = { version = "3.3.0", optional = true, features = ["async_std_lib", "search"] }
num-bigint = { version = "0.3.1", features = ["serde"] } num-bigint = { version = "0.3.1", features = ["serde"] }
@ -78,7 +79,7 @@ rayon = "1.5.0"
regex = "1.4.3" regex = "1.4.3"
roxmltree = "0.14.0" roxmltree = "0.14.0"
rust-embed = "5.9.0" rust-embed = "5.9.0"
rustyline = { version = "7.1.0", optional = true } rustyline = { version = "8.0.0", optional = true }
serde = { version = "1.0.123", features = ["derive"] } serde = { version = "1.0.123", features = ["derive"] }
serde_bytes = "0.11.5" serde_bytes = "0.11.5"
serde_ini = "0.2.0" serde_ini = "0.2.0"
@ -124,6 +125,7 @@ shadow-rs = "0.5"
[dev-dependencies] [dev-dependencies]
quickcheck = "1.0.3" quickcheck = "1.0.3"
quickcheck_macros = "1.0.0" quickcheck_macros = "1.0.0"
hamcrest2 = "0.3.0"
[features] [features]
clipboard-cli = ["arboard"] clipboard-cli = ["arboard"]

View File

@ -20,11 +20,9 @@ pub(crate) mod chart;
pub(crate) mod classified; pub(crate) mod classified;
#[cfg(feature = "clipboard-cli")] #[cfg(feature = "clipboard-cli")]
pub(crate) mod clip; pub(crate) mod clip;
pub mod command;
pub(crate) mod compact; pub(crate) mod compact;
pub(crate) mod config; pub(crate) mod config;
pub(crate) mod constants; pub(crate) mod constants;
pub(crate) mod count;
pub(crate) mod cp; pub(crate) mod cp;
pub(crate) mod date; pub(crate) mod date;
pub(crate) mod debug; pub(crate) mod debug;
@ -73,6 +71,7 @@ pub(crate) mod insert;
pub(crate) mod into_int; pub(crate) mod into_int;
pub(crate) mod keep; pub(crate) mod keep;
pub(crate) mod last; pub(crate) mod last;
pub(crate) mod length;
pub(crate) mod let_; pub(crate) mod let_;
pub(crate) mod let_env; pub(crate) mod let_env;
pub(crate) mod lines; pub(crate) mod lines;
@ -153,9 +152,8 @@ pub(crate) use char_::Char;
pub(crate) use chart::Chart; pub(crate) use chart::Chart;
pub(crate) use compact::Compact; pub(crate) use compact::Compact;
pub(crate) use config::{ pub(crate) use config::{
Config, ConfigClear, ConfigGet, ConfigLoad, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto, Config, ConfigClear, ConfigGet, ConfigPath, ConfigRemove, ConfigSet, ConfigSetInto,
}; };
pub(crate) use count::Count;
pub(crate) use cp::Cpy; pub(crate) use cp::Cpy;
pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone}; pub(crate) use date::{Date, DateFormat, DateListTimeZone, DateNow, DateToTable, DateToTimeZone};
pub(crate) use debug::Debug; pub(crate) use debug::Debug;
@ -186,25 +184,25 @@ pub(crate) use first::First;
pub(crate) use flatten::Command as Flatten; pub(crate) use flatten::Command as Flatten;
pub(crate) use format::{FileSize, Format}; pub(crate) use format::{FileSize, Format};
pub(crate) use from::From; pub(crate) use from::From;
pub(crate) use from_csv::FromCSV; pub(crate) use from_csv::FromCsv;
pub(crate) use from_eml::FromEML; pub(crate) use from_eml::FromEml;
pub(crate) use from_ics::FromIcs; pub(crate) use from_ics::FromIcs;
pub(crate) use from_ini::FromINI; pub(crate) use from_ini::FromIni;
pub(crate) use from_json::FromJSON; pub(crate) use from_json::FromJson;
pub(crate) use from_ods::FromODS; pub(crate) use from_ods::FromOds;
pub(crate) use from_ssv::FromSSV; pub(crate) use from_ssv::FromSsv;
pub(crate) use from_toml::FromTOML; pub(crate) use from_toml::FromToml;
pub(crate) use from_tsv::FromTSV; pub(crate) use from_tsv::FromTsv;
pub(crate) use from_url::FromURL; pub(crate) use from_url::FromUrl;
pub(crate) use from_vcf::FromVcf; pub(crate) use from_vcf::FromVcf;
pub(crate) use from_xlsx::FromXLSX; pub(crate) use from_xlsx::FromXlsx;
pub(crate) use from_xml::FromXML; pub(crate) use from_xml::FromXml;
pub(crate) use from_yaml::FromYAML; pub(crate) use from_yaml::FromYaml;
pub(crate) use from_yaml::FromYML; pub(crate) use from_yaml::FromYml;
pub(crate) use get::Command as Get; pub(crate) use get::Command as Get;
pub(crate) use group_by::Command as GroupBy; pub(crate) use group_by::Command as GroupBy;
pub(crate) use group_by_date::GroupByDate; pub(crate) use group_by_date::GroupByDate;
pub(crate) use hash_::{Hash, HashBase64}; pub(crate) use hash_::{Hash, HashBase64, HashMd5};
pub(crate) use headers::Headers; pub(crate) use headers::Headers;
pub(crate) use help::Help; pub(crate) use help::Help;
pub(crate) use histogram::Histogram; pub(crate) use histogram::Histogram;
@ -213,6 +211,7 @@ pub(crate) use insert::Command as Insert;
pub(crate) use into_int::IntoInt; pub(crate) use into_int::IntoInt;
pub(crate) use keep::{Keep, KeepUntil, KeepWhile}; pub(crate) use keep::{Keep, KeepUntil, KeepWhile};
pub(crate) use last::Last; pub(crate) use last::Last;
pub(crate) use length::Length;
pub(crate) use let_::Let; pub(crate) use let_::Let;
pub(crate) use let_env::LetEnv; pub(crate) use let_env::LetEnv;
pub(crate) use lines::Lines; pub(crate) use lines::Lines;
@ -273,15 +272,15 @@ pub(crate) use table::Table;
pub(crate) use tags::Tags; pub(crate) use tags::Tags;
pub(crate) use termsize::TermSize; pub(crate) use termsize::TermSize;
pub(crate) use to::To; pub(crate) use to::To;
pub(crate) use to_csv::ToCSV; pub(crate) use to_csv::ToCsv;
pub(crate) use to_html::ToHTML; pub(crate) use to_html::ToHtml;
pub(crate) use to_json::ToJSON; pub(crate) use to_json::ToJson;
pub(crate) use to_md::Command as ToMarkdown; pub(crate) use to_md::Command as ToMarkdown;
pub(crate) use to_toml::ToTOML; pub(crate) use to_toml::ToToml;
pub(crate) use to_tsv::ToTSV; pub(crate) use to_tsv::ToTsv;
pub(crate) use to_url::ToURL; pub(crate) use to_url::ToUrl;
pub(crate) use to_xml::ToXML; pub(crate) use to_xml::ToXml;
pub(crate) use to_yaml::ToYAML; pub(crate) use to_yaml::ToYaml;
pub(crate) use touch::Touch; pub(crate) use touch::Touch;
pub(crate) use uniq::Uniq; pub(crate) use uniq::Uniq;
pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme}; pub(crate) use url_::{UrlCommand, UrlHost, UrlPath, UrlQuery, UrlScheme};

View File

@ -9,6 +9,7 @@ pub struct Char;
#[derive(Deserialize)] #[derive(Deserialize)]
struct CharArgs { struct CharArgs {
name: Tagged<String>, name: Tagged<String>,
rest: Vec<Tagged<String>>,
unicode: bool, unicode: bool,
} }
@ -25,6 +26,7 @@ impl WholeStreamCommand for Char {
SyntaxShape::Any, SyntaxShape::Any,
"the name of the character to output", "the name of the character to output",
) )
.rest(SyntaxShape::String, "multiple unicode bytes")
.switch("unicode", "unicode string i.e. 1f378", Some('u')) .switch("unicode", "unicode string i.e. 1f378", Some('u'))
} }
@ -53,24 +55,60 @@ impl WholeStreamCommand for Char {
example: r#"char -u 1f378"#, example: r#"char -u 1f378"#,
result: Some(vec![Value::from("\u{1f378}")]), result: Some(vec![Value::from("\u{1f378}")]),
}, },
Example {
description: "Output multi-byte unicode character",
example: r#"char -u 1F468 200D 1F466 200D 1F466"#,
result: Some(vec![Value::from(
"\u{1F468}\u{200D}\u{1F466}\u{200D}\u{1F466}",
)]),
},
] ]
} }
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let (CharArgs { name, unicode }, _) = args.process().await?; let (
CharArgs {
name,
rest,
unicode,
},
_,
) = args.process().await?;
if unicode { if unicode {
let decoded_char = string_to_unicode_char(&name.item); if !rest.is_empty() {
if let Some(output) = decoded_char { // Setup a new buffer to put all the unicode bytes in
let mut multi_byte = String::new();
// Get the first byte
let decoded_char = string_to_unicode_char(&name.item, &name.tag);
match decoded_char {
Ok(ch) => multi_byte.push(ch),
Err(e) => return Err(e),
}
// Get the rest of the bytes
for byte_part in rest {
let decoded_char = string_to_unicode_char(&byte_part, &byte_part.tag);
match decoded_char {
Ok(ch) => multi_byte.push(ch),
Err(e) => return Err(e),
}
}
Ok(OutputStream::one(ReturnSuccess::value( Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(output).into_value(name.tag()), UntaggedValue::string(multi_byte).into_value(name.tag),
))) )))
} else { } else {
Err(ShellError::labeled_error( let decoded_char = string_to_unicode_char(&name.item, &name.tag);
"error decoding unicode character", if let Ok(ch) = decoded_char {
"error decoding unicode character", Ok(OutputStream::one(ReturnSuccess::value(
name.tag(), UntaggedValue::string(ch).into_value(name.tag()),
)) )))
} else {
Err(ShellError::labeled_error(
"error decoding unicode character",
"error decoding unicode character",
name.tag(),
))
}
} }
} else { } else {
let special_character = str_to_character(&name.item); let special_character = str_to_character(&name.item);
@ -89,10 +127,20 @@ impl WholeStreamCommand for Char {
} }
} }
fn string_to_unicode_char(s: &str) -> Option<char> { fn string_to_unicode_char(s: &str, t: &Tag) -> Result<char, ShellError> {
u32::from_str_radix(s, 16) let decoded_char = u32::from_str_radix(s, 16)
.ok() .ok()
.and_then(std::char::from_u32) .and_then(std::char::from_u32);
if let Some(ch) = decoded_char {
Ok(ch)
} else {
Err(ShellError::labeled_error(
"error decoding unicode character",
"error decoding unicode character",
t,
))
}
} }
fn str_to_character(s: &str) -> Option<String> { fn str_to_character(s: &str) -> Option<String> {

View File

@ -28,7 +28,7 @@ pub(crate) async fn run_external_command(
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name); trace!(target: "nu::run::external", "-> {}", command.name);
if !did_find_command(&command.name) { if !context.host.lock().is_external_cmd(&command.name) {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Command not found", "Command not found",
"command not found", "command not found",
@ -443,35 +443,6 @@ fn spawn(
} }
} }
pub fn did_find_command(#[allow(unused)] name: &str) -> bool {
#[cfg(not(feature = "which"))]
{
// we can't perform this check, so just assume it can be found
true
}
#[cfg(all(feature = "which", unix))]
{
which::which(name).is_ok()
}
#[cfg(all(feature = "which", windows))]
{
if which::which(name).is_ok() {
true
} else {
// Reference: https://ss64.com/nt/syntax-internal.html
let cmd_builtins = [
"assoc", "break", "color", "copy", "date", "del", "dir", "dpath", "echo", "erase",
"for", "ftype", "md", "mkdir", "mklink", "move", "path", "ren", "rename", "rd",
"rmdir", "start", "time", "title", "type", "ver", "verify", "vol",
];
cmd_builtins.contains(&name)
}
}
}
fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str> fn expand_tilde<SI: ?Sized, P, HD>(input: &SI, home_dir: HD) -> std::borrow::Cow<str>
where where
SI: AsRef<str>, SI: AsRef<str>,
@ -538,7 +509,7 @@ mod tests {
#[cfg(feature = "which")] #[cfg(feature = "which")]
use futures::executor::block_on; use futures::executor::block_on;
#[cfg(feature = "which")] #[cfg(feature = "which")]
use nu_engine::basic_evaluation_context; use nu_engine::EvaluationContext;
#[cfg(feature = "which")] #[cfg(feature = "which")]
use nu_errors::ShellError; use nu_errors::ShellError;
#[cfg(feature = "which")] #[cfg(feature = "which")]
@ -563,7 +534,7 @@ mod tests {
let input = InputStream::empty(); let input = InputStream::empty();
let mut ctx = let mut ctx =
basic_evaluation_context().expect("There was a problem creating a basic context."); EvaluationContext::basic().expect("There was a problem creating a basic context.");
assert!( assert!(
run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout) run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout)
@ -577,7 +548,7 @@ mod tests {
// async fn failure_run() -> Result<(), ShellError> { // async fn failure_run() -> Result<(), ShellError> {
// let cmd = ExternalBuilder::for_name("fail").build(); // let cmd = ExternalBuilder::for_name("fail").build();
// let mut ctx = crate::cli::basic_evaluation_context().expect("There was a problem creating a basic context."); // let mut ctx = crate::cli::EvaluationContext::basic().expect("There was a problem creating a basic context.");
// let stream = run_external_command(cmd, &mut ctx, None, false) // let stream = run_external_command(cmd, &mut ctx, None, false)
// .await? // .await?
// .expect("There was a problem running the external command."); // .expect("There was a problem running the external command.");

View File

@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
pub struct SubCommand; pub struct SubCommand;
@ -35,13 +35,19 @@ impl WholeStreamCommand for SubCommand {
pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> { pub async fn clear(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone(); let name_span = args.call_info.name_tag.clone();
// NOTE: None because we are not loading a new config file, we just want to read from the let path = match args.scope.get_var("config-path") {
// existing config Some(Value {
let mut result = nu_data::config::read(name_span, &None)?; value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) => Some(path),
_ => nu_data::config::default_path().ok(),
};
let mut result = nu_data::config::read(name_span, &path)?;
result.clear(); result.clear();
config::write(&result, &None)?; config::write(&result, &path)?;
Ok(OutputStream::one(ReturnSuccess::value( Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag), UntaggedValue::Row(result.into()).into_value(args.call_info.name_tag),

View File

@ -2,7 +2,7 @@ use crate::prelude::*;
use nu_engine::CommandArgs; use nu_engine::CommandArgs;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_stream::OutputStream; use nu_stream::OutputStream;
pub struct Command; pub struct Command;
@ -22,9 +22,16 @@ impl WholeStreamCommand for Command {
} }
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let name = args.call_info.name_tag;
let result = nu_data::config::read(name_span, &None)?; let path = match args.scope.get_var("config-path") {
Some(Value {
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) => Some(path),
_ => nu_data::config::default_path().ok(),
};
let result = nu_data::config::read(&name, &path)?;
Ok(futures::stream::iter(vec![ReturnSuccess::value( Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name), UntaggedValue::Row(result.into()).into_value(name),

View File

@ -1,13 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct SubCommand; pub struct SubCommand;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct GetArgs { pub struct Arguments {
path: ColumnPath, column_path: ColumnPath,
} }
#[async_trait] #[async_trait]
@ -42,14 +44,21 @@ impl WholeStreamCommand for SubCommand {
} }
pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> { pub async fn get(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let (GetArgs { path }, _) = args.process().await?; let scope = args.scope.clone();
let (Arguments { column_path }, _) = args.process().await?;
// NOTE: None because we are not loading a new config file, we just want to read from the let path = match scope.get_var("config-path") {
// existing config Some(Value {
let result = UntaggedValue::row(nu_data::config::read(&name_tag, &None)?).into_value(&name_tag); value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) => Some(path),
_ => nu_data::config::default_path().ok(),
};
let value = crate::commands::get::get_column_path(&path, &result)?; let result = UntaggedValue::row(nu_data::config::read(&name, &path)?).into_value(&name);
let value = crate::commands::get::get_column_path(&column_path, &result)?;
Ok(match value { Ok(match value {
Value { Value {

View File

@ -1,51 +0,0 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
use std::path::PathBuf;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct LoadArgs {
load: Tagged<PathBuf>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"config load"
}
fn signature(&self) -> Signature {
Signature::build("config load").required(
"load",
SyntaxShape::FilePath,
"Path to load the config from",
)
}
fn usage(&self) -> &str {
"Loads the config from the path given"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
set(args).await
}
}
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let name_span = args.call_info.name_tag.clone();
let (LoadArgs { load }, _) = args.process().await?;
let configuration = load.item().clone();
let result = nu_data::config::read(name_span, &Some(configuration))?;
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
)])
.to_output_stream())
}

View File

@ -1,7 +1,6 @@
pub mod clear; pub mod clear;
pub mod command; pub mod command;
pub mod get; pub mod get;
pub mod load;
pub mod path; pub mod path;
pub mod remove; pub mod remove;
pub mod set; pub mod set;
@ -10,7 +9,6 @@ pub mod set_into;
pub use clear::SubCommand as ConfigClear; pub use clear::SubCommand as ConfigClear;
pub use command::Command as Config; pub use command::Command as Config;
pub use get::SubCommand as ConfigGet; pub use get::SubCommand as ConfigGet;
pub use load::SubCommand as ConfigLoad;
pub use path::SubCommand as ConfigPath; pub use path::SubCommand as ConfigPath;
pub use remove::SubCommand as ConfigRemove; pub use remove::SubCommand as ConfigRemove;
pub use set::SubCommand as ConfigSet; pub use set::SubCommand as ConfigSet;

View File

@ -1,7 +1,7 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
pub struct SubCommand; pub struct SubCommand;
@ -33,9 +33,18 @@ impl WholeStreamCommand for SubCommand {
} }
pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> { pub async fn path(args: CommandArgs) -> Result<OutputStream, ShellError> {
let path = config::default_path()?;
Ok(OutputStream::one(ReturnSuccess::value( Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Primitive(Primitive::FilePath(path)).into_value(args.call_info.name_tag), match args.scope.get_var("config-path") {
Some(
path
@
Value {
value: UntaggedValue::Primitive(Primitive::FilePath(_)),
..
},
) => path,
_ => UntaggedValue::Primitive(Primitive::FilePath(nu_data::config::default_path()?))
.into_value(args.call_info.name_tag),
},
))) )))
} }

View File

@ -1,13 +1,13 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue}; use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
pub struct SubCommand; pub struct SubCommand;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct RemoveArgs { pub struct Arguments {
remove: Tagged<String>, remove: Tagged<String>,
} }
@ -44,15 +44,24 @@ impl WholeStreamCommand for SubCommand {
pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> { pub async fn remove(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone(); let name_span = args.call_info.name_tag.clone();
let (RemoveArgs { remove }, _) = args.process().await?; let scope = args.scope.clone();
let (Arguments { remove }, _) = args.process().await?;
let mut result = nu_data::config::read(name_span, &None)?; let path = match scope.get_var("config-path") {
Some(Value {
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) => Some(path),
_ => nu_data::config::default_path().ok(),
};
let mut result = nu_data::config::read(name_span, &path)?;
let key = remove.to_string(); let key = remove.to_string();
if result.contains_key(&key) { if result.contains_key(&key) {
result.swap_remove(&key); result.swap_remove(&key);
config::write(&result, &None)?; config::write(&result, &path)?;
Ok(futures::stream::iter(vec![ReturnSuccess::value( Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(remove.tag()), UntaggedValue::Row(result.into()).into_value(remove.tag()),
)]) )])

View File

@ -1,13 +1,15 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ColumnPath, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct SubCommand; pub struct SubCommand;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct SetArgs { pub struct Arguments {
path: ColumnPath, column_path: ColumnPath,
value: Value, value: Value,
} }
@ -58,13 +60,26 @@ impl WholeStreamCommand for SubCommand {
} }
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> { pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let (SetArgs { path, mut value }, _) = args.process().await?; let scope = args.scope.clone();
let (
Arguments {
column_path,
mut value,
},
_,
) = args.process().await?;
// NOTE: None because we are not loading a new config file, we just want to read from the let path = match scope.get_var("config-path") {
// existing config Some(Value {
let raw_entries = nu_data::config::read(&name_tag, &None)?; value: UntaggedValue::Primitive(Primitive::FilePath(path)),
let configuration = UntaggedValue::row(raw_entries).into_value(&name_tag); ..
}) => Some(path),
_ => nu_data::config::default_path().ok(),
};
let raw_entries = nu_data::config::read(&name, &path)?;
let configuration = UntaggedValue::row(raw_entries).into_value(&name);
if let UntaggedValue::Table(rows) = &value.value { if let UntaggedValue::Table(rows) = &value.value {
if rows.len() == 1 && rows[0].is_row() { if rows.len() == 1 && rows[0].is_row() {
@ -72,15 +87,15 @@ pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
} }
} }
match configuration.forgiving_insert_data_at_column_path(&path, value) { match configuration.forgiving_insert_data_at_column_path(&column_path, value) {
Ok(Value { Ok(Value {
value: UntaggedValue::Row(changes), value: UntaggedValue::Row(changes),
.. ..
}) => { }) => {
config::write(&changes.entries, &None)?; config::write(&changes.entries, &path)?;
Ok(OutputStream::one(ReturnSuccess::value( Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(changes).into_value(name_tag), UntaggedValue::Row(changes).into_value(name),
))) )))
} }
Ok(_) => Ok(OutputStream::empty()), Ok(_) => Ok(OutputStream::empty()),

View File

@ -1,13 +1,13 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
pub struct SubCommand; pub struct SubCommand;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct SetIntoArgs { pub struct Arguments {
set_into: Tagged<String>, set_into: Tagged<String>,
} }
@ -43,17 +43,19 @@ impl WholeStreamCommand for SubCommand {
} }
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> { pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone();
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let scope = args.scope.clone();
let (Arguments { set_into: v }, input) = args.process().await?;
let (SetIntoArgs { set_into: v }, input) = args.process().await?; let path = match scope.get_var("config-path") {
Some(Value {
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) => Some(path),
_ => nu_data::config::default_path().ok(),
};
// NOTE: None because we are not loading a new config file, we just want to read from the let mut result = nu_data::config::read(&name, &path)?;
// existing config
let mut result = nu_data::config::read(name_span, &None)?;
// In the original code, this is set to `Some` if the `load flag is set`
let configuration = None;
let rows: Vec<Value> = input.collect().await; let rows: Vec<Value> = input.collect().await;
let key = v.to_string(); let key = v.to_string();
@ -70,7 +72,7 @@ pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
result.insert(key, value.clone()); result.insert(key, value.clone());
config::write(&result, &configuration)?; config::write(&result, &path)?;
OutputStream::one(ReturnSuccess::value( OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name), UntaggedValue::Row(result.into()).into_value(name),
@ -81,7 +83,7 @@ pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
result.insert(key, value); result.insert(key, value);
config::write(&result, &configuration)?; config::write(&result, &path)?;
OutputStream::one(ReturnSuccess::value( OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name), UntaggedValue::Row(result.into()).into_value(name),

View File

@ -27,10 +27,11 @@ impl WholeStreamCommand for Date {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Convert a date to a given time zone. "Convert a date to a given time zone."
}
Use `date list-timezone` to list all supported time zones.
" fn extra_usage(&self) -> &str {
"Use 'date list-timezone' to list all supported time zones."
} }
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -1,10 +1,9 @@
use crate::prelude::*;
use nu_engine::basic_evaluation_context;
use nu_engine::whole_stream_command; use nu_engine::whole_stream_command;
use nu_engine::EvaluationContext;
use std::error::Error; use std::error::Error;
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> { pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
let context = basic_evaluation_context()?; let context = EvaluationContext::basic()?;
{ {
use crate::commands::*; use crate::commands::*;
@ -29,7 +28,6 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(ConfigSet), whole_stream_command(ConfigSet),
whole_stream_command(ConfigSetInto), whole_stream_command(ConfigSetInto),
whole_stream_command(ConfigClear), whole_stream_command(ConfigClear),
whole_stream_command(ConfigLoad),
whole_stream_command(ConfigRemove), whole_stream_command(ConfigRemove),
whole_stream_command(ConfigPath), whole_stream_command(ConfigPath),
whole_stream_command(Help), whole_stream_command(Help),
@ -57,7 +55,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(Sleep), whole_stream_command(Sleep),
// Statistics // Statistics
whole_stream_command(Size), whole_stream_command(Size),
whole_stream_command(Count), whole_stream_command(Length),
whole_stream_command(Benchmark), whole_stream_command(Benchmark),
// Metadata // Metadata
whole_stream_command(Tags), whole_stream_command(Tags),
@ -75,6 +73,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
// Text manipulation // Text manipulation
whole_stream_command(Hash), whole_stream_command(Hash),
whole_stream_command(HashBase64), whole_stream_command(HashBase64),
whole_stream_command(HashMd5),
whole_stream_command(Split), whole_stream_command(Split),
whole_stream_command(SplitColumn), whole_stream_command(SplitColumn),
whole_stream_command(SplitRow), whole_stream_command(SplitRow),
@ -190,30 +189,30 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(MathCeil), whole_stream_command(MathCeil),
// File format output // File format output
whole_stream_command(To), whole_stream_command(To),
whole_stream_command(ToCSV), whole_stream_command(ToCsv),
whole_stream_command(ToHTML), whole_stream_command(ToHtml),
whole_stream_command(ToJSON), whole_stream_command(ToJson),
whole_stream_command(ToMarkdown), whole_stream_command(ToMarkdown),
whole_stream_command(ToTOML), whole_stream_command(ToToml),
whole_stream_command(ToTSV), whole_stream_command(ToTsv),
whole_stream_command(ToURL), whole_stream_command(ToUrl),
whole_stream_command(ToYAML), whole_stream_command(ToYaml),
whole_stream_command(ToXML), whole_stream_command(ToXml),
// File format input // File format input
whole_stream_command(From), whole_stream_command(From),
whole_stream_command(FromCSV), whole_stream_command(FromCsv),
whole_stream_command(FromEML), whole_stream_command(FromEml),
whole_stream_command(FromTSV), whole_stream_command(FromTsv),
whole_stream_command(FromSSV), whole_stream_command(FromSsv),
whole_stream_command(FromINI), whole_stream_command(FromIni),
whole_stream_command(FromJSON), whole_stream_command(FromJson),
whole_stream_command(FromODS), whole_stream_command(FromOds),
whole_stream_command(FromTOML), whole_stream_command(FromToml),
whole_stream_command(FromURL), whole_stream_command(FromUrl),
whole_stream_command(FromXLSX), whole_stream_command(FromXlsx),
whole_stream_command(FromXML), whole_stream_command(FromXml),
whole_stream_command(FromYAML), whole_stream_command(FromYaml),
whole_stream_command(FromYML), whole_stream_command(FromYml),
whole_stream_command(FromIcs), whole_stream_command(FromIcs),
whole_stream_command(FromVcf), whole_stream_command(FromVcf),
// "Private" commands (not intended to be accessed directly) // "Private" commands (not intended to be accessed directly)

View File

@ -4,16 +4,16 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
pub struct FromCSV; pub struct FromCsv;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FromCSVArgs { pub struct FromCsvArgs {
noheaders: bool, noheaders: bool,
separator: Option<Value>, separator: Option<Value>,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromCSV { impl WholeStreamCommand for FromCsv {
fn name(&self) -> &str { fn name(&self) -> &str {
"from csv" "from csv"
} }
@ -71,7 +71,7 @@ async fn from_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ( let (
FromCSVArgs { FromCsvArgs {
noheaders, noheaders,
separator, separator,
}, },
@ -105,13 +105,13 @@ async fn from_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromCSV; use super::FromCsv;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromCSV {}) test_examples(FromCsv {})
} }
} }

View File

@ -6,18 +6,18 @@ use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
use nu_source::Tagged; use nu_source::Tagged;
pub struct FromEML; pub struct FromEml;
const DEFAULT_BODY_PREVIEW: usize = 50; const DEFAULT_BODY_PREVIEW: usize = 50;
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]
pub struct FromEMLArgs { pub struct FromEmlArgs {
#[serde(rename(deserialize = "preview-body"))] #[serde(rename(deserialize = "preview-body"))]
preview_body: Option<Tagged<usize>>, preview_body: Option<Tagged<usize>>,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromEML { impl WholeStreamCommand for FromEml {
fn name(&self) -> &str { fn name(&self) -> &str {
"from eml" "from eml"
} }
@ -75,7 +75,7 @@ fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedVal
async fn from_eml(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn from_eml(args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (eml_args, input): (FromEMLArgs, _) = args.process().await?; let (eml_args, input): (FromEmlArgs, _) = args.process().await?;
let value = input.collect_string(tag.clone()).await?; let value = input.collect_string(tag.clone()).await?;
let body_preview = eml_args let body_preview = eml_args
@ -121,13 +121,13 @@ async fn from_eml(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromEML; use super::FromEml;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromEML {}) test_examples(FromEml {})
} }
} }

View File

@ -4,10 +4,10 @@ use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
use std::collections::HashMap; use std::collections::HashMap;
pub struct FromINI; pub struct FromIni;
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromINI { impl WholeStreamCommand for FromIni {
fn name(&self) -> &str { fn name(&self) -> &str {
"from ini" "from ini"
} }
@ -86,13 +86,13 @@ async fn from_ini(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromINI; use super::FromIni;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromINI {}) test_examples(FromIni {})
} }
} }

View File

@ -3,15 +3,15 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct FromJSON; pub struct FromJson;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FromJSONArgs { pub struct FromJsonArgs {
objects: bool, objects: bool,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromJSON { impl WholeStreamCommand for FromJson {
fn name(&self) -> &str { fn name(&self) -> &str {
"from json" "from json"
} }
@ -71,7 +71,7 @@ pub fn from_json_string_to_value(s: String, tag: impl Into<Tag>) -> nu_json::Res
async fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name_tag = args.call_info.name_tag.clone();
let (FromJSONArgs { objects }, input) = args.process().await?; let (FromJsonArgs { objects }, input) = args.process().await?;
let concat_string = input.collect_string(name_tag.clone()).await?; let concat_string = input.collect_string(name_tag.clone()).await?;
let string_clone: Vec<_> = concat_string.item.lines().map(|x| x.to_string()).collect(); let string_clone: Vec<_> = concat_string.item.lines().map(|x| x.to_string()).collect();
@ -135,13 +135,13 @@ async fn from_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromJSON; use super::FromJson;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromJSON {}) test_examples(FromJson {})
} }
} }

View File

@ -6,15 +6,15 @@ use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
use std::io::Cursor; use std::io::Cursor;
pub struct FromODS; pub struct FromOds;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FromODSArgs { pub struct FromOdsArgs {
noheaders: bool, noheaders: bool,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromODS { impl WholeStreamCommand for FromOds {
fn name(&self) -> &str { fn name(&self) -> &str {
"from ods" "from ods"
} }
@ -41,7 +41,7 @@ async fn from_ods(args: CommandArgs) -> Result<OutputStream, ShellError> {
let span = tag.span; let span = tag.span;
let ( let (
FromODSArgs { FromOdsArgs {
noheaders: _noheaders, noheaders: _noheaders,
}, },
input, input,
@ -93,13 +93,13 @@ async fn from_ods(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromODS; use super::FromOds;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromODS {}) test_examples(FromOds {})
} }
} }

View File

@ -6,10 +6,10 @@ use nu_protocol::{
}; };
use nu_source::Tagged; use nu_source::Tagged;
pub struct FromSSV; pub struct FromSsv;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FromSSVArgs { pub struct FromSsvArgs {
noheaders: bool, noheaders: bool,
#[serde(rename(deserialize = "aligned-columns"))] #[serde(rename(deserialize = "aligned-columns"))]
aligned_columns: bool, aligned_columns: bool,
@ -21,7 +21,7 @@ const STRING_REPRESENTATION: &str = "from ssv";
const DEFAULT_MINIMUM_SPACES: usize = 2; const DEFAULT_MINIMUM_SPACES: usize = 2;
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromSSV { impl WholeStreamCommand for FromSsv {
fn name(&self) -> &str { fn name(&self) -> &str {
STRING_REPRESENTATION STRING_REPRESENTATION
} }
@ -250,7 +250,7 @@ fn from_ssv_string_to_value(
async fn from_ssv(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn from_ssv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ( let (
FromSSVArgs { FromSsvArgs {
noheaders, noheaders,
aligned_columns, aligned_columns,
minimum_spaces, minimum_spaces,
@ -489,9 +489,9 @@ mod tests {
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use super::FromSSV; use super::FromSsv;
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromSSV {}) test_examples(FromSsv {})
} }
} }

View File

@ -3,10 +3,10 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct FromTOML; pub struct FromToml;
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromTOML { impl WholeStreamCommand for FromToml {
fn name(&self) -> &str { fn name(&self) -> &str {
"from toml" "from toml"
} }
@ -92,13 +92,13 @@ pub async fn from_toml(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromTOML; use super::FromToml;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromTOML {}) test_examples(FromToml {})
} }
} }

View File

@ -4,15 +4,15 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::Signature; use nu_protocol::Signature;
pub struct FromTSV; pub struct FromTsv;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FromTSVArgs { pub struct FromTsvArgs {
noheaders: bool, noheaders: bool,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromTSV { impl WholeStreamCommand for FromTsv {
fn name(&self) -> &str { fn name(&self) -> &str {
"from tsv" "from tsv"
} }
@ -36,20 +36,20 @@ impl WholeStreamCommand for FromTSV {
async fn from_tsv(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn from_tsv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let (FromTSVArgs { noheaders }, input) = args.process().await?; let (FromTsvArgs { noheaders }, input) = args.process().await?;
from_delimited_data(noheaders, '\t', "TSV", input, name).await from_delimited_data(noheaders, '\t', "TSV", input, name).await
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromTSV; use super::FromTsv;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromTSV {}) test_examples(FromTsv {})
} }
} }

View File

@ -3,10 +3,10 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
pub struct FromURL; pub struct FromUrl;
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromURL { impl WholeStreamCommand for FromUrl {
fn name(&self) -> &str { fn name(&self) -> &str {
"from url" "from url"
} }
@ -55,13 +55,13 @@ async fn from_url(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromURL; use super::FromUrl;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromURL {}) test_examples(FromUrl {})
} }
} }

View File

@ -6,15 +6,15 @@ use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue};
use std::io::Cursor; use std::io::Cursor;
pub struct FromXLSX; pub struct FromXlsx;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FromXLSXArgs { pub struct FromXlsxArgs {
noheaders: bool, noheaders: bool,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromXLSX { impl WholeStreamCommand for FromXlsx {
fn name(&self) -> &str { fn name(&self) -> &str {
"from xlsx" "from xlsx"
} }
@ -40,7 +40,7 @@ async fn from_xlsx(args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let span = tag.span; let span = tag.span;
let ( let (
FromXLSXArgs { FromXlsxArgs {
noheaders: _noheaders, noheaders: _noheaders,
}, },
input, input,
@ -93,13 +93,13 @@ async fn from_xlsx(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::FromXLSX; use super::FromXlsx;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromXLSX {}) test_examples(FromXlsx {})
} }
} }

View File

@ -3,10 +3,10 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct FromXML; pub struct FromXml;
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromXML { impl WholeStreamCommand for FromXml {
fn name(&self) -> &str { fn name(&self) -> &str {
"from xml" "from xml"
} }
@ -298,9 +298,9 @@ mod tests {
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use super::FromXML; use super::FromXml;
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromXML {}) test_examples(FromXml {})
} }
} }

View File

@ -3,10 +3,10 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, TaggedDictBuilder, UntaggedValue, Value};
pub struct FromYAML; pub struct FromYaml;
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromYAML { impl WholeStreamCommand for FromYaml {
fn name(&self) -> &str { fn name(&self) -> &str {
"from yaml" "from yaml"
} }
@ -24,10 +24,10 @@ impl WholeStreamCommand for FromYAML {
} }
} }
pub struct FromYML; pub struct FromYml;
#[async_trait] #[async_trait]
impl WholeStreamCommand for FromYML { impl WholeStreamCommand for FromYml {
fn name(&self) -> &str { fn name(&self) -> &str {
"from yml" "from yml"
} }
@ -169,7 +169,7 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(FromYAML {}) test_examples(FromYaml {})
} }
#[test] #[test]

View File

@ -0,0 +1,133 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tag;
#[derive(Deserialize)]
pub struct Arguments {
pub rest: Vec<ColumnPath>,
}
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"hash md5"
}
fn signature(&self) -> Signature {
Signature::build("hash md5").rest(
SyntaxShape::ColumnPath,
"optionally md5 encode data by column paths",
)
}
fn usage(&self) -> &str {
"md5 encode a value"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate(args).await
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "md5 encode a string",
example: "echo 'abcdefghijklmnopqrstuvwxyz' | hash md5",
result: Some(vec![UntaggedValue::string(
"c3fcd3d76192e4007dfb496cca67e13b",
)
.into_untagged_value()]),
},
Example {
description: "md5 encode a file",
example: "open ./nu_0_24_1_windows.zip | hash md5",
result: Some(vec![UntaggedValue::string(
"dcf30f2836a1a99fc55cf72e28272606",
)
.into_untagged_value()]),
},
]
}
}
async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (Arguments { rest }, input) = args.process().await?;
let column_paths: Vec<_> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag())?)
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::String(s)) => {
let md5_digest = md5::compute(s.as_bytes());
Ok(UntaggedValue::string(&format!("{:x}", md5_digest)).into_value(tag))
}
UntaggedValue::Primitive(Primitive::Binary(bytes)) => {
let md5_digest = md5::compute(bytes);
Ok(UntaggedValue::string(&format!("{:x}", md5_digest)).into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not supported for hashing as md5",
got,
tag.into().span,
))
}
}
}
#[cfg(test)]
mod tests {
use super::action;
use nu_protocol::{Primitive, UntaggedValue};
use nu_source::Tag;
use nu_test_support::value::string;
#[test]
fn md5_encode_string() {
let word = string("abcdefghijklmnopqrstuvwxyz");
let expected =
UntaggedValue::string("c3fcd3d76192e4007dfb496cca67e13b").into_untagged_value();
let actual = action(&word, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
}
#[test]
fn md5_encode_bytes() {
let bytes = vec![0xC0, 0xFF, 0xEE];
let binary = UntaggedValue::Primitive(Primitive::Binary(bytes)).into_untagged_value();
let expected =
UntaggedValue::string("5f80e231382769b0102b1164cf722d83").into_untagged_value();
let actual = action(&binary, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -1,5 +1,7 @@
mod base64_; mod base64_;
mod command; mod command;
mod md5_;
pub use base64_::SubCommand as HashBase64; pub use base64_::SubCommand as HashBase64;
pub use command::Command as Hash; pub use command::Command as Hash;
pub use md5_::SubCommand as HashMd5;

View File

@ -29,7 +29,7 @@ impl WholeStreamCommand for Headers {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Create headers for a raw string", description: "Create headers from a raw string",
example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#, example: r#"echo "a b c|1 2 3" | split row "|" | split column " " | headers"#,
result: None, result: None,
}, },

View File

@ -1,6 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use nu_data::config::{Conf, NuConfig}; use nu_data::config::{path::history as history_path, NuConfig};
use nu_engine::history_path;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
@ -34,7 +33,7 @@ impl WholeStreamCommand for History {
} }
async fn history(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn history(args: CommandArgs) -> Result<OutputStream, ShellError> {
let config: Box<dyn Conf> = Box::new(NuConfig::new()); let config = NuConfig::new();
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (Arguments { clear }, _) = args.process().await?; let (Arguments { clear }, _) = args.process().await?;

View File

@ -13,6 +13,7 @@ pub struct KillArgs {
pub rest: Vec<Tagged<u64>>, pub rest: Vec<Tagged<u64>>,
pub force: Tagged<bool>, pub force: Tagged<bool>,
pub quiet: Tagged<bool>, pub quiet: Tagged<bool>,
pub signal: Option<Tagged<u32>>,
} }
#[async_trait] #[async_trait]
@ -22,7 +23,7 @@ impl WholeStreamCommand for Kill {
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("kill") let signature = Signature::build("kill")
.required( .required(
"pid", "pid",
SyntaxShape::Int, SyntaxShape::Int,
@ -30,7 +31,18 @@ impl WholeStreamCommand for Kill {
) )
.rest(SyntaxShape::Int, "rest of processes to kill") .rest(SyntaxShape::Int, "rest of processes to kill")
.switch("force", "forcefully kill the process", Some('f')) .switch("force", "forcefully kill the process", Some('f'))
.switch("quiet", "won't print anything to the console", Some('q')) .switch("quiet", "won't print anything to the console", Some('q'));
if cfg!(windows) {
return signature;
}
signature.named(
"signal",
SyntaxShape::Int,
"signal decimal number to be sent instead of the default 15 (unsupported on Windows)",
Some('s'),
)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
@ -53,6 +65,11 @@ impl WholeStreamCommand for Kill {
example: "kill --force 12345", example: "kill --force 12345",
result: None, result: None,
}, },
Example {
description: "Send INT signal",
example: "kill -s 2 12345",
result: None,
},
] ]
} }
} }
@ -64,6 +81,7 @@ async fn kill(args: CommandArgs) -> Result<OutputStream, ShellError> {
rest, rest,
force, force,
quiet, quiet,
signal,
}, },
.., ..,
) = args.process().await?; ) = args.process().await?;
@ -89,7 +107,18 @@ async fn kill(args: CommandArgs) -> Result<OutputStream, ShellError> {
let mut cmd = Command::new("kill"); let mut cmd = Command::new("kill");
if *force { if *force {
if let Some(signal_value) = signal {
return Err(ShellError::labeled_error_with_secondary(
"mixing force and signal options is not supported",
"signal option",
signal_value.tag(),
"force option",
force.tag(),
));
}
cmd.arg("-9"); cmd.arg("-9");
} else if let Some(signal_value) = signal {
cmd.arg(format!("-{}", signal_value.item().to_string()));
} }
cmd.arg(pid.item().to_string()); cmd.arg(pid.item().to_string());

View File

@ -4,21 +4,21 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value}; use nu_protocol::{Signature, UntaggedValue, Value};
pub struct Count; pub struct Length;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CountArgs { pub struct LengthArgs {
column: bool, column: bool,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for Count { impl WholeStreamCommand for Length {
fn name(&self) -> &str { fn name(&self) -> &str {
"count" "length"
} }
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("count").switch( Signature::build("length").switch(
"column", "column",
"Calculate number of columns in table", "Calculate number of columns in table",
Some('c'), Some('c'),
@ -31,10 +31,10 @@ impl WholeStreamCommand for Count {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (CountArgs { column }, input) = args.process().await?; let (LengthArgs { column }, input) = args.process().await?;
let rows: Vec<Value> = input.collect().await; let rows: Vec<Value> = input.collect().await;
let count = if column { let length = if column {
if rows.is_empty() { if rows.is_empty() {
0 0
} else { } else {
@ -42,8 +42,8 @@ impl WholeStreamCommand for Count {
UntaggedValue::Row(dictionary) => dictionary.length(), UntaggedValue::Row(dictionary) => dictionary.length(),
_ => { _ => {
return Err(ShellError::labeled_error( return Err(ShellError::labeled_error(
"Cannot obtain column count", "Cannot obtain column length",
"cannot obtain column count", "cannot obtain column length",
tag, tag,
)); ));
} }
@ -53,19 +53,21 @@ impl WholeStreamCommand for Count {
rows.len() rows.len()
}; };
Ok(OutputStream::one(UntaggedValue::int(count).into_value(tag))) Ok(OutputStream::one(
UntaggedValue::int(length).into_value(tag),
))
} }
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![ vec![
Example { Example {
description: "Count the number of entries in a list", description: "Count the number of entries in a list",
example: "echo [1 2 3 4 5] | count", example: "echo [1 2 3 4 5] | length",
result: Some(vec![UntaggedValue::int(5).into()]), result: Some(vec![UntaggedValue::int(5).into()]),
}, },
Example { Example {
description: "Count the number of columns in the calendar table", description: "Count the number of columns in the calendar table",
example: "cal | count -c", example: "cal | length -c",
result: None, result: None,
}, },
] ]
@ -74,13 +76,13 @@ impl WholeStreamCommand for Count {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Count; use super::Length;
use super::ShellError; use super::ShellError;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(Count {}) test_examples(Length {})
} }
} }

View File

@ -56,59 +56,70 @@ async fn lines(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(args Ok(args
.input .input
.chain(eos) .chain(eos)
.map(move |item| { .filter_map(move |item| {
let leftover_string = leftover_string.clone(); let leftover_string = leftover_string.clone();
async move {
match item {
Value {
value: UntaggedValue::Primitive(Primitive::String(st)),
..
} => {
let mut leftover_string = leftover_string.lock();
match item { let mut buffer = leftover_string.clone();
Value { buffer.push_str(&st);
value: UntaggedValue::Primitive(Primitive::String(st)),
..
} => {
let mut leftover_string = leftover_string.lock();
let lo_lines = (&*leftover_string).lines().map(|x| x.to_string()); let mut lines: Vec<String> =
let st_lines = st.lines().map(|x| x.to_string()); buffer.lines().map(|x| x.to_string()).collect();
let mut lines: Vec<String> = lo_lines.chain(st_lines).collect();
leftover_string.clear(); leftover_string.clear();
if !ends_with_line_ending(&st) { if !ends_with_line_ending(&st) {
if let Some(last) = lines.pop() { if let Some(last) = lines.pop() {
leftover_string.push_str(&last); leftover_string.push_str(&last);
}
}
if !lines.is_empty() {
let success_lines: Vec<_> = lines
.iter()
.map(|x| {
ReturnSuccess::value(
UntaggedValue::string(x).into_untagged_value(),
)
})
.collect();
Some(futures::stream::iter(success_lines))
} else {
None
} }
} }
Value {
let success_lines: Vec<_> = lines value: UntaggedValue::Primitive(Primitive::EndOfStream),
.iter() ..
.map(|x| { } => {
ReturnSuccess::value(UntaggedValue::string(x).into_untagged_value()) let st = (&*leftover_string).lock().clone();
}) if !st.is_empty() {
.collect(); Some(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(st).into_untagged_value(),
futures::stream::iter(success_lines) )]))
} } else {
Value { None
value: UntaggedValue::Primitive(Primitive::EndOfStream), }
..
} => {
let st = (&*leftover_string).lock().clone();
if !st.is_empty() {
futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::string(st).into_untagged_value(),
)])
} else {
futures::stream::iter(vec![])
} }
Value {
tag: value_span, ..
} => Some(futures::stream::iter(vec![Err(
ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
"value originates from here",
value_span,
),
)])),
} }
Value {
tag: value_span, ..
} => futures::stream::iter(vec![Err(ShellError::labeled_error_with_secondary(
"Expected a string from pipeline",
"requires string input",
name_span,
"value originates from here",
value_span,
))]),
} }
}) })
.flatten() .flatten()

View File

@ -74,6 +74,11 @@ documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
example: "open file.csv --encoding iso-8859-1 | from csv", example: "open file.csv --encoding iso-8859-1 | from csv",
result: None, result: None,
}, },
Example {
description: "Lists the contents of a directory (identical to `ls ../projectB`)",
example: "open ../projectB",
result: None,
},
] ]
} }
} }
@ -99,6 +104,8 @@ async fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
let scope = args.scope.clone(); let scope = args.scope.clone();
let cwd = PathBuf::from(args.shell_manager.path()); let cwd = PathBuf::from(args.shell_manager.path());
let shell_manager = args.shell_manager.clone(); let shell_manager = args.shell_manager.clone();
let name = args.call_info.name_tag.clone();
let ctrl_c = args.ctrl_c.clone();
let ( let (
OpenArgs { OpenArgs {
@ -109,6 +116,17 @@ async fn open(args: CommandArgs) -> Result<OutputStream, ShellError> {
_, _,
) = args.process().await?; ) = args.process().await?;
if path.is_dir() {
let args = nu_engine::shell::LsArgs {
path: Some(path),
all: false,
long: false,
short_names: false,
du: false,
};
return shell_manager.ls(args, name, ctrl_c);
}
// TODO: Remove once Streams are supported everywhere! // TODO: Remove once Streams are supported everywhere!
// As a short term workaround for getting AutoConvert and Bat functionality (Those don't currently support Streams) // As a short term workaround for getting AutoConvert and Bat functionality (Those don't currently support Streams)

View File

@ -143,11 +143,11 @@ async fn maybe_autocd_dir<'a>(
// - the command name ends in a path separator, or // - the command name ends in a path separator, or
// - it's not a command on the path and no arguments were given. // - it's not a command on the path and no arguments were given.
let name = &cmd.name; let name = &cmd.name;
let path_name = if name.ends_with(std::path::MAIN_SEPARATOR) let path_name = if name.ends_with(std::path::is_separator)
|| (cmd.args.is_empty() || (cmd.args.is_empty()
&& PathBuf::from(name).is_dir() && PathBuf::from(name).is_dir()
&& dunce::canonicalize(name).is_ok() && dunce::canonicalize(name).is_ok()
&& !crate::commands::classified::external::did_find_command(&name)) && !ctx.host.lock().is_external_cmd(&name))
{ {
Some(name) Some(name)
} else { } else {

View File

@ -1,5 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use nu_engine::WholeStreamCommand; use nu_engine::{script, WholeStreamCommand};
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::expand_path; use nu_parser::expand_path;
@ -50,7 +50,7 @@ pub async fn source(args: CommandArgs) -> Result<OutputStream, ShellError> {
let contents = std::fs::read_to_string(expand_path(&filename.item).into_owned()); let contents = std::fs::read_to_string(expand_path(&filename.item).into_owned());
match contents { match contents {
Ok(contents) => { Ok(contents) => {
let result = crate::script::run_script_standalone(contents, true, &ctx, false).await; let result = script::run_script_standalone(contents, true, &ctx, false).await;
if let Err(err) = result { if let Err(err) = result {
ctx.error(err.into()); ctx.error(err.into());

View File

@ -8,14 +8,48 @@ use nu_protocol::{
use nu_source::{Tag, Tagged}; use nu_source::{Tag, Tagged};
use nu_value_ext::ValueExt; use nu_value_ext::ValueExt;
use chrono::{DateTime, FixedOffset, LocalResult, Offset, TimeZone}; use chrono::{DateTime, FixedOffset, Local, LocalResult, Offset, TimeZone, Utc};
#[derive(Deserialize)] #[derive(Deserialize)]
struct Arguments { struct Arguments {
timezone: Option<Tagged<String>>,
offset: Option<Tagged<i16>>,
format: Option<Tagged<String>>, format: Option<Tagged<String>>,
rest: Vec<ColumnPath>, rest: Vec<ColumnPath>,
} }
// In case it may be confused with chrono::TimeZone
#[derive(Clone)]
enum Zone {
Utc,
Local,
East(u8),
West(u8),
Error, // we want the nullshell to cast it instead of rust
}
impl Zone {
fn new(i: i16) -> Self {
if i.abs() <= 12 {
// guanranteed here
if i >= 0 {
Self::East(i as u8) // won't go out of range
} else {
Self::West(-i as u8) // same here
}
} else {
Self::Error // Out of range
}
}
fn from_string(s: String) -> Self {
match s.to_lowercase().as_str() {
"utc" | "u" => Self::Utc,
"local" | "l" => Self::Local,
_ => Self::Error,
}
}
}
pub struct SubCommand; pub struct SubCommand;
#[async_trait] #[async_trait]
@ -26,6 +60,18 @@ impl WholeStreamCommand for SubCommand {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("str to-datetime") Signature::build("str to-datetime")
.named(
"timezone",
SyntaxShape::String,
"Specify timezone if the input is timestamp, like 'UTC/u' or 'LOCAL/l'",
Some('z'),
)
.named(
"offset",
SyntaxShape::Int,
"Specify timezone by offset if the input is timestamp, like '+8', '-4', prior than timezone",
Some('o'),
)
.named( .named(
"format", "format",
SyntaxShape::String, SyntaxShape::String,
@ -63,6 +109,17 @@ impl WholeStreamCommand for SubCommand {
example: "echo '20200904_163918+0000' | str to-datetime -f '%Y%m%d_%H%M%S%z'", example: "echo '20200904_163918+0000' | str to-datetime -f '%Y%m%d_%H%M%S%z'",
result: None, result: None,
}, },
Example {
description: "Convert to datetime using a specified timezone",
example: "echo '1614434140' | str to-datetime -z 'UTC'",
result: None,
},
Example {
description:
"Convert to datetime using a specified timezone offset (between -12 and 12)",
example: "echo '1614434140' | str to-datetime -o '+9'",
result: None,
},
] ]
} }
} }
@ -71,11 +128,42 @@ impl WholeStreamCommand for SubCommand {
struct DatetimeFormat(String); struct DatetimeFormat(String);
async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (Arguments { format, rest }, input) = args.process().await?; let (
Arguments {
timezone,
offset,
format,
rest,
},
input,
) = args.process().await?;
let column_paths: Vec<_> = rest; let column_paths: Vec<_> = rest;
let options = if let Some(Tagged { item: fmt, .. }) = format { // if zone-offset is specified, then zone will be neglected
let zone_options = if let Some(Tagged {
item: zone_offset,
tag: _tag,
}) = offset
{
Some(Tagged {
item: Zone::new(zone_offset),
tag: _tag,
})
} else if let Some(Tagged {
item: zone,
tag: _tag,
}) = timezone
{
Some(Tagged {
item: Zone::from_string(zone),
tag: _tag,
})
} else {
None
};
let format_options = if let Some(Tagged { item: fmt, .. }) = format {
Some(DatetimeFormat(fmt)) Some(DatetimeFormat(fmt))
} else { } else {
None None
@ -84,16 +172,17 @@ async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(input Ok(input
.map(move |v| { .map(move |v| {
if column_paths.is_empty() { if column_paths.is_empty() {
ReturnSuccess::value(action(&v, &options, v.tag())?) ReturnSuccess::value(action(&v, &zone_options, &format_options, v.tag())?)
} else { } else {
let mut ret = v; let mut ret = v;
for path in &column_paths { for path in &column_paths {
let options = options.clone(); let zone_options = zone_options.clone();
let format_options = format_options.clone();
ret = ret.swap_data_by_column_path( ret = ret.swap_data_by_column_path(
path, path,
Box::new(move |old| action(old, &options, old.tag())), Box::new(move |old| action(old, &zone_options, &format_options, old.tag())),
)?; )?;
} }
@ -105,12 +194,41 @@ async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
fn action( fn action(
input: &Value, input: &Value,
options: &Option<DatetimeFormat>, timezone: &Option<Tagged<Zone>>,
dateformat: &Option<DatetimeFormat>,
tag: impl Into<Tag>, tag: impl Into<Tag>,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
match &input.value { match &input.value {
UntaggedValue::Primitive(Primitive::String(s)) => { UntaggedValue::Primitive(Primitive::String(s)) => {
let out = match options { let ts = s.parse::<i64>();
// if timezone if specified, first check if the input is a timestamp.
if let Some(tz) = timezone {
if let Ok(t) = ts {
const HOUR: i32 = 3600;
let stampout = match tz.item {
Zone::Utc => UntaggedValue::date(Utc.timestamp(t, 0)),
Zone::Local => UntaggedValue::date(Local.timestamp(t, 0)),
Zone::East(i) => {
let eastoffset = FixedOffset::east((i as i32) * HOUR);
UntaggedValue::date(eastoffset.timestamp(t, 0))
}
Zone::West(i) => {
let westoffset = FixedOffset::west((i as i32) * HOUR);
UntaggedValue::date(westoffset.timestamp(t, 0))
}
Zone::Error => {
return Err(ShellError::labeled_error(
"could not continue to convert timestamp",
"given timezone or offset is invalid",
tz.tag().span,
));
}
};
return Ok(stampout.into_value(tag));
}
};
// if it's not, continue and negelect the timezone option.
let out = match dateformat {
Some(dt) => match DateTime::parse_from_str(s, &dt.0) { Some(dt) => match DateTime::parse_from_str(s, &dt.0) {
Ok(d) => UntaggedValue::date(d), Ok(d) => UntaggedValue::date(d),
Err(reason) => { Err(reason) => {
@ -165,9 +283,9 @@ fn action(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;
use super::{action, DatetimeFormat, SubCommand}; use super::{action, DatetimeFormat, SubCommand, Zone};
use nu_protocol::{Primitive, UntaggedValue}; use nu_protocol::{Primitive, UntaggedValue};
use nu_source::Tag; use nu_source::{Tag, Tagged};
use nu_test_support::value::string; use nu_test_support::value::string;
#[test] #[test]
@ -183,7 +301,7 @@ mod tests {
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let actual = action(&date_str, &fmt_options, Tag::unknown()).unwrap(); let actual = action(&date_str, &None, &fmt_options, Tag::unknown()).unwrap();
match actual.value { match actual.value {
UntaggedValue::Primitive(Primitive::Date(_)) => {} UntaggedValue::Primitive(Primitive::Date(_)) => {}
@ -194,7 +312,35 @@ mod tests {
#[test] #[test]
fn takes_iso8601_date_format() { fn takes_iso8601_date_format() {
let date_str = string("2020-08-04T16:39:18+00:00"); let date_str = string("2020-08-04T16:39:18+00:00");
let actual = action(&date_str, &None, Tag::unknown()).unwrap(); let actual = action(&date_str, &None, &None, Tag::unknown()).unwrap();
match actual.value {
UntaggedValue::Primitive(Primitive::Date(_)) => {}
_ => panic!("Didn't convert to date"),
}
}
#[test]
fn takes_timestamp_offset() {
let date_str = string("1614434140");
let timezone_option = Some(Tagged {
item: Zone::East(8),
tag: Tag::unknown(),
});
let actual = action(&date_str, &timezone_option, &None, Tag::unknown()).unwrap();
match actual.value {
UntaggedValue::Primitive(Primitive::Date(_)) => {}
_ => panic!("Didn't convert to date"),
}
}
#[test]
fn takes_timestamp() {
let date_str = string("1614434140");
let timezone_option = Some(Tagged {
item: Zone::Local,
tag: Tag::unknown(),
});
let actual = action(&date_str, &timezone_option, &None, Tag::unknown()).unwrap();
match actual.value { match actual.value {
UntaggedValue::Primitive(Primitive::Date(_)) => {} UntaggedValue::Primitive(Primitive::Date(_)) => {}
_ => panic!("Didn't convert to date"), _ => panic!("Didn't convert to date"),
@ -207,7 +353,7 @@ mod tests {
let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string())); let fmt_options = Some(DatetimeFormat("%d.%m.%Y %H:%M %P %z".to_string()));
let actual = action(&date_str, &fmt_options, Tag::unknown()); let actual = action(&date_str, &None, &fmt_options, Tag::unknown());
assert!(actual.is_err()); assert!(actual.is_err());
} }

View File

@ -7,7 +7,9 @@ use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_table::{draw_table, Alignment, StyledString, TextStyle}; use nu_table::{draw_table, Alignment, StyledString, TextStyle};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::time::Instant; use std::time::Instant;
#[cfg(feature = "table-pager")] #[cfg(feature = "table-pager")]
use { use {
futures::future::join, futures::future::join,

View File

@ -4,16 +4,16 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
pub struct ToCSV; pub struct ToCsv;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ToCSVArgs { pub struct ToCsvArgs {
noheaders: bool, noheaders: bool,
separator: Option<Value>, separator: Option<Value>,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToCSV { impl WholeStreamCommand for ToCsv {
fn name(&self) -> &str { fn name(&self) -> &str {
"to csv" "to csv"
} }
@ -45,7 +45,7 @@ impl WholeStreamCommand for ToCSV {
async fn to_csv(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn to_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let ( let (
ToCSVArgs { ToCsvArgs {
separator, separator,
noheaders, noheaders,
}, },
@ -80,12 +80,12 @@ async fn to_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;
use super::ToCSV; use super::ToCsv;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToCSV {}) test_examples(ToCsv {})
} }
} }

View File

@ -79,10 +79,10 @@ impl Default for HtmlTheme {
#[folder = "assets/"] #[folder = "assets/"]
struct Assets; struct Assets;
pub struct ToHTML; pub struct ToHtml;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ToHTMLArgs { pub struct ToHtmlArgs {
html_color: bool, html_color: bool,
no_color: bool, no_color: bool,
dark: bool, dark: bool,
@ -92,7 +92,7 @@ pub struct ToHTMLArgs {
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToHTML { impl WholeStreamCommand for ToHtml {
fn name(&self) -> &str { fn name(&self) -> &str {
"to html" "to html"
} }
@ -271,7 +271,7 @@ fn get_list_of_theme_names() -> Vec<String> {
async fn to_html(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn to_html(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name_tag = args.call_info.name_tag.clone();
let ( let (
ToHTMLArgs { ToHtmlArgs {
html_color, html_color,
no_color, no_color,
dark, dark,
@ -758,6 +758,6 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToHTML {}) test_examples(ToHtml {})
} }
} }

View File

@ -7,15 +7,15 @@ use nu_protocol::{
use serde::Serialize; use serde::Serialize;
use serde_json::json; use serde_json::json;
pub struct ToJSON; pub struct ToJson;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ToJSONArgs { pub struct ToJsonArgs {
pretty: Option<Value>, pretty: Option<Value>,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToJSON { impl WholeStreamCommand for ToJson {
fn name(&self) -> &str { fn name(&self) -> &str {
"to json" "to json"
} }
@ -160,7 +160,7 @@ fn json_list(input: &[Value]) -> Result<Vec<serde_json::Value>, ShellError> {
async fn to_json(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn to_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name_tag = args.call_info.name_tag.clone();
let (ToJSONArgs { pretty }, input) = args.process().await?; let (ToJsonArgs { pretty }, input) = args.process().await?;
let name_span = name_tag.span; let name_span = name_tag.span;
let input: Vec<Value> = input.collect().await; let input: Vec<Value> = input.collect().await;
@ -256,12 +256,12 @@ async fn to_json(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;
use super::ToJSON; use super::ToJson;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToJSON {}) test_examples(ToJson {})
} }
} }

View File

@ -3,10 +3,10 @@ use nu_engine::WholeStreamCommand;
use nu_errors::{CoerceInto, ShellError}; use nu_errors::{CoerceInto, ShellError};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UnspannedPathMember, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UnspannedPathMember, UntaggedValue, Value};
pub struct ToTOML; pub struct ToToml;
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToTOML { impl WholeStreamCommand for ToToml {
fn name(&self) -> &str { fn name(&self) -> &str {
"to toml" "to toml"
} }
@ -195,7 +195,7 @@ mod tests {
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToTOML {}) test_examples(ToToml {})
} }
#[test] #[test]

View File

@ -4,15 +4,15 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::Signature; use nu_protocol::Signature;
pub struct ToTSV; pub struct ToTsv;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ToTSVArgs { pub struct ToTsvArgs {
noheaders: bool, noheaders: bool,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToTSV { impl WholeStreamCommand for ToTsv {
fn name(&self) -> &str { fn name(&self) -> &str {
"to tsv" "to tsv"
} }
@ -36,7 +36,7 @@ impl WholeStreamCommand for ToTSV {
async fn to_tsv(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn to_tsv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let (ToTSVArgs { noheaders }, input) = args.process().await?; let (ToTsvArgs { noheaders }, input) = args.process().await?;
to_delimited_data(noheaders, '\t', "TSV", input, name).await to_delimited_data(noheaders, '\t', "TSV", input, name).await
} }
@ -44,12 +44,12 @@ async fn to_tsv(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;
use super::ToTSV; use super::ToTsv;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToTSV {}) test_examples(ToTsv {})
} }
} }

View File

@ -3,10 +3,10 @@ use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue, Value};
pub struct ToURL; pub struct ToUrl;
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToURL { impl WholeStreamCommand for ToUrl {
fn name(&self) -> &str { fn name(&self) -> &str {
"to url" "to url"
} }
@ -76,12 +76,12 @@ async fn to_url(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;
use super::ToURL; use super::ToUrl;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToURL {}) test_examples(ToUrl {})
} }
} }

View File

@ -8,15 +8,15 @@ use std::collections::HashSet;
use std::io::Cursor; use std::io::Cursor;
use std::io::Write; use std::io::Write;
pub struct ToXML; pub struct ToXml;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ToXMLArgs { pub struct ToXmlArgs {
pretty: Option<Value>, pretty: Option<Value>,
} }
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToXML { impl WholeStreamCommand for ToXml {
fn name(&self) -> &str { fn name(&self) -> &str {
"to xml" "to xml"
} }
@ -135,7 +135,7 @@ pub fn write_xml_events<W: Write>(
async fn to_xml(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn to_xml(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_tag = args.call_info.name_tag.clone(); let name_tag = args.call_info.name_tag.clone();
let name_span = name_tag.span; let name_span = name_tag.span;
let (ToXMLArgs { pretty }, input) = args.process().await?; let (ToXmlArgs { pretty }, input) = args.process().await?;
let input: Vec<Value> = input.collect().await; let input: Vec<Value> = input.collect().await;
let to_process_input = match input.len() { let to_process_input = match input.len() {
@ -189,12 +189,12 @@ async fn to_xml(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;
use super::ToXML; use super::ToXml;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToXML {}) test_examples(ToXml {})
} }
} }

View File

@ -3,10 +3,10 @@ use nu_engine::WholeStreamCommand;
use nu_errors::{CoerceInto, ShellError}; use nu_errors::{CoerceInto, ShellError};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UnspannedPathMember, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UnspannedPathMember, UntaggedValue, Value};
pub struct ToYAML; pub struct ToYaml;
#[async_trait] #[async_trait]
impl WholeStreamCommand for ToYAML { impl WholeStreamCommand for ToYaml {
fn name(&self) -> &str { fn name(&self) -> &str {
"to yaml" "to yaml"
} }
@ -164,12 +164,12 @@ async fn to_yaml(args: CommandArgs) -> Result<OutputStream, ShellError> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::ShellError; use super::ShellError;
use super::ToYAML; use super::ToYaml;
#[test] #[test]
fn examples_work_as_expected() -> Result<(), ShellError> { fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples; use crate::examples::test as test_examples;
test_examples(ToYAML {}) test_examples(ToYaml {})
} }
} }

View File

@ -6,14 +6,12 @@ mod stub_generate;
use double_echo::Command as DoubleEcho; use double_echo::Command as DoubleEcho;
use double_ls::Command as DoubleLs; use double_ls::Command as DoubleLs;
use stub_generate::{mock_path, Command as StubOpen};
use nu_engine::basic_evaluation_context;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::ParserScope; use nu_parser::ParserScope;
use nu_protocol::hir::ClassifiedBlock; use nu_protocol::hir::ClassifiedBlock;
use nu_protocol::{ShellTypeName, Value}; use nu_protocol::{ShellTypeName, Value};
use nu_source::AnchorLocation; use nu_source::AnchorLocation;
use stub_generate::{mock_path, Command as StubOpen};
use crate::commands::{ use crate::commands::{
Append, BuildString, Each, Echo, First, Get, Keep, Last, Let, Nth, Select, StrCollect, Wrap, Append, BuildString, Each, Echo, First, Get, Keep, Last, Let, Nth, Select, StrCollect, Wrap,
@ -26,7 +24,7 @@ use futures::executor::block_on;
pub fn test_examples(cmd: Command) -> Result<(), ShellError> { pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
let examples = cmd.examples(); let examples = cmd.examples();
let base_context = basic_evaluation_context()?; let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![ base_context.add_commands(vec![
// Command Doubles // Command Doubles
@ -92,7 +90,7 @@ pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> { pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
let examples = cmd.examples(); let examples = cmd.examples();
let base_context = basic_evaluation_context()?; let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![ base_context.add_commands(vec![
whole_stream_command(Echo {}), whole_stream_command(Echo {}),
@ -149,7 +147,7 @@ pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
pub fn test_anchors(cmd: Command) -> Result<(), ShellError> { pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
let examples = cmd.examples(); let examples = cmd.examples();
let base_context = basic_evaluation_context()?; let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![ base_context.add_commands(vec![
// Minimal restricted commands to aid in testing // Minimal restricted commands to aid in testing

View File

@ -8,15 +8,11 @@ extern crate indexmap;
mod prelude; mod prelude;
pub mod commands; pub mod commands;
mod futures; mod futures;
pub mod maybe_print_errors;
pub mod script;
pub mod utils; pub mod utils;
#[cfg(test)] #[cfg(test)]
mod examples; mod examples;
pub use crate::maybe_print_errors::maybe_print_errors;
pub use nu_data::config; pub use nu_data::config;
pub use nu_data::dict::TaggedListBuilder; pub use nu_data::dict::TaggedListBuilder;
pub use nu_data::primitive; pub use nu_data::primitive;

View File

@ -1,17 +0,0 @@
use nu_engine::EvaluationContext;
use nu_source::Text;
pub fn maybe_print_errors(context: &EvaluationContext, source: Text) -> bool {
let errors = context.current_errors.clone();
let mut errors = errors.lock();
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
crate::script::print_err(error, &source, context);
true
} else {
false
}
}

View File

@ -46,7 +46,6 @@ macro_rules! trace_out_stream {
}}; }};
} }
pub(crate) use crate::commands::command::RunnableContext;
pub(crate) use async_trait::async_trait; pub(crate) use async_trait::async_trait;
pub(crate) use bigdecimal::BigDecimal; pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::{Stream, StreamExt}; pub(crate) use futures::{Stream, StreamExt};
@ -58,11 +57,12 @@ pub(crate) use nu_engine::EvaluationContext;
pub(crate) use nu_engine::Example; pub(crate) use nu_engine::Example;
pub(crate) use nu_engine::Host; pub(crate) use nu_engine::Host;
pub(crate) use nu_engine::RawCommandArgs; pub(crate) use nu_engine::RawCommandArgs;
pub(crate) use nu_engine::RunnableContext;
pub(crate) use nu_engine::ShellManager; pub(crate) use nu_engine::ShellManager;
pub(crate) use nu_engine::{get_full_help, CommandArgs, Scope, WholeStreamCommand}; pub(crate) use nu_engine::{get_full_help, CommandArgs, Scope, WholeStreamCommand};
pub(crate) use nu_parser::ParserScope; pub(crate) use nu_parser::ParserScope;
pub(crate) use nu_protocol::{out, row}; pub(crate) use nu_protocol::{out, row};
pub(crate) use nu_source::{AnchorLocation, PrettyDebug, Span, SpannedItem, Tag, TaggedItem, Text}; pub(crate) use nu_source::{AnchorLocation, PrettyDebug, Span, SpannedItem, Tag, TaggedItem};
pub(crate) use nu_stream::ToInputStream; pub(crate) use nu_stream::ToInputStream;
pub(crate) use nu_stream::{InputStream, Interruptible, OutputStream}; pub(crate) use nu_stream::{InputStream, Interruptible, OutputStream};
pub(crate) use nu_value_ext::ValueExt; pub(crate) use nu_value_ext::ValueExt;
@ -71,7 +71,7 @@ pub(crate) use num_traits::cast::ToPrimitive;
pub(crate) use serde::Deserialize; pub(crate) use serde::Deserialize;
pub(crate) use std::collections::VecDeque; pub(crate) use std::collections::VecDeque;
pub(crate) use std::future::Future; pub(crate) use std::future::Future;
pub(crate) use std::sync::atomic::{AtomicBool, Ordering}; pub(crate) use std::sync::atomic::AtomicBool;
pub(crate) use std::sync::Arc; pub(crate) use std::sync::Arc;
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]

View File

@ -17,6 +17,21 @@ pub fn nonu() {
args().iter().skip(1).for_each(|arg| print!("{}", arg)); args().iter().skip(1).for_each(|arg| print!("{}", arg));
} }
pub fn repeater() {
let mut stdout = io::stdout();
let args = args();
let mut args = args.iter().skip(1);
let letter = args.next().expect("needs a character to iterate");
let count = args.next().expect("need the number of times to iterate");
let count: u64 = count.parse().expect("can't convert count to number");
for _ in 0..count {
let _ = write!(stdout, "{}", letter);
}
let _ = stdout.flush();
}
pub fn iecho() { pub fn iecho() {
// println! panics if stdout gets closed, whereas writeln gives us an error // println! panics if stdout gets closed, whereas writeln gives us an error
let mut stdout = io::stdout(); let mut stdout = io::stdout();

View File

@ -1,29 +1,21 @@
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; use nu_test_support::pipeline as input;
use nu_test_support::playground::Playground; use nu_test_support::playground::{says, Playground};
use nu_test_support::{nu, pipeline};
use hamcrest2::assert_that;
use hamcrest2::prelude::*;
#[test] #[test]
fn adds_a_row_to_the_end() { fn adds_a_row_to_the_end() {
Playground::setup("append_test_1", |dirs, sandbox| { Playground::setup("append_test_1", |_, nu| {
sandbox.with_files(vec![FileWithContentToBeTrimmed( assert_that!(
"los_tres_caballeros.txt", nu.pipeline(&input(
r#" r#"
Andrés N. Robalino echo [ "Andrés N. Robalino", "Jonathan Turner", "Yehuda Katz" ]
Jonathan Turner
Yehuda Katz
"#,
)]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open los_tres_caballeros.txt
| lines
| append "pollo loco" | append "pollo loco"
| nth 3 | nth 3
"# "#
)); )),
says().to_stdout("pollo loco")
assert_eq!(actual.out, "pollo loco"); );
}) })
} }

View File

@ -33,7 +33,7 @@ fn cal_friday_the_thirteenths_in_2015() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
cal --full-year 2015 | default friday 0 | where friday == 13 | count cal --full-year 2015 | default friday 0 | where friday == 13 | length
"# "#
)); ));
@ -45,7 +45,7 @@ fn cal_rows_in_2020() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
cal --full-year 2020 | count cal --full-year 2020 | length
"# "#
)); ));

View File

@ -25,7 +25,7 @@ fn discards_rows_where_given_column_is_empty() {
open los_tres_amigos.json open los_tres_amigos.json
| get amigos | get amigos
| compact rusty_luck | compact rusty_luck
| count | length
"# "#
)); ));
@ -41,7 +41,7 @@ fn discards_empty_rows_by_default() {
echo "[1,2,3,14,null]" echo "[1,2,3,14,null]"
| from json | from json
| compact | compact
| count | length
"# "#
)); ));

View File

@ -26,7 +26,7 @@ fn adds_row_data_if_column_missing() {
| get amigos | get amigos
| default rusty_luck 1 | default rusty_luck 1
| where rusty_luck == 1 | where rusty_luck == 1
| count | length
"# "#
)); ));

View File

@ -13,7 +13,7 @@ fn columns() {
] ]
| drop column | drop column
| get | get
| count | length
"#) "#)
); );
@ -62,7 +62,7 @@ fn rows() {
#[test] #[test]
fn more_rows_than_table_has() { fn more_rows_than_table_has() {
let actual = nu!(cwd: ".", "date | drop 50 | count"); let actual = nu!(cwd: ".", "date | drop 50 | length");
assert_eq!(actual.out, "0"); assert_eq!(actual.out, "0");
} }

View File

@ -13,7 +13,7 @@ fn reports_emptiness() {
| get are_empty | get are_empty
| empty? check | empty? check
| where check | where check
| count | length
"# "#
)); ));

View File

@ -17,7 +17,7 @@ fn gets_first_rows_by_amount() {
r#" r#"
ls ls
| first 3 | first 3
| count | length
"# "#
)); ));
@ -40,7 +40,7 @@ fn gets_all_rows_if_amount_higher_than_all_rows() {
r#" r#"
ls ls
| first 99 | first 99
| count | length
"# "#
)); ));
@ -58,7 +58,7 @@ fn gets_first_row_when_no_amount_given() {
r#" r#"
ls ls
| first | first
| count | length
"# "#
)); ));

View File

@ -71,7 +71,7 @@ fn flatten_row_column_explicitly() {
let actual = nu!( let actual = nu!(
cwd: dirs.test(), cwd: dirs.test(),
"open katz.json | flatten people | where name == Andres | count" "open katz.json | flatten people | where name == Andres | length"
); );
assert_eq!(actual.out, "1"); assert_eq!(actual.out, "1");
@ -105,7 +105,7 @@ fn flatten_row_columns_having_same_column_names_flats_separately() {
let actual = nu!( let actual = nu!(
cwd: dirs.test(), cwd: dirs.test(),
"open katz.json | flatten | flatten people city | get city_name | count" "open katz.json | flatten | flatten people city | get city_name | length"
); );
assert_eq!(actual.out, "4"); assert_eq!(actual.out, "4");
@ -139,7 +139,7 @@ fn flatten_table_columns_explicitly() {
let actual = nu!( let actual = nu!(
cwd: dirs.test(), cwd: dirs.test(),
"open katz.json | flatten city | where people.name == Katz | count" "open katz.json | flatten city | where people.name == Katz | length"
); );
assert_eq!(actual.out, "2"); assert_eq!(actual.out, "2");

View File

@ -89,7 +89,7 @@ fn column_paths_are_either_double_quoted_or_regular_unquoted_words_separated_by_
r#" r#"
open sample.toml open sample.toml
| get package."9999" | get package."9999"
| count | length
"# "#
)); ));
@ -151,24 +151,12 @@ fn errors_fetching_by_column_not_present() {
"# "#
)); ));
assert!( assert!(actual.err.contains("Unknown column"),);
actual.err.contains("Unknown column"), assert!(actual.err.contains("There isn't a column named 'taco'"),);
format!("actual: {:?}", actual.err) assert!(actual.err.contains("Perhaps you meant 'taconushell'?"),);
); assert!(actual
assert!( .err
actual.err.contains("There isn't a column named 'taco'"), .contains("Columns available: pizzanushell, taconushell"),);
format!("actual: {:?}", actual.err)
);
assert!(
actual.err.contains("Perhaps you meant 'taconushell'?"),
format!("actual: {:?}", actual.err)
);
assert!(
actual
.err
.contains("Columns available: pizzanushell, taconushell"),
format!("actual: {:?}", actual.err)
);
}) })
} }
@ -191,20 +179,11 @@ fn errors_fetching_by_column_using_a_number() {
"# "#
)); ));
assert!( assert!(actual.err.contains("No rows available"),);
actual.err.contains("No rows available"), assert!(actual.err.contains("A row at '0' can't be indexed."),);
format!("actual: {:?}", actual.err) assert!(actual
); .err
assert!( .contains("Appears to contain columns. Columns available: 0"),)
actual.err.contains("A row at '0' can't be indexed."),
format!("actual: {:?}", actual.err)
);
assert!(
actual
.err
.contains("Appears to contain columns. Columns available: 0"),
format!("actual: {:?}", actual.err)
)
}) })
} }
#[test] #[test]
@ -226,18 +205,9 @@ fn errors_fetching_by_index_out_of_bounds() {
"# "#
)); ));
assert!( assert!(actual.err.contains("Row not found"),);
actual.err.contains("Row not found"), assert!(actual.err.contains("There isn't a row indexed at 3"),);
format!("actual: {:?}", actual.err) assert!(actual.err.contains("The table only has 3 rows (0 to 2)"),)
);
assert!(
actual.err.contains("There isn't a row indexed at 3"),
format!("actual: {:?}", actual.err)
);
assert!(
actual.err.contains("The table only has 3 rows (0 to 2)"),
format!("actual: {:?}", actual.err)
)
}) })
} }

View File

@ -21,7 +21,7 @@ fn groups() {
open los_tres_caballeros.csv open los_tres_caballeros.csv
| group-by rusty_at | group-by rusty_at
| get "10/11/2013" | get "10/11/2013"
| count | length
"# "#
)); ));

View File

@ -83,3 +83,16 @@ fn error_use_both_flags() {
.err .err
.contains("only one of --decode and --encode flags can be used")); .contains("only one of --decode and --encode flags can be used"));
} }
#[test]
fn md5_works_with_file() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open sample.db | hash md5
"#
)
);
assert_eq!(actual.out, "4de97601d232c427977ef11db396c951");
}

View File

@ -1,11 +1,11 @@
use nu_test_support::{nu, pipeline}; use nu_test_support::{nu, pipeline};
#[test] #[test]
fn help_commands_count() { fn help_commands_length() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
help commands | count help commands | length
"# "#
)); ));
@ -16,11 +16,11 @@ fn help_commands_count() {
} }
#[test] #[test]
fn help_generate_docs_count() { fn help_generate_docs_length() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
help generate_docs | flatten | count help generate_docs | flatten | length
"# "#
)); ));

View File

@ -27,7 +27,7 @@ fn gets_last_rows_by_amount() {
r#" r#"
ls ls
| last 3 | last 3
| count | length
"# "#
)); ));
@ -45,7 +45,7 @@ fn gets_last_row_when_no_amount_given() {
r#" r#"
ls ls
| last | last
| count | length
"# "#
)); ));
@ -58,7 +58,7 @@ fn requests_more_rows_than_table_has() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
date | last 50 | count date | last 50 | length
"# "#
)); ));

View File

@ -1,11 +1,11 @@
use nu_test_support::{nu, pipeline}; use nu_test_support::{nu, pipeline};
#[test] #[test]
fn count_columns_in_cal_table() { fn length_columns_in_cal_table() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
cal | count -c cal | length -c
"# "#
)); ));
@ -13,11 +13,11 @@ fn count_columns_in_cal_table() {
} }
#[test] #[test]
fn count_columns_no_rows() { fn length_columns_no_rows() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
echo [] | count -c echo [] | length -c
"# "#
)); ));

View File

@ -42,9 +42,9 @@ fn lines_multi_value_split() {
open sample-simple.json open sample-simple.json
| get first second | get first second
| lines | lines
| count | length
"# "#
)); ));
assert_eq!(actual.out, "6"); assert_eq!(actual.out, "5");
} }

View File

@ -1,5 +1,5 @@
use nu_test_support::fs::Stub::EmptyFile; use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::{Dirs, Playground}; use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline}; use nu_test_support::{nu, pipeline};
#[test] #[test]
@ -15,7 +15,7 @@ fn lists_regular_files() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls ls
| count | length
"# "#
)); ));
@ -37,7 +37,7 @@ fn lists_regular_files_using_asterisk_wildcard() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls *.txt ls *.txt
| count | length
"# "#
)); ));
@ -59,7 +59,7 @@ fn lists_regular_files_using_question_mark_wildcard() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls *.??.txt ls *.??.txt
| count | length
"# "#
)); ));
@ -88,7 +88,7 @@ fn lists_all_files_in_directories_from_stream() {
r#" r#"
echo dir_a dir_b echo dir_a dir_b
| each { ls $it } | each { ls $it }
| count | length
"# "#
)); ));
@ -105,7 +105,7 @@ fn does_not_fail_if_glob_matches_empty_directory() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls dir_a ls dir_a
| count | length
"# "#
)); ));
@ -142,7 +142,7 @@ fn list_files_from_two_parents_up_using_multiple_dots() {
let actual = nu!( let actual = nu!(
cwd: dirs.test().join("foo/bar"), cwd: dirs.test().join("foo/bar"),
r#" r#"
ls ... | count ls ... | length
"# "#
); );
@ -165,7 +165,7 @@ fn lists_hidden_file_when_explicitly_specified() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls .testdotfile ls .testdotfile
| count | length
"# "#
)); ));
@ -199,7 +199,7 @@ fn lists_all_hidden_files_when_glob_contains_dot() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls **/.* ls **/.*
| count | length
"# "#
)); ));
@ -236,7 +236,7 @@ fn lists_all_hidden_files_when_glob_does_not_contain_dot() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls **/* ls **/*
| count | length
"# "#
)); ));
@ -259,7 +259,7 @@ fn lists_files_including_starting_with_dot() {
cwd: dirs.test(), pipeline( cwd: dirs.test(), pipeline(
r#" r#"
ls -a ls -a
| count | length
"# "#
)); ));
@ -269,61 +269,57 @@ fn lists_files_including_starting_with_dot() {
#[test] #[test]
fn list_all_columns() { fn list_all_columns() {
Playground::setup( Playground::setup("ls_test_all_columns", |dirs, sandbox| {
"ls_test_all_columns", sandbox.with_files(vec![
|dirs: Dirs, sandbox: &mut Playground| { EmptyFile("Leonardo.yaml"),
sandbox.with_files(vec![ EmptyFile("Raphael.json"),
EmptyFile("Leonardo.yaml"), EmptyFile("Donatello.xml"),
EmptyFile("Raphael.json"), EmptyFile("Michelangelo.txt"),
EmptyFile("Donatello.xml"), ]);
EmptyFile("Michelangelo.txt"), // Normal Operation
]); let actual = nu!(
// Normal Operation cwd: dirs.test(),
let actual = nu!( "ls | get | to md"
cwd: dirs.test(), );
"ls | get | to md" let expected = ["name", "type", "size", "modified"].join("");
); assert_eq!(actual.out, expected, "column names are incorrect for ls");
let expected = ["name", "type", "size", "modified"].join(""); // Long
assert_eq!(actual.out, expected, "column names are incorrect for ls"); let actual = nu!(
// Long cwd: dirs.test(),
let actual = nu!( "ls -l | get | to md"
cwd: dirs.test(), );
"ls -l | get | to md" let expected = {
); #[cfg(unix)]
let expected = { {
#[cfg(unix)] [
{ "name",
[ "type",
"name", "target",
"type", "num_links",
"target", "inode",
"num_links", "readonly",
"inode", "mode",
"readonly", "uid",
"mode", "group",
"uid", "size",
"group", "created",
"size", "accessed",
"created", "modified",
"accessed", ]
"modified", .join("")
] }
.join("")
}
#[cfg(windows)] #[cfg(windows)]
{ {
[ [
"name", "type", "target", "readonly", "size", "created", "accessed", "name", "type", "target", "readonly", "size", "created", "accessed", "modified",
"modified", ]
] .join("")
.join("") }
} };
}; assert_eq!(
assert_eq!( actual.out, expected,
actual.out, expected, "column names are incorrect for ls long"
"column names are incorrect for ls long" );
); });
},
);
} }

View File

@ -70,7 +70,7 @@ fn show_created_paths() {
pipeline( pipeline(
r#" r#"
mkdir -s dir_1 dir_2 dir_3 mkdir -s dir_1 dir_2 dir_3
| count | length
"# "#
)); ));

View File

@ -1,11 +1,7 @@
mod append; mod append;
mod autoenv;
mod autoenv_trust;
mod autoenv_untrust;
mod cal; mod cal;
mod cd; mod cd;
mod compact; mod compact;
mod count;
mod cp; mod cp;
mod def; mod def;
mod default; mod default;
@ -28,6 +24,7 @@ mod insert;
mod into_int; mod into_int;
mod keep; mod keep;
mod last; mod last;
mod length;
mod lines; mod lines;
mod ls; mod ls;
mod math; mod math;

View File

@ -28,7 +28,7 @@ fn selects_many_rows() {
ls ls
| get name | get name
| nth 1 0 | nth 1 0
| count | length
"# "#
)); ));

View File

@ -1,3 +1,4 @@
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::fs::Stub::FileWithContentToBeTrimmed; use nu_test_support::fs::Stub::FileWithContentToBeTrimmed;
use nu_test_support::playground::Playground; use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline}; use nu_test_support::{nu, pipeline};
@ -230,3 +231,24 @@ fn errors_if_file_not_found() {
expected expected
); );
} }
#[test]
fn open_dir_is_ls() {
Playground::setup("open_dir", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jonathan.txt"),
EmptyFile("andres.txt"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
open .
| length
"#
));
assert_eq!(actual.out, "3");
})
}

View File

@ -5,7 +5,7 @@ fn rolls_4_roll() {
let actual = nu!( let actual = nu!(
cwd: ".", pipeline( cwd: ".", pipeline(
r#" r#"
random dice -d 4 -s 10 | count random dice -d 4 -s 10 | length
"# "#
)); ));

View File

@ -36,7 +36,7 @@ fn selects_some_rows() {
ls ls
| get name | get name
| range 1..2 | range 1..2
| count | length
"# "#
)); ));

View File

@ -23,7 +23,7 @@ fn changes_the_column_name() {
| wrap name | wrap name
| rename mosqueteros | rename mosqueteros
| get mosqueteros | get mosqueteros
| count | length
"# "#
)); ));
@ -53,7 +53,7 @@ fn keeps_remaining_original_names_given_less_new_names_than_total_original_names
| default hit "arepa!" | default hit "arepa!"
| rename mosqueteros | rename mosqueteros
| get hit | get hit
| count | length
"# "#
)); ));
@ -83,13 +83,7 @@ fn errors_if_no_columns_present() {
"# "#
)); ));
assert!( assert!(actual.err.contains("no column names available"));
actual.err.contains("no column names available"), assert!(actual.err.contains("can't rename"));
format!("actual: {:?}", actual.err)
);
assert!(
actual.err.contains("can't rename"),
format!("actual: {:?}", actual.err)
);
}) })
} }

View File

@ -76,7 +76,7 @@ fn allows_if_given_unknown_column_name_is_missing() {
[Yehuda Katz 10/11/2013 A] [Yehuda Katz 10/11/2013 A]
] ]
| select rrusty_at first_name | select rrusty_at first_name
| count | length
"# "#
)); ));

View File

@ -22,7 +22,7 @@ fn splits() {
| group-by rusty_at | group-by rusty_at
| split-by type | split-by type
| get A."10/11/2013" | get A."10/11/2013"
| count | length
"# "#
)); ));

View File

@ -19,7 +19,7 @@ fn to_row() {
| lines | lines
| str trim | str trim
| split row "," | split row ","
| count | length
"# "#
)); ));

View File

@ -22,7 +22,7 @@ fn removes_duplicate_rows() {
r#" r#"
open los_tres_caballeros.csv open los_tres_caballeros.csv
| uniq | uniq
| count | length
"# "#
)); ));
@ -52,7 +52,7 @@ fn uniq_values() {
open los_tres_caballeros.csv open los_tres_caballeros.csv
| select type | select type
| uniq | uniq
| count | length
"# "#
)); ));
@ -117,7 +117,7 @@ fn nested_json_structures() {
r#" r#"
open nested_json_structures.json open nested_json_structures.json
| uniq | uniq
| count | length
"# "#
)); ));
@ -133,7 +133,7 @@ fn uniq_when_keys_out_of_order() {
echo '[{"a": "a", "b": [1,2,3]},{"b": [1,2,3], "a": "a"}]' echo '[{"a": "a", "b": [1,2,3]},{"b": [1,2,3], "a": "a"}]'
| from json | from json
| uniq | uniq
| count | length
"# "#
)); ));

Some files were not shown because too many files have changed in this diff Show More