2022-12-22 15:46:55 +01:00
|
|
|
/// Run a command in nu and get its output
|
2022-08-13 04:13:50 +02:00
|
|
|
///
|
|
|
|
/// The `nu!` macro accepts a number of options like the `cwd` in which the
|
|
|
|
/// command should be run. It is also possible to specify a different `locale`
|
|
|
|
/// to test locale dependent commands.
|
|
|
|
///
|
|
|
|
/// Pass options as the first arguments in the form of `key_1: value_1, key_1:
|
|
|
|
/// value_2, ...`. The options are defined in the `NuOpts` struct inside the
|
|
|
|
/// `nu!` macro.
|
|
|
|
///
|
|
|
|
/// The command can be formatted using `{}` just like `println!` or `format!`.
|
|
|
|
/// Pass the format arguments comma separated after the command itself.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```no_run
|
|
|
|
/// # // NOTE: The `nu!` macro needs the `nu` binary to exist. The test are
|
Fix typos by codespell (#7600)
# Description
Found via `codespell -S target -L
crate,ser,numer,falsy,ro,te,nd,bu,ndoes,statics,ons,fo,rouge,pard`
# User-Facing Changes
None.
# Tests + Formatting
None and done.
# After Submitting
None.
2022-12-26 08:31:26 +01:00
|
|
|
/// # // therefore only compiled but not run (that's what the `no_run` at
|
2022-08-13 04:13:50 +02:00
|
|
|
/// # // the beginning of this code block is for).
|
|
|
|
/// #
|
|
|
|
/// use nu_test_support::nu;
|
|
|
|
///
|
|
|
|
/// let outcome = nu!(
|
|
|
|
/// "date now | date to-record | get year"
|
|
|
|
/// );
|
|
|
|
///
|
|
|
|
/// let dir = "/";
|
|
|
|
/// let outcome = nu!(
|
|
|
|
/// "ls {} | get name",
|
|
|
|
/// dir,
|
|
|
|
/// );
|
|
|
|
///
|
|
|
|
/// let outcome = nu!(
|
|
|
|
/// cwd: "/",
|
|
|
|
/// "ls | get name",
|
|
|
|
/// );
|
|
|
|
///
|
|
|
|
/// let cell = "size";
|
|
|
|
/// let outcome = nu!(
|
|
|
|
/// locale: "de_DE.UTF-8",
|
|
|
|
/// "ls | into int {}",
|
|
|
|
/// cell,
|
|
|
|
/// );
|
|
|
|
///
|
|
|
|
/// let decimals = 2;
|
|
|
|
/// let outcome = nu!(
|
|
|
|
/// locale: "de_DE.UTF-8",
|
|
|
|
/// "10 | into string --decimals {}",
|
|
|
|
/// decimals,
|
|
|
|
/// );
|
|
|
|
/// ```
|
2019-12-15 17:15:06 +01:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! nu {
|
2022-08-13 04:13:50 +02:00
|
|
|
// In the `@options` phase, we restucture all the
|
|
|
|
// `$field_1: $value_1, $field_2: $value_2, ...`
|
|
|
|
// pairs to a structure like
|
|
|
|
// `@options[ $field_1 => $value_1 ; $field_2 => $value_2 ; ... ]`.
|
|
|
|
// We do this to later distinguish the options from the `$path` and `$part`s.
|
|
|
|
// (See
|
|
|
|
// https://users.rust-lang.org/t/i-dont-think-this-local-ambiguity-when-calling-macro-is-ambiguous/79401?u=x3ro
|
|
|
|
// )
|
|
|
|
//
|
|
|
|
// If there is any special treatment needed for the `$value`, we can just
|
|
|
|
// match for the specific `field` name.
|
|
|
|
(
|
|
|
|
@options [ $($options:tt)* ]
|
|
|
|
cwd: $value:expr,
|
|
|
|
$($rest:tt)*
|
|
|
|
) => {
|
|
|
|
nu!(@options [ $($options)* cwd => $crate::fs::in_directory($value) ; ] $($rest)*)
|
2023-04-10 00:55:29 +02:00
|
|
|
};
|
|
|
|
// For all other options, we call `.into()` on the `$value` and hope for the best. ;)
|
|
|
|
(
|
|
|
|
@options [ $($options:tt)* ]
|
|
|
|
$field:ident : $value:expr,
|
|
|
|
$($rest:tt)*
|
|
|
|
) => {
|
|
|
|
nu!(@options [ $($options)* $field => $value.into() ; ] $($rest)*)
|
|
|
|
};
|
|
|
|
|
|
|
|
// When the `$field: $value,` pairs are all parsed, the next tokens are the `$path` and any
|
|
|
|
// number of `$part`s, potentially followed by a trailing comma.
|
|
|
|
(
|
|
|
|
@options [ $($options:tt)* ]
|
|
|
|
$path:expr
|
|
|
|
$(, $part:expr)*
|
|
|
|
$(,)*
|
|
|
|
) => {{
|
|
|
|
// Here we parse the options into a `NuOpts` struct
|
|
|
|
let opts = nu!(@nu_opts $($options)*);
|
|
|
|
// and format the `$path` using the `$part`s
|
|
|
|
let path = nu!(@format_path $path, $($part),*);
|
|
|
|
// Then finally we go to the `@main` phase, where the actual work is done.
|
|
|
|
nu!(@main opts, path)
|
|
|
|
}};
|
|
|
|
|
|
|
|
// Create the NuOpts struct from the `field => value ;` pairs
|
|
|
|
(@nu_opts $( $field:ident => $value:expr ; )*) => {
|
2023-09-21 20:11:56 +02:00
|
|
|
$crate::macros::NuOpts{
|
2023-04-10 00:55:29 +02:00
|
|
|
$(
|
|
|
|
$field: Some($value),
|
|
|
|
)*
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Helper to format `$path`.
|
|
|
|
(@format_path $path:expr $(,)?) => {
|
|
|
|
// When there are no `$part`s, do not format anything
|
|
|
|
$path
|
|
|
|
};
|
|
|
|
(@format_path $path:expr, $($part:expr),* $(,)?) => {{
|
|
|
|
format!($path, $( $part ),*)
|
|
|
|
}};
|
|
|
|
|
|
|
|
// Do the actual work.
|
|
|
|
(@main $opts:expr, $path:expr) => {{
|
2023-09-21 20:11:56 +02:00
|
|
|
$crate::macros::nu_run_test($opts, $path, false)
|
2023-04-10 00:55:29 +02:00
|
|
|
}};
|
|
|
|
|
|
|
|
// This is the entrypoint for this macro.
|
|
|
|
($($token:tt)*) => {{
|
|
|
|
|
|
|
|
nu!(@options [ ] $($token)*)
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[macro_export]
|
|
|
|
macro_rules! nu_with_std {
|
|
|
|
// In the `@options` phase, we restucture all the
|
|
|
|
// `$field_1: $value_1, $field_2: $value_2, ...`
|
|
|
|
// pairs to a structure like
|
|
|
|
// `@options[ $field_1 => $value_1 ; $field_2 => $value_2 ; ... ]`.
|
|
|
|
// We do this to later distinguish the options from the `$path` and `$part`s.
|
|
|
|
// (See
|
|
|
|
// https://users.rust-lang.org/t/i-dont-think-this-local-ambiguity-when-calling-macro-is-ambiguous/79401?u=x3ro
|
|
|
|
// )
|
|
|
|
//
|
|
|
|
// If there is any special treatment needed for the `$value`, we can just
|
|
|
|
// match for the specific `field` name.
|
|
|
|
(
|
|
|
|
@options [ $($options:tt)* ]
|
|
|
|
cwd: $value:expr,
|
|
|
|
$($rest:tt)*
|
|
|
|
) => {
|
|
|
|
nu!(@options [ $($options)* cwd => $crate::fs::in_directory($value) ; ] $($rest)*)
|
2022-08-13 04:13:50 +02:00
|
|
|
};
|
|
|
|
// For all other options, we call `.into()` on the `$value` and hope for the best. ;)
|
|
|
|
(
|
|
|
|
@options [ $($options:tt)* ]
|
|
|
|
$field:ident : $value:expr,
|
|
|
|
$($rest:tt)*
|
|
|
|
) => {
|
|
|
|
nu!(@options [ $($options)* $field => $value.into() ; ] $($rest)*)
|
|
|
|
};
|
|
|
|
|
|
|
|
// When the `$field: $value,` pairs are all parsed, the next tokens are the `$path` and any
|
|
|
|
// number of `$part`s, potentially followed by a trailing comma.
|
|
|
|
(
|
|
|
|
@options [ $($options:tt)* ]
|
|
|
|
$path:expr
|
|
|
|
$(, $part:expr)*
|
|
|
|
$(,)*
|
|
|
|
) => {{
|
|
|
|
// Here we parse the options into a `NuOpts` struct
|
|
|
|
let opts = nu!(@nu_opts $($options)*);
|
|
|
|
// and format the `$path` using the `$part`s
|
|
|
|
let path = nu!(@format_path $path, $($part),*);
|
|
|
|
// Then finally we go to the `@main` phase, where the actual work is done.
|
|
|
|
nu!(@main opts, path)
|
2019-12-15 17:15:06 +01:00
|
|
|
}};
|
|
|
|
|
2022-08-13 04:13:50 +02:00
|
|
|
// Create the NuOpts struct from the `field => value ;` pairs
|
|
|
|
(@nu_opts $( $field:ident => $value:expr ; )*) => {
|
2023-09-21 20:11:56 +02:00
|
|
|
$crate::macros::NuOpts{
|
2022-08-13 04:13:50 +02:00
|
|
|
$(
|
|
|
|
$field: Some($value),
|
|
|
|
)*
|
|
|
|
..Default::default()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Helper to format `$path`.
|
|
|
|
(@format_path $path:expr $(,)?) => {
|
|
|
|
// When there are no `$part`s, do not format anything
|
|
|
|
$path
|
|
|
|
};
|
|
|
|
(@format_path $path:expr, $($part:expr),* $(,)?) => {{
|
|
|
|
format!($path, $( $part ),*)
|
2019-12-15 17:15:06 +01:00
|
|
|
}};
|
|
|
|
|
2022-08-13 04:13:50 +02:00
|
|
|
// Do the actual work.
|
|
|
|
(@main $opts:expr, $path:expr) => {{
|
2023-09-21 20:11:56 +02:00
|
|
|
$crate::macros::nu_run_test($opts, $path, true)
|
2020-09-14 16:07:02 +02:00
|
|
|
}};
|
2022-08-13 04:13:50 +02:00
|
|
|
|
|
|
|
// This is the entrypoint for this macro.
|
|
|
|
($($token:tt)*) => {{
|
|
|
|
nu!(@options [ ] $($token)*)
|
|
|
|
}};
|
2020-09-14 16:07:02 +02:00
|
|
|
}
|
|
|
|
|
2022-07-22 06:14:37 +02:00
|
|
|
#[macro_export]
|
|
|
|
macro_rules! nu_with_plugins {
|
2024-04-21 14:36:26 +02:00
|
|
|
(cwd: $cwd:expr, plugins: [$(($plugin_name:expr)),*$(,)?], $command:expr) => {{
|
Local socket mode and foreground terminal control for plugins (#12448)
# Description
Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.
This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.
This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.
This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.
TODO:
- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
- Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
- Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
- `nu_plugin_stress_internals` added
# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
|
|
|
nu_with_plugins!(
|
|
|
|
cwd: $cwd,
|
|
|
|
envs: Vec::<(&str, &str)>::new(),
|
2024-04-21 14:36:26 +02:00
|
|
|
plugins: [$(($plugin_name)),*],
|
Local socket mode and foreground terminal control for plugins (#12448)
# Description
Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.
This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.
This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.
This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.
TODO:
- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
- Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
- Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
- `nu_plugin_stress_internals` added
# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
|
|
|
$command
|
|
|
|
)
|
2022-07-22 06:14:37 +02:00
|
|
|
}};
|
2022-09-07 16:07:42 +02:00
|
|
|
(cwd: $cwd:expr, plugin: ($plugin_name:expr), $command:expr) => {{
|
Local socket mode and foreground terminal control for plugins (#12448)
# Description
Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.
This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.
This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.
This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.
TODO:
- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
- Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
- Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
- `nu_plugin_stress_internals` added
# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
|
|
|
nu_with_plugins!(
|
|
|
|
cwd: $cwd,
|
|
|
|
envs: Vec::<(&str, &str)>::new(),
|
|
|
|
plugin: ($plugin_name),
|
|
|
|
$command
|
|
|
|
)
|
|
|
|
}};
|
|
|
|
|
|
|
|
(
|
|
|
|
cwd: $cwd:expr,
|
|
|
|
envs: $envs:expr,
|
2024-04-21 14:36:26 +02:00
|
|
|
plugins: [$(($plugin_name:expr)),*$(,)?],
|
Local socket mode and foreground terminal control for plugins (#12448)
# Description
Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.
This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.
This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.
This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.
TODO:
- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
- Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
- Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
- `nu_plugin_stress_internals` added
# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
|
|
|
$command:expr
|
|
|
|
) => {{
|
2024-04-21 14:36:26 +02:00
|
|
|
$crate::macros::nu_with_plugin_run_test($cwd, $envs, &[$($plugin_name),*], $command)
|
Local socket mode and foreground terminal control for plugins (#12448)
# Description
Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.
This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.
This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.
This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.
TODO:
- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
- Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
- Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
- `nu_plugin_stress_internals` added
# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
|
|
|
}};
|
|
|
|
(cwd: $cwd:expr, envs: $envs:expr, plugin: ($plugin_name:expr), $command:expr) => {{
|
|
|
|
$crate::macros::nu_with_plugin_run_test($cwd, $envs, &[$plugin_name], $command)
|
2020-09-14 16:07:02 +02:00
|
|
|
}};
|
|
|
|
|
2023-09-21 20:11:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
use crate::{Outcome, NATIVE_PATH_ENV_VAR};
|
2024-07-12 04:43:10 +02:00
|
|
|
use nu_path::{AbsolutePath, AbsolutePathBuf, Path};
|
2023-09-21 20:11:56 +02:00
|
|
|
use std::{
|
2024-07-12 04:43:10 +02:00
|
|
|
ffi::OsStr,
|
2023-09-21 20:11:56 +02:00
|
|
|
process::{Command, Stdio},
|
|
|
|
};
|
|
|
|
use tempfile::tempdir;
|
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct NuOpts {
|
2024-07-12 04:43:10 +02:00
|
|
|
pub cwd: Option<AbsolutePathBuf>,
|
2023-09-21 20:11:56 +02:00
|
|
|
pub locale: Option<String>,
|
Move most of the peculiar argument handling for external calls into the parser (#13089)
# Description
We've had a lot of different issues and PRs related to arg handling with
externals since the rewrite of `run-external` in #12921:
- #12950
- #12955
- #13000
- #13001
- #13021
- #13027
- #13028
- #13073
Many of these are caused by the argument handling of external calls and
`run-external` being very special and involving the parser handing
quoted strings over to `run-external` so that it knows whether to expand
tildes and globs and so on. This is really unusual and also makes it
harder to use `run-external`, and also harder to understand it (and
probably is part of the reason why it was rewritten in the first place).
This PR moves a lot more of that work over to the parser, so that by the
time `run-external` gets it, it's dealing with much more normal Nushell
values. In particular:
- Unquoted strings are handled as globs with no expand
- The unescaped-but-quoted handling of strings was removed, and the
parser constructs normal looking strings instead, removing internal
quotes so that `run-external` doesn't have to do it
- Bare word interpolation is now supported and expansion is done in this
case
- Expressions typed as `Glob` containing `Expr::StringInterpolation` now
produce `Value::Glob` instead, with the quoted status from the expr
passed through so we know if it was a bare word
- Bare word interpolation for values typed as `glob` now possible, but
not implemented
- Because expansion is now triggered by `Value::Glob(_, false)` instead
of looking at the expr, externals now support glob types
# User-Facing Changes
- Bare word interpolation works for external command options, and
otherwise embedded in other strings:
```nushell
^echo --foo=(2 + 2) # prints --foo=4
^echo -foo=$"(2 + 2)" # prints -foo=4
^echo foo="(2 + 2)" # prints (no interpolation!) foo=(2 + 2)
^echo foo,(2 + 2),bar # prints foo,4,bar
```
- Bare word interpolation expands for external command head/args:
```nushell
let name = "exa"
~/.cargo/bin/($name) # this works, and expands the tilde
^$"~/.cargo/bin/($name)" # this doesn't expand the tilde
^echo ~/($name)/* # this glob is expanded
^echo $"~/($name)/*" # this isn't expanded
```
- Ndots are now supported for the head of an external command
(`^.../foo` works)
- Glob values are now supported for head/args of an external command,
and expanded appropriately:
```nushell
^("~/.cargo/bin/exa" | into glob) # the tilde is expanded
^echo ("*.txt" | into glob) # this glob is expanded
```
- `run-external` now works more like any other command, without
expecting a special call convention
for its args:
```nushell
run-external echo "'foo'"
# before PR: 'foo'
# after PR: foo
run-external echo "*.txt"
# before PR: (glob is expanded)
# after PR: *.txt
```
# Tests + Formatting
Lots of tests added and cleaned up. Some tests that weren't active on
Windows changed to use `nu --testbin cococo` so that they can work.
Added a test for Linux only to make sure tilde expansion of commands
works, because changing `HOME` there causes `~` to reliably change.
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] release notes: make sure to mention the new syntaxes that are
supported
2024-06-20 06:00:03 +02:00
|
|
|
pub envs: Option<Vec<(String, String)>>,
|
2024-04-26 13:23:16 +02:00
|
|
|
pub collapse_output: Option<bool>,
|
Internal representation (IR) compiler and evaluator (#13330)
# Description
This PR adds an internal representation language to Nushell, offering an
alternative evaluator based on simple instructions, stream-containing
registers, and indexed control flow. The number of registers required is
determined statically at compile-time, and the fixed size required is
allocated upon entering the block.
Each instruction is associated with a span, which makes going backwards
from IR instructions to source code very easy.
Motivations for IR:
1. **Performance.** By simplifying the evaluation path and making it
more cache-friendly and branch predictor-friendly, code that does a lot
of computation in Nushell itself can be sped up a decent bit. Because
the IR is fairly easy to reason about, we can also implement
optimization passes in the future to eliminate and simplify code.
2. **Correctness.** The instructions mostly have very simple and
easily-specified behavior, so hopefully engine changes are a little bit
easier to reason about, and they can be specified in a more formal way
at some point. I have made an effort to document each of the
instructions in the docs for the enum itself in a reasonably specific
way. Some of the errors that would have happened during evaluation
before are now moved to the compilation step instead, because they don't
make sense to check during evaluation.
3. **As an intermediate target.** This is a good step for us to bring
the [`new-nu-parser`](https://github.com/nushell/new-nu-parser) in at
some point, as code generated from new AST can be directly compared to
code generated from old AST. If the IR code is functionally equivalent,
it will behave the exact same way.
4. **Debugging.** With a little bit more work, we can probably give
control over advancing the virtual machine that `IrBlock`s run on to
some sort of external driver, making things like breakpoints and single
stepping possible. Tools like `view ir` and [`explore
ir`](https://github.com/devyn/nu_plugin_explore_ir) make it easier than
before to see what exactly is going on with your Nushell code.
The goal is to eventually replace the AST evaluator entirely, once we're
sure it's working just as well. You can help dogfood this by running
Nushell with `$env.NU_USE_IR` set to some value. The environment
variable is checked when Nushell starts, so config runs with IR, or it
can also be set on a line at the REPL to change it dynamically. It is
also checked when running `do` in case within a script you want to just
run a specific piece of code with or without IR.
# Example
```nushell
view ir { |data|
mut sum = 0
for n in $data {
$sum += $n
}
$sum
}
```
```gas
# 3 registers, 19 instructions, 0 bytes of data
0: load-literal %0, int(0)
1: store-variable var 904, %0 # let
2: drain %0
3: drop %0
4: load-variable %1, var 903
5: iterate %0, %1, end 15 # for, label(1), from(14:)
6: store-variable var 905, %0
7: load-variable %0, var 904
8: load-variable %2, var 905
9: binary-op %0, Math(Plus), %2
10: span %0
11: store-variable var 904, %0
12: load-literal %0, nothing
13: drain %0
14: jump 5
15: drop %0 # label(0), from(5:)
16: drain %0
17: load-variable %0, var 904
18: return %0
```
# Benchmarks
All benchmarks run on a base model Mac Mini M1.
## Iterative Fibonacci sequence
This is about as best case as possible, making use of the much faster
control flow. Most code will not experience a speed improvement nearly
this large.
```nushell
def fib [n: int] {
mut a = 0
mut b = 1
for _ in 2..=$n {
let c = $a + $b
$a = $b
$b = $c
}
$b
}
use std bench
bench { 0..50 | each { |n| fib $n } }
```
IR disabled:
```
╭───────┬─────────────────╮
│ mean │ 1ms 924µs 665ns │
│ min │ 1ms 700µs 83ns │
│ max │ 3ms 450µs 125ns │
│ std │ 395µs 759ns │
│ times │ [list 50 items] │
╰───────┴─────────────────╯
```
IR enabled:
```
╭───────┬─────────────────╮
│ mean │ 452µs 820ns │
│ min │ 427µs 417ns │
│ max │ 540µs 167ns │
│ std │ 17µs 158ns │
│ times │ [list 50 items] │
╰───────┴─────────────────╯
```
![explore ir
view](https://github.com/nushell/nushell/assets/10729/d7bccc03-5222-461c-9200-0dce71b83b83)
##
[gradient_benchmark_no_check.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/gradient_benchmark_no_check.nu)
IR disabled:
```
╭───┬──────────────────╮
│ 0 │ 27ms 929µs 958ns │
│ 1 │ 21ms 153µs 459ns │
│ 2 │ 18ms 639µs 666ns │
│ 3 │ 19ms 554µs 583ns │
│ 4 │ 13ms 383µs 375ns │
│ 5 │ 11ms 328µs 208ns │
│ 6 │ 5ms 659µs 542ns │
╰───┴──────────────────╯
```
IR enabled:
```
╭───┬──────────────────╮
│ 0 │ 22ms 662µs │
│ 1 │ 17ms 221µs 792ns │
│ 2 │ 14ms 786µs 708ns │
│ 3 │ 13ms 876µs 834ns │
│ 4 │ 13ms 52µs 875ns │
│ 5 │ 11ms 269µs 666ns │
│ 6 │ 6ms 942µs 500ns │
╰───┴──────────────────╯
```
##
[random-bytes.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/random-bytes.nu)
I got pretty random results out of this benchmark so I decided not to
include it. Not clear why.
# User-Facing Changes
- IR compilation errors may appear even if the user isn't evaluating
with IR.
- IR evaluation can be enabled by setting the `NU_USE_IR` environment
variable to any value.
- New command `view ir` pretty-prints the IR for a block, and `view ir
--json` can be piped into an external tool like [`explore
ir`](https://github.com/devyn/nu_plugin_explore_ir).
# Tests + Formatting
All tests are passing with `NU_USE_IR=1`, and I've added some more eval
tests to compare the results for some very core operations. I will
probably want to add some more so we don't have to always check
`NU_USE_IR=1 toolkit test --workspace` on a regular basis.
# After Submitting
- [ ] release notes
- [ ] further documentation of instructions?
- [ ] post-release: publish `nu_plugin_explore_ir`
2024-07-11 02:33:59 +02:00
|
|
|
pub use_ir: Option<bool>,
|
2023-09-21 20:11:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn nu_run_test(opts: NuOpts, commands: impl AsRef<str>, with_std: bool) -> Outcome {
|
2024-07-12 04:43:10 +02:00
|
|
|
let test_bins = crate::fs::binaries()
|
|
|
|
.canonicalize()
|
|
|
|
.expect("Could not canonicalize dummy binaries path");
|
2023-09-21 20:11:56 +02:00
|
|
|
|
|
|
|
let mut paths = crate::shell_os_paths();
|
2024-07-12 04:43:10 +02:00
|
|
|
paths.insert(0, test_bins.into());
|
2023-09-21 20:11:56 +02:00
|
|
|
|
|
|
|
let commands = commands.as_ref().lines().collect::<Vec<_>>().join("; ");
|
|
|
|
|
|
|
|
let paths_joined = match std::env::join_paths(paths) {
|
|
|
|
Ok(all) => all,
|
|
|
|
Err(_) => panic!("Couldn't join paths for PATH var."),
|
|
|
|
};
|
|
|
|
|
2024-07-12 04:43:10 +02:00
|
|
|
let target_cwd = opts.cwd.unwrap_or_else(crate::fs::root);
|
2023-09-21 20:11:56 +02:00
|
|
|
let locale = opts.locale.unwrap_or("en_US.UTF-8".to_string());
|
|
|
|
let executable_path = crate::fs::executable_path();
|
|
|
|
|
|
|
|
let mut command = setup_command(&executable_path, &target_cwd);
|
|
|
|
command
|
|
|
|
.env(nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR, locale)
|
|
|
|
.env(NATIVE_PATH_ENV_VAR, paths_joined);
|
Move most of the peculiar argument handling for external calls into the parser (#13089)
# Description
We've had a lot of different issues and PRs related to arg handling with
externals since the rewrite of `run-external` in #12921:
- #12950
- #12955
- #13000
- #13001
- #13021
- #13027
- #13028
- #13073
Many of these are caused by the argument handling of external calls and
`run-external` being very special and involving the parser handing
quoted strings over to `run-external` so that it knows whether to expand
tildes and globs and so on. This is really unusual and also makes it
harder to use `run-external`, and also harder to understand it (and
probably is part of the reason why it was rewritten in the first place).
This PR moves a lot more of that work over to the parser, so that by the
time `run-external` gets it, it's dealing with much more normal Nushell
values. In particular:
- Unquoted strings are handled as globs with no expand
- The unescaped-but-quoted handling of strings was removed, and the
parser constructs normal looking strings instead, removing internal
quotes so that `run-external` doesn't have to do it
- Bare word interpolation is now supported and expansion is done in this
case
- Expressions typed as `Glob` containing `Expr::StringInterpolation` now
produce `Value::Glob` instead, with the quoted status from the expr
passed through so we know if it was a bare word
- Bare word interpolation for values typed as `glob` now possible, but
not implemented
- Because expansion is now triggered by `Value::Glob(_, false)` instead
of looking at the expr, externals now support glob types
# User-Facing Changes
- Bare word interpolation works for external command options, and
otherwise embedded in other strings:
```nushell
^echo --foo=(2 + 2) # prints --foo=4
^echo -foo=$"(2 + 2)" # prints -foo=4
^echo foo="(2 + 2)" # prints (no interpolation!) foo=(2 + 2)
^echo foo,(2 + 2),bar # prints foo,4,bar
```
- Bare word interpolation expands for external command head/args:
```nushell
let name = "exa"
~/.cargo/bin/($name) # this works, and expands the tilde
^$"~/.cargo/bin/($name)" # this doesn't expand the tilde
^echo ~/($name)/* # this glob is expanded
^echo $"~/($name)/*" # this isn't expanded
```
- Ndots are now supported for the head of an external command
(`^.../foo` works)
- Glob values are now supported for head/args of an external command,
and expanded appropriately:
```nushell
^("~/.cargo/bin/exa" | into glob) # the tilde is expanded
^echo ("*.txt" | into glob) # this glob is expanded
```
- `run-external` now works more like any other command, without
expecting a special call convention
for its args:
```nushell
run-external echo "'foo'"
# before PR: 'foo'
# after PR: foo
run-external echo "*.txt"
# before PR: (glob is expanded)
# after PR: *.txt
```
# Tests + Formatting
Lots of tests added and cleaned up. Some tests that weren't active on
Windows changed to use `nu --testbin cococo` so that they can work.
Added a test for Linux only to make sure tilde expansion of commands
works, because changing `HOME` there causes `~` to reliably change.
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] release notes: make sure to mention the new syntaxes that are
supported
2024-06-20 06:00:03 +02:00
|
|
|
|
|
|
|
if let Some(envs) = opts.envs {
|
|
|
|
command.envs(envs);
|
|
|
|
}
|
|
|
|
|
2024-04-10 00:27:46 +02:00
|
|
|
// Ensure that the user's config doesn't interfere with the tests
|
|
|
|
command.arg("--no-config-file");
|
2023-09-21 20:11:56 +02:00
|
|
|
if !with_std {
|
|
|
|
command.arg("--no-std-lib");
|
|
|
|
}
|
2024-06-19 06:37:24 +02:00
|
|
|
// Use plain errors to help make error text matching more consistent
|
|
|
|
command.args(["--error-style", "plain"]);
|
2023-09-21 20:11:56 +02:00
|
|
|
command
|
2024-05-04 02:53:15 +02:00
|
|
|
.arg(format!("-c {}", escape_quote_string(&commands)))
|
2023-09-21 20:11:56 +02:00
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.stderr(Stdio::piped());
|
|
|
|
|
Internal representation (IR) compiler and evaluator (#13330)
# Description
This PR adds an internal representation language to Nushell, offering an
alternative evaluator based on simple instructions, stream-containing
registers, and indexed control flow. The number of registers required is
determined statically at compile-time, and the fixed size required is
allocated upon entering the block.
Each instruction is associated with a span, which makes going backwards
from IR instructions to source code very easy.
Motivations for IR:
1. **Performance.** By simplifying the evaluation path and making it
more cache-friendly and branch predictor-friendly, code that does a lot
of computation in Nushell itself can be sped up a decent bit. Because
the IR is fairly easy to reason about, we can also implement
optimization passes in the future to eliminate and simplify code.
2. **Correctness.** The instructions mostly have very simple and
easily-specified behavior, so hopefully engine changes are a little bit
easier to reason about, and they can be specified in a more formal way
at some point. I have made an effort to document each of the
instructions in the docs for the enum itself in a reasonably specific
way. Some of the errors that would have happened during evaluation
before are now moved to the compilation step instead, because they don't
make sense to check during evaluation.
3. **As an intermediate target.** This is a good step for us to bring
the [`new-nu-parser`](https://github.com/nushell/new-nu-parser) in at
some point, as code generated from new AST can be directly compared to
code generated from old AST. If the IR code is functionally equivalent,
it will behave the exact same way.
4. **Debugging.** With a little bit more work, we can probably give
control over advancing the virtual machine that `IrBlock`s run on to
some sort of external driver, making things like breakpoints and single
stepping possible. Tools like `view ir` and [`explore
ir`](https://github.com/devyn/nu_plugin_explore_ir) make it easier than
before to see what exactly is going on with your Nushell code.
The goal is to eventually replace the AST evaluator entirely, once we're
sure it's working just as well. You can help dogfood this by running
Nushell with `$env.NU_USE_IR` set to some value. The environment
variable is checked when Nushell starts, so config runs with IR, or it
can also be set on a line at the REPL to change it dynamically. It is
also checked when running `do` in case within a script you want to just
run a specific piece of code with or without IR.
# Example
```nushell
view ir { |data|
mut sum = 0
for n in $data {
$sum += $n
}
$sum
}
```
```gas
# 3 registers, 19 instructions, 0 bytes of data
0: load-literal %0, int(0)
1: store-variable var 904, %0 # let
2: drain %0
3: drop %0
4: load-variable %1, var 903
5: iterate %0, %1, end 15 # for, label(1), from(14:)
6: store-variable var 905, %0
7: load-variable %0, var 904
8: load-variable %2, var 905
9: binary-op %0, Math(Plus), %2
10: span %0
11: store-variable var 904, %0
12: load-literal %0, nothing
13: drain %0
14: jump 5
15: drop %0 # label(0), from(5:)
16: drain %0
17: load-variable %0, var 904
18: return %0
```
# Benchmarks
All benchmarks run on a base model Mac Mini M1.
## Iterative Fibonacci sequence
This is about as best case as possible, making use of the much faster
control flow. Most code will not experience a speed improvement nearly
this large.
```nushell
def fib [n: int] {
mut a = 0
mut b = 1
for _ in 2..=$n {
let c = $a + $b
$a = $b
$b = $c
}
$b
}
use std bench
bench { 0..50 | each { |n| fib $n } }
```
IR disabled:
```
╭───────┬─────────────────╮
│ mean │ 1ms 924µs 665ns │
│ min │ 1ms 700µs 83ns │
│ max │ 3ms 450µs 125ns │
│ std │ 395µs 759ns │
│ times │ [list 50 items] │
╰───────┴─────────────────╯
```
IR enabled:
```
╭───────┬─────────────────╮
│ mean │ 452µs 820ns │
│ min │ 427µs 417ns │
│ max │ 540µs 167ns │
│ std │ 17µs 158ns │
│ times │ [list 50 items] │
╰───────┴─────────────────╯
```
![explore ir
view](https://github.com/nushell/nushell/assets/10729/d7bccc03-5222-461c-9200-0dce71b83b83)
##
[gradient_benchmark_no_check.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/gradient_benchmark_no_check.nu)
IR disabled:
```
╭───┬──────────────────╮
│ 0 │ 27ms 929µs 958ns │
│ 1 │ 21ms 153µs 459ns │
│ 2 │ 18ms 639µs 666ns │
│ 3 │ 19ms 554µs 583ns │
│ 4 │ 13ms 383µs 375ns │
│ 5 │ 11ms 328µs 208ns │
│ 6 │ 5ms 659µs 542ns │
╰───┴──────────────────╯
```
IR enabled:
```
╭───┬──────────────────╮
│ 0 │ 22ms 662µs │
│ 1 │ 17ms 221µs 792ns │
│ 2 │ 14ms 786µs 708ns │
│ 3 │ 13ms 876µs 834ns │
│ 4 │ 13ms 52µs 875ns │
│ 5 │ 11ms 269µs 666ns │
│ 6 │ 6ms 942µs 500ns │
╰───┴──────────────────╯
```
##
[random-bytes.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/random-bytes.nu)
I got pretty random results out of this benchmark so I decided not to
include it. Not clear why.
# User-Facing Changes
- IR compilation errors may appear even if the user isn't evaluating
with IR.
- IR evaluation can be enabled by setting the `NU_USE_IR` environment
variable to any value.
- New command `view ir` pretty-prints the IR for a block, and `view ir
--json` can be piped into an external tool like [`explore
ir`](https://github.com/devyn/nu_plugin_explore_ir).
# Tests + Formatting
All tests are passing with `NU_USE_IR=1`, and I've added some more eval
tests to compare the results for some very core operations. I will
probably want to add some more so we don't have to always check
`NU_USE_IR=1 toolkit test --workspace` on a regular basis.
# After Submitting
- [ ] release notes
- [ ] further documentation of instructions?
- [ ] post-release: publish `nu_plugin_explore_ir`
2024-07-11 02:33:59 +02:00
|
|
|
// Explicitly set NU_USE_IR
|
|
|
|
if let Some(use_ir) = opts.use_ir {
|
|
|
|
if use_ir {
|
|
|
|
command.env("NU_USE_IR", "1");
|
|
|
|
} else {
|
|
|
|
command.env_remove("NU_USE_IR");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-10 00:27:46 +02:00
|
|
|
// Uncomment to debug the command being run:
|
|
|
|
// println!("=== command\n{command:?}\n");
|
|
|
|
|
2023-09-21 20:11:56 +02:00
|
|
|
let process = match command.spawn() {
|
|
|
|
Ok(child) => child,
|
|
|
|
Err(why) => panic!("Can't run test {:?} {}", crate::fs::executable_path(), why),
|
|
|
|
};
|
|
|
|
|
|
|
|
let output = process
|
|
|
|
.wait_with_output()
|
|
|
|
.expect("couldn't read from stdout/stderr");
|
|
|
|
|
2024-04-26 13:23:16 +02:00
|
|
|
let out = String::from_utf8_lossy(&output.stdout);
|
2023-09-21 20:11:56 +02:00
|
|
|
let err = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
2024-04-26 13:23:16 +02:00
|
|
|
let out = if opts.collapse_output.unwrap_or(true) {
|
|
|
|
collapse_output(&out)
|
|
|
|
} else {
|
|
|
|
out.into_owned()
|
|
|
|
};
|
|
|
|
|
2023-09-21 20:11:56 +02:00
|
|
|
println!("=== stderr\n{}", err);
|
|
|
|
|
2023-11-15 23:50:43 +01:00
|
|
|
Outcome::new(out, err.into_owned(), output.status)
|
2023-09-21 20:11:56 +02:00
|
|
|
}
|
|
|
|
|
Local socket mode and foreground terminal control for plugins (#12448)
# Description
Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.
This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.
This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.
This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.
TODO:
- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
- Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
- Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
- `nu_plugin_stress_internals` added
# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
|
|
|
pub fn nu_with_plugin_run_test<E, K, V>(
|
|
|
|
cwd: impl AsRef<Path>,
|
|
|
|
envs: E,
|
|
|
|
plugins: &[&str],
|
|
|
|
command: &str,
|
|
|
|
) -> Outcome
|
|
|
|
where
|
|
|
|
E: IntoIterator<Item = (K, V)>,
|
|
|
|
K: AsRef<OsStr>,
|
|
|
|
V: AsRef<OsStr>,
|
|
|
|
{
|
2023-09-21 20:11:56 +02:00
|
|
|
let test_bins = crate::fs::binaries();
|
|
|
|
let test_bins = nu_path::canonicalize_with(&test_bins, ".").unwrap_or_else(|e| {
|
|
|
|
panic!(
|
|
|
|
"Couldn't canonicalize dummy binaries path {}: {:?}",
|
|
|
|
test_bins.display(),
|
|
|
|
e
|
|
|
|
)
|
|
|
|
});
|
|
|
|
|
|
|
|
let temp = tempdir().expect("couldn't create a temporary directory");
|
2024-04-21 14:36:26 +02:00
|
|
|
let [temp_config_file, temp_env_config_file] = ["config.nu", "env.nu"].map(|name| {
|
|
|
|
let temp_file = temp.path().join(name);
|
|
|
|
std::fs::File::create(&temp_file).expect("couldn't create temporary config file");
|
|
|
|
temp_file
|
|
|
|
});
|
|
|
|
|
2024-04-25 00:40:39 +02:00
|
|
|
// We don't have to write the plugin registry file, it's ok for it to not exist
|
2024-04-21 14:36:26 +02:00
|
|
|
let temp_plugin_file = temp.path().join("plugin.msgpackz");
|
2023-09-21 20:11:56 +02:00
|
|
|
|
|
|
|
crate::commands::ensure_plugins_built();
|
|
|
|
|
2024-04-23 13:37:50 +02:00
|
|
|
let plugin_paths_quoted: Vec<String> = plugins
|
2023-09-21 20:11:56 +02:00
|
|
|
.iter()
|
2024-04-23 13:37:50 +02:00
|
|
|
.map(|plugin_name| {
|
2023-09-21 20:11:56 +02:00
|
|
|
let plugin = with_exe(plugin_name);
|
|
|
|
let plugin_path = nu_path::canonicalize_with(&plugin, &test_bins)
|
|
|
|
.unwrap_or_else(|_| panic!("failed to canonicalize plugin {} path", &plugin));
|
|
|
|
let plugin_path = plugin_path.to_string_lossy();
|
2024-05-04 02:53:15 +02:00
|
|
|
escape_quote_string(&plugin_path)
|
2024-04-23 13:37:50 +02:00
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
let plugins_arg = format!("[{}]", plugin_paths_quoted.join(","));
|
2023-09-21 20:11:56 +02:00
|
|
|
|
|
|
|
let target_cwd = crate::fs::in_directory(&cwd);
|
|
|
|
// In plugin testing, we need to use installed nushell to drive
|
|
|
|
// plugin commands.
|
|
|
|
let mut executable_path = crate::fs::executable_path();
|
|
|
|
if !executable_path.exists() {
|
|
|
|
executable_path = crate::fs::installed_nu_path();
|
|
|
|
}
|
Internal representation (IR) compiler and evaluator (#13330)
# Description
This PR adds an internal representation language to Nushell, offering an
alternative evaluator based on simple instructions, stream-containing
registers, and indexed control flow. The number of registers required is
determined statically at compile-time, and the fixed size required is
allocated upon entering the block.
Each instruction is associated with a span, which makes going backwards
from IR instructions to source code very easy.
Motivations for IR:
1. **Performance.** By simplifying the evaluation path and making it
more cache-friendly and branch predictor-friendly, code that does a lot
of computation in Nushell itself can be sped up a decent bit. Because
the IR is fairly easy to reason about, we can also implement
optimization passes in the future to eliminate and simplify code.
2. **Correctness.** The instructions mostly have very simple and
easily-specified behavior, so hopefully engine changes are a little bit
easier to reason about, and they can be specified in a more formal way
at some point. I have made an effort to document each of the
instructions in the docs for the enum itself in a reasonably specific
way. Some of the errors that would have happened during evaluation
before are now moved to the compilation step instead, because they don't
make sense to check during evaluation.
3. **As an intermediate target.** This is a good step for us to bring
the [`new-nu-parser`](https://github.com/nushell/new-nu-parser) in at
some point, as code generated from new AST can be directly compared to
code generated from old AST. If the IR code is functionally equivalent,
it will behave the exact same way.
4. **Debugging.** With a little bit more work, we can probably give
control over advancing the virtual machine that `IrBlock`s run on to
some sort of external driver, making things like breakpoints and single
stepping possible. Tools like `view ir` and [`explore
ir`](https://github.com/devyn/nu_plugin_explore_ir) make it easier than
before to see what exactly is going on with your Nushell code.
The goal is to eventually replace the AST evaluator entirely, once we're
sure it's working just as well. You can help dogfood this by running
Nushell with `$env.NU_USE_IR` set to some value. The environment
variable is checked when Nushell starts, so config runs with IR, or it
can also be set on a line at the REPL to change it dynamically. It is
also checked when running `do` in case within a script you want to just
run a specific piece of code with or without IR.
# Example
```nushell
view ir { |data|
mut sum = 0
for n in $data {
$sum += $n
}
$sum
}
```
```gas
# 3 registers, 19 instructions, 0 bytes of data
0: load-literal %0, int(0)
1: store-variable var 904, %0 # let
2: drain %0
3: drop %0
4: load-variable %1, var 903
5: iterate %0, %1, end 15 # for, label(1), from(14:)
6: store-variable var 905, %0
7: load-variable %0, var 904
8: load-variable %2, var 905
9: binary-op %0, Math(Plus), %2
10: span %0
11: store-variable var 904, %0
12: load-literal %0, nothing
13: drain %0
14: jump 5
15: drop %0 # label(0), from(5:)
16: drain %0
17: load-variable %0, var 904
18: return %0
```
# Benchmarks
All benchmarks run on a base model Mac Mini M1.
## Iterative Fibonacci sequence
This is about as best case as possible, making use of the much faster
control flow. Most code will not experience a speed improvement nearly
this large.
```nushell
def fib [n: int] {
mut a = 0
mut b = 1
for _ in 2..=$n {
let c = $a + $b
$a = $b
$b = $c
}
$b
}
use std bench
bench { 0..50 | each { |n| fib $n } }
```
IR disabled:
```
╭───────┬─────────────────╮
│ mean │ 1ms 924µs 665ns │
│ min │ 1ms 700µs 83ns │
│ max │ 3ms 450µs 125ns │
│ std │ 395µs 759ns │
│ times │ [list 50 items] │
╰───────┴─────────────────╯
```
IR enabled:
```
╭───────┬─────────────────╮
│ mean │ 452µs 820ns │
│ min │ 427µs 417ns │
│ max │ 540µs 167ns │
│ std │ 17µs 158ns │
│ times │ [list 50 items] │
╰───────┴─────────────────╯
```
![explore ir
view](https://github.com/nushell/nushell/assets/10729/d7bccc03-5222-461c-9200-0dce71b83b83)
##
[gradient_benchmark_no_check.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/gradient_benchmark_no_check.nu)
IR disabled:
```
╭───┬──────────────────╮
│ 0 │ 27ms 929µs 958ns │
│ 1 │ 21ms 153µs 459ns │
│ 2 │ 18ms 639µs 666ns │
│ 3 │ 19ms 554µs 583ns │
│ 4 │ 13ms 383µs 375ns │
│ 5 │ 11ms 328µs 208ns │
│ 6 │ 5ms 659µs 542ns │
╰───┴──────────────────╯
```
IR enabled:
```
╭───┬──────────────────╮
│ 0 │ 22ms 662µs │
│ 1 │ 17ms 221µs 792ns │
│ 2 │ 14ms 786µs 708ns │
│ 3 │ 13ms 876µs 834ns │
│ 4 │ 13ms 52µs 875ns │
│ 5 │ 11ms 269µs 666ns │
│ 6 │ 6ms 942µs 500ns │
╰───┴──────────────────╯
```
##
[random-bytes.nu](https://github.com/nushell/nu_scripts/blob/main/benchmarks/random-bytes.nu)
I got pretty random results out of this benchmark so I decided not to
include it. Not clear why.
# User-Facing Changes
- IR compilation errors may appear even if the user isn't evaluating
with IR.
- IR evaluation can be enabled by setting the `NU_USE_IR` environment
variable to any value.
- New command `view ir` pretty-prints the IR for a block, and `view ir
--json` can be piped into an external tool like [`explore
ir`](https://github.com/devyn/nu_plugin_explore_ir).
# Tests + Formatting
All tests are passing with `NU_USE_IR=1`, and I've added some more eval
tests to compare the results for some very core operations. I will
probably want to add some more so we don't have to always check
`NU_USE_IR=1 toolkit test --workspace` on a regular basis.
# After Submitting
- [ ] release notes
- [ ] further documentation of instructions?
- [ ] post-release: publish `nu_plugin_explore_ir`
2024-07-11 02:33:59 +02:00
|
|
|
|
2023-09-21 20:11:56 +02:00
|
|
|
let process = match setup_command(&executable_path, &target_cwd)
|
Local socket mode and foreground terminal control for plugins (#12448)
# Description
Adds support for running plugins using local socket communication
instead of stdio. This will be an optional thing that not all plugins
have to support.
This frees up stdio for use to make plugins that use stdio to create
terminal UIs, cc @amtoine, @fdncred.
This uses the [`interprocess`](https://crates.io/crates/interprocess)
crate (298 stars, MIT license, actively maintained), which seems to be
the best option for cross-platform local socket support in Rust. On
Windows, a local socket name is provided. On Unixes, it's a path. The
socket name is kept to a relatively small size because some operating
systems have pretty strict limits on the whole path (~100 chars), so on
macOS for example we prefer `/tmp/nu.{pid}.{hash64}.sock` where the hash
includes the plugin filename and timestamp to be unique enough.
This also adds an API for moving plugins in and out of the foreground
group, which is relevant for Unixes where direct terminal control
depends on that.
TODO:
- [x] Generate local socket path according to OS conventions
- [x] Add support for passing `--local-socket` to the plugin executable
instead of `--stdio`, and communicating over that instead
- [x] Test plugins that were broken, including
[amtoine/nu_plugin_explore](https://github.com/amtoine/nu_plugin_explore)
- [x] Automatically upgrade to using local sockets when supported,
falling back if it doesn't work, transparently to the user without any
visible error messages
- Added protocol feature: `LocalSocket`
- [x] Reset preferred mode to `None` on `register`
- [x] Allow plugins to detect whether they're running on a local socket
and can use stdio freely, so that TUI plugins can just produce an error
message otherwise
- Implemented via `EngineInterface::is_using_stdio()`
- [x] Clean up foreground state when plugin command exits on the engine
side too, not just whole plugin
- [x] Make sure tests for failure cases work as intended
- `nu_plugin_stress_internals` added
# User-Facing Changes
- TUI plugins work
- Non-Rust plugins could optionally choose to use this
- This might behave differently, so will need to test it carefully
across different operating systems
# Tests + Formatting
- :green_circle: `toolkit fmt`
- :green_circle: `toolkit clippy`
- :green_circle: `toolkit test`
- :green_circle: `toolkit test stdlib`
# After Submitting
- [ ] Document local socket option in plugin contrib docs
- [ ] Document how to do a terminal UI plugin in plugin contrib docs
- [ ] Document: `EnterForeground` engine call
- [ ] Document: `LeaveForeground` engine call
- [ ] Document: `LocalSocket` protocol feature
2024-04-15 20:28:18 +02:00
|
|
|
.envs(envs)
|
2023-09-21 20:11:56 +02:00
|
|
|
.arg("--commands")
|
2024-04-23 13:37:50 +02:00
|
|
|
.arg(command)
|
2024-06-19 06:37:24 +02:00
|
|
|
// Use plain errors to help make error text matching more consistent
|
|
|
|
.args(["--error-style", "plain"])
|
2024-04-10 00:27:46 +02:00
|
|
|
.arg("--config")
|
|
|
|
.arg(temp_config_file)
|
|
|
|
.arg("--env-config")
|
|
|
|
.arg(temp_env_config_file)
|
2023-09-21 20:11:56 +02:00
|
|
|
.arg("--plugin-config")
|
|
|
|
.arg(temp_plugin_file)
|
2024-04-23 13:37:50 +02:00
|
|
|
.arg("--plugins")
|
|
|
|
.arg(plugins_arg)
|
2023-09-21 20:11:56 +02:00
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.stderr(Stdio::piped())
|
|
|
|
.spawn()
|
|
|
|
{
|
|
|
|
Ok(child) => child,
|
|
|
|
Err(why) => panic!("Can't run test {}", why),
|
|
|
|
};
|
|
|
|
|
|
|
|
let output = process
|
|
|
|
.wait_with_output()
|
|
|
|
.expect("couldn't read from stdout/stderr");
|
|
|
|
|
2024-04-26 13:23:16 +02:00
|
|
|
let out = collapse_output(&String::from_utf8_lossy(&output.stdout));
|
2023-09-21 20:11:56 +02:00
|
|
|
let err = String::from_utf8_lossy(&output.stderr);
|
|
|
|
|
|
|
|
println!("=== stderr\n{}", err);
|
|
|
|
|
2023-11-15 23:50:43 +01:00
|
|
|
Outcome::new(out, err.into_owned(), output.status)
|
2023-09-21 20:11:56 +02:00
|
|
|
}
|
|
|
|
|
2024-05-04 02:53:15 +02:00
|
|
|
fn escape_quote_string(input: &str) -> String {
|
2023-09-21 20:11:56 +02:00
|
|
|
let mut output = String::with_capacity(input.len() + 2);
|
|
|
|
output.push('"');
|
|
|
|
|
|
|
|
for c in input.chars() {
|
|
|
|
if c == '"' || c == '\\' {
|
|
|
|
output.push('\\');
|
2023-02-13 13:42:08 +01:00
|
|
|
}
|
2023-09-21 20:11:56 +02:00
|
|
|
output.push(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
output.push('"');
|
|
|
|
output
|
2019-12-15 17:15:06 +01:00
|
|
|
}
|
|
|
|
|
2023-09-21 20:11:56 +02:00
|
|
|
fn with_exe(name: &str) -> String {
|
|
|
|
#[cfg(windows)]
|
|
|
|
{
|
|
|
|
name.to_string() + ".exe"
|
|
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
|
|
{
|
|
|
|
name.to_string()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-26 13:23:16 +02:00
|
|
|
fn collapse_output(out: &str) -> String {
|
2021-03-27 06:08:03 +01:00
|
|
|
let out = out.lines().collect::<Vec<_>>().join("\n");
|
Restructure and streamline token expansion (#1123)
Restructure and streamline token expansion
The purpose of this commit is to streamline the token expansion code, by
removing aspects of the code that are no longer relevant, removing
pointless duplication, and eliminating the need to pass the same
arguments to `expand_syntax`.
The first big-picture change in this commit is that instead of a handful
of `expand_` functions, which take a TokensIterator and ExpandContext, a
smaller number of methods on the `TokensIterator` do the same job.
The second big-picture change in this commit is fully eliminating the
coloring traits, making coloring a responsibility of the base expansion
implementations. This also means that the coloring tracer is merged into
the expansion tracer, so you can follow a single expansion and see how
the expansion process produced colored tokens.
One side effect of this change is that the expander itself is marginally
more error-correcting. The error correction works by switching from
structured expansion to `BackoffColoringMode` when an unexpected token
is found, which guarantees that all spans of the source are colored, but
may not be the most optimal error recovery strategy.
That said, because `BackoffColoringMode` only extends as far as a
closing delimiter (`)`, `]`, `}`) or pipe (`|`), it does result in
fairly granular correction strategy.
The current code still produces an `Err` (plus a complete list of
colored shapes) from the parsing process if any errors are encountered,
but this could easily be addressed now that the underlying expansion is
error-correcting.
This commit also colors any spans that are syntax errors in red, and
causes the parser to include some additional information about what
tokens were expected at any given point where an error was encountered,
so that completions and hinting could be more robust in the future.
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-01-21 23:45:03 +01:00
|
|
|
let out = out.replace("\r\n", "");
|
2022-02-24 20:02:28 +01:00
|
|
|
out.replace('\n', "")
|
Restructure and streamline token expansion (#1123)
Restructure and streamline token expansion
The purpose of this commit is to streamline the token expansion code, by
removing aspects of the code that are no longer relevant, removing
pointless duplication, and eliminating the need to pass the same
arguments to `expand_syntax`.
The first big-picture change in this commit is that instead of a handful
of `expand_` functions, which take a TokensIterator and ExpandContext, a
smaller number of methods on the `TokensIterator` do the same job.
The second big-picture change in this commit is fully eliminating the
coloring traits, making coloring a responsibility of the base expansion
implementations. This also means that the coloring tracer is merged into
the expansion tracer, so you can follow a single expansion and see how
the expansion process produced colored tokens.
One side effect of this change is that the expander itself is marginally
more error-correcting. The error correction works by switching from
structured expansion to `BackoffColoringMode` when an unexpected token
is found, which guarantees that all spans of the source are colored, but
may not be the most optimal error recovery strategy.
That said, because `BackoffColoringMode` only extends as far as a
closing delimiter (`)`, `]`, `}`) or pipe (`|`), it does result in
fairly granular correction strategy.
The current code still produces an `Err` (plus a complete list of
colored shapes) from the parsing process if any errors are encountered,
but this could easily be addressed now that the underlying expansion is
error-correcting.
This commit also colors any spans that are syntax errors in red, and
causes the parser to include some additional information about what
tokens were expected at any given point where an error was encountered,
so that completions and hinting could be more robust in the future.
Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
Co-authored-by: Andrés N. Robalino <andres@androbtech.com>
2020-01-21 23:45:03 +01:00
|
|
|
}
|
2023-08-14 12:49:55 +02:00
|
|
|
|
2024-07-12 04:43:10 +02:00
|
|
|
fn setup_command(executable_path: &AbsolutePath, target_cwd: &AbsolutePath) -> Command {
|
2023-08-14 12:49:55 +02:00
|
|
|
let mut command = Command::new(executable_path);
|
|
|
|
|
|
|
|
command
|
|
|
|
.current_dir(target_cwd)
|
|
|
|
.env_remove("FILE_PWD")
|
|
|
|
.env("PWD", target_cwd); // setting PWD is enough to set cwd;
|
|
|
|
|
|
|
|
command
|
|
|
|
}
|