LazyRecord (#7619)
This is an attempt to implement a new `Value::LazyRecord` variant for
performance reasons.
`LazyRecord` is like a regular `Record`, but it's possible to access
individual columns without evaluating other columns. I've implemented
`LazyRecord` for the special `$nu` variable; accessing `$nu` is
relatively slow because of all the information in `scope`, and [`$nu`
accounts for about 2/3 of Nu's startup time on
Linux](https://github.com/nushell/nushell/issues/6677#issuecomment-1364618122).
### Benchmarks
I ran some benchmarks on my desktop (Linux, 12900K) and the results are
very pleasing.
Nu's time to start up and run a command (`cargo build --release;
hyperfine 'target/release/nu -c "echo \"Hello, world!\""' --shell=none
--warmup 10`) goes from **8.8ms to 3.2ms, about 2.8x faster**.
Tests are also much faster! Running `cargo nextest` (with our very slow
`proptest` tests disabled) goes from **7.2s to 4.4s (1.6x faster)**,
because most tests involve launching a new instance of Nu.
### Design (updated)
I've added a new `LazyRecord` trait and added a `Value` variant wrapping
those trait objects, much like `CustomValue`. `LazyRecord`
implementations must implement these 2 functions:
```rust
// All column names
fn column_names(&self) -> Vec<&'static str>;
// Get 1 specific column value
fn get_column_value(&self, column: &str) -> Result<Value, ShellError>;
```
### Serializability
`Value` variants must implement `Serializable` and `Deserializable`, which poses some problems because I want to use unserializable things like `EngineState` in `LazyRecord`s. To work around this, I basically lie to the type system:
1. Add `#[typetag::serde(tag = "type")]` to `LazyRecord` to make it serializable
2. Any unserializable fields in `LazyRecord` implementations get marked with `#[serde(skip)]`
3. At the point where a `LazyRecord` normally would get serialized and sent to a plugin, I instead collect it into a regular `Value::Record` (which can be serialized)
2023-01-19 04:27:26 +01:00
|
|
|
use crate::{current_dir_str, get_full_help, nu_variable::NuVariable};
|
2022-01-16 14:55:56 +01:00
|
|
|
use nu_path::expand_path_with;
|
2021-10-25 06:01:02 +02:00
|
|
|
use nu_protocol::{
|
2022-11-11 08:16:07 +01:00
|
|
|
ast::{
|
2022-11-22 19:26:13 +01:00
|
|
|
Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math,
|
|
|
|
Operator, PathMember, PipelineElement, Redirection,
|
2022-11-11 08:16:07 +01:00
|
|
|
},
|
2023-02-24 23:29:07 +01:00
|
|
|
engine::{EngineState, ProfilingConfig, Stack},
|
2023-03-16 23:53:46 +01:00
|
|
|
DataSource, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, PipelineMetadata,
|
|
|
|
Range, ShellError, Span, Spanned, Unit, Value, VarId, ENV_VARIABLE_ID,
|
2021-10-25 06:01:02 +02:00
|
|
|
};
|
revert: move to ahash (#9464)
This PR reverts https://github.com/nushell/nushell/pull/9391
We try not to revert PRs like this, though after discussion with the
Nushell team, we decided to revert this one.
The main reason is that Nushell, as a codebase, isn't ready for these
kinds of optimisations. It's in the part of the development cycle where
our main focus should be on improving the algorithms inside of Nushell
itself. Once we have matured our algorithms, then we can look for
opportunities to switch out technologies we're using for alternate
forms.
Much of Nushell still has lots of opportunities for tuning the codebase,
paying down technical debt, and making the codebase generally cleaner
and more robust. This should be the focus. Performance improvements
should flow out of that work.
Said another, optimisation that isn't part of tuning the codebase is
premature at this stage. We need to focus on doing the hard work of
making the engine, parser, etc better.
# User-Facing Changes
Reverts the HashMap -> ahash change.
cc @FilipAndersson245
2023-06-18 05:27:57 +02:00
|
|
|
use std::collections::HashMap;
|
2023-02-11 22:35:48 +01:00
|
|
|
use std::time::Instant;
|
2021-10-13 19:53:27 +02:00
|
|
|
|
2021-08-16 00:33:34 +02:00
|
|
|
pub fn eval_operator(op: &Expression) -> Result<Operator, ShellError> {
|
2021-07-23 07:14:49 +02:00
|
|
|
match op {
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Operator(operator),
|
|
|
|
..
|
|
|
|
} => Ok(operator.clone()),
|
2023-03-06 11:31:07 +01:00
|
|
|
Expression { span, expr, .. } => Err(ShellError::UnknownOperator {
|
|
|
|
op_token: format!("{expr:?}"),
|
|
|
|
span: *span,
|
|
|
|
}),
|
2021-07-16 03:10:22 +02:00
|
|
|
}
|
2021-07-23 07:14:49 +02:00
|
|
|
}
|
2021-07-16 03:10:22 +02:00
|
|
|
|
2022-03-10 08:49:02 +01:00
|
|
|
pub fn eval_call(
|
2021-10-25 08:31:39 +02:00
|
|
|
engine_state: &EngineState,
|
2021-12-25 20:39:42 +01:00
|
|
|
caller_stack: &mut Stack,
|
2021-10-25 06:01:02 +02:00
|
|
|
call: &Call,
|
|
|
|
input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-12-15 18:39:24 +01:00
|
|
|
if nu_utils::ctrl_c::was_pressed(&engine_state.ctrlc) {
|
|
|
|
return Ok(Value::Nothing { span: call.head }.into_pipeline_data());
|
2022-05-02 10:18:25 +02:00
|
|
|
}
|
2021-12-06 05:09:49 +01:00
|
|
|
let decl = engine_state.get_decl(call.decl_id);
|
2021-11-28 20:35:02 +01:00
|
|
|
|
2022-04-09 07:17:48 +02:00
|
|
|
if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
|
2022-02-10 03:24:29 +01:00
|
|
|
let mut signature = decl.signature();
|
|
|
|
signature.usage = decl.usage().to_string();
|
|
|
|
signature.extra_usage = decl.extra_usage().to_string();
|
|
|
|
|
2022-11-20 14:22:42 +01:00
|
|
|
let full_help = get_full_help(
|
|
|
|
&signature,
|
|
|
|
&decl.examples(),
|
|
|
|
engine_state,
|
|
|
|
caller_stack,
|
|
|
|
decl.is_parser_keyword(),
|
|
|
|
);
|
2021-10-13 19:53:27 +02:00
|
|
|
Ok(Value::String {
|
|
|
|
val: full_help,
|
|
|
|
span: call.head,
|
2021-10-25 06:01:02 +02:00
|
|
|
}
|
|
|
|
.into_pipeline_data())
|
2021-10-13 19:53:27 +02:00
|
|
|
} else if let Some(block_id) = decl.get_block_id() {
|
2021-10-25 22:04:23 +02:00
|
|
|
let block = engine_state.get_block(block_id);
|
|
|
|
|
2022-01-12 05:06:56 +01:00
|
|
|
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
2021-12-20 07:58:09 +01:00
|
|
|
|
|
|
|
for (param_idx, param) in decl
|
|
|
|
.signature()
|
|
|
|
.required_positional
|
|
|
|
.iter()
|
|
|
|
.chain(decl.signature().optional_positional.iter())
|
|
|
|
.enumerate()
|
|
|
|
{
|
2021-07-23 23:19:30 +02:00
|
|
|
let var_id = param
|
|
|
|
.var_id
|
|
|
|
.expect("internal error: all custom parameters must have var_ids");
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
if let Some(arg) = call.positional_nth(param_idx) {
|
2021-12-25 20:39:42 +01:00
|
|
|
let result = eval_expression(engine_state, caller_stack, arg)?;
|
|
|
|
callee_stack.add_var(var_id, result);
|
2023-04-26 16:14:02 +02:00
|
|
|
} else if let Some(value) = ¶m.default_value {
|
|
|
|
callee_stack.add_var(var_id, value.to_owned());
|
2021-12-20 07:58:09 +01:00
|
|
|
} else {
|
2021-12-25 20:39:42 +01:00
|
|
|
callee_stack.add_var(var_id, Value::nothing(call.head));
|
2021-12-20 07:58:09 +01:00
|
|
|
}
|
2021-07-23 23:19:30 +02:00
|
|
|
}
|
2021-09-07 05:37:02 +02:00
|
|
|
|
|
|
|
if let Some(rest_positional) = decl.signature().rest_positional {
|
|
|
|
let mut rest_items = vec![];
|
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
for arg in call.positional_iter().skip(
|
2021-09-07 05:37:02 +02:00
|
|
|
decl.signature().required_positional.len()
|
|
|
|
+ decl.signature().optional_positional.len(),
|
|
|
|
) {
|
2021-12-25 20:39:42 +01:00
|
|
|
let result = eval_expression(engine_state, caller_stack, arg)?;
|
2021-09-07 05:37:02 +02:00
|
|
|
rest_items.push(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
let span = if let Some(rest_item) = rest_items.first() {
|
2021-10-11 20:45:31 +02:00
|
|
|
rest_item.span()?
|
2021-09-07 05:37:02 +02:00
|
|
|
} else {
|
2021-12-19 08:46:13 +01:00
|
|
|
call.head
|
2021-09-07 05:37:02 +02:00
|
|
|
};
|
|
|
|
|
2021-12-25 20:39:42 +01:00
|
|
|
callee_stack.add_var(
|
2021-09-07 05:37:02 +02:00
|
|
|
rest_positional
|
|
|
|
.var_id
|
|
|
|
.expect("Internal error: rest positional parameter lacks var_id"),
|
|
|
|
Value::List {
|
2021-09-07 09:07:11 +02:00
|
|
|
vals: rest_items,
|
2021-09-07 05:37:02 +02:00
|
|
|
span,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2021-10-11 23:17:45 +02:00
|
|
|
|
|
|
|
for named in decl.signature().named {
|
2021-10-13 19:53:27 +02:00
|
|
|
if let Some(var_id) = named.var_id {
|
|
|
|
let mut found = false;
|
2022-04-09 04:55:02 +02:00
|
|
|
for call_named in call.named_iter() {
|
2023-04-26 15:16:32 +02:00
|
|
|
if let (Some(spanned), Some(short)) = (&call_named.1, named.short) {
|
|
|
|
if spanned.item == short.to_string() {
|
|
|
|
if let Some(arg) = &call_named.2 {
|
|
|
|
let result = eval_expression(engine_state, caller_stack, arg)?;
|
|
|
|
|
|
|
|
callee_stack.add_var(var_id, result);
|
2023-05-03 23:09:36 +02:00
|
|
|
} else if let Some(value) = &named.default_value {
|
|
|
|
callee_stack.add_var(var_id, value.to_owned());
|
2023-04-26 15:16:32 +02:00
|
|
|
} else {
|
|
|
|
callee_stack.add_var(var_id, Value::boolean(true, call.head))
|
|
|
|
}
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
} else if call_named.0.item == named.long {
|
2022-04-09 07:17:48 +02:00
|
|
|
if let Some(arg) = &call_named.2 {
|
2021-12-25 20:39:42 +01:00
|
|
|
let result = eval_expression(engine_state, caller_stack, arg)?;
|
2021-10-12 06:49:17 +02:00
|
|
|
|
2022-03-07 21:08:56 +01:00
|
|
|
callee_stack.add_var(var_id, result);
|
2023-05-03 23:09:36 +02:00
|
|
|
} else if let Some(value) = &named.default_value {
|
|
|
|
callee_stack.add_var(var_id, value.to_owned());
|
2021-10-13 19:53:27 +02:00
|
|
|
} else {
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
callee_stack.add_var(var_id, Value::boolean(true, call.head))
|
2021-10-13 19:53:27 +02:00
|
|
|
}
|
|
|
|
found = true;
|
2021-10-11 23:17:45 +02:00
|
|
|
}
|
|
|
|
}
|
2021-10-12 06:49:17 +02:00
|
|
|
|
2022-01-06 21:32:47 +01:00
|
|
|
if !found {
|
|
|
|
if named.arg.is_none() {
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
callee_stack.add_var(var_id, Value::boolean(false, call.head))
|
2023-05-03 23:09:36 +02:00
|
|
|
} else if let Some(value) = named.default_value {
|
|
|
|
callee_stack.add_var(var_id, value);
|
2022-01-06 21:32:47 +01:00
|
|
|
} else {
|
|
|
|
callee_stack.add_var(var_id, Value::Nothing { span: call.head })
|
|
|
|
}
|
2021-10-13 19:53:27 +02:00
|
|
|
}
|
2021-10-12 06:49:17 +02:00
|
|
|
}
|
2021-10-11 23:17:45 +02:00
|
|
|
}
|
2022-02-04 19:02:03 +01:00
|
|
|
|
New commands: `break`, `continue`, `return`, and `loop` (#7230)
# Description
This adds `break`, `continue`, `return`, and `loop`.
* `break` - breaks out a loop
* `continue` - continues a loop at the next iteration
* `return` - early return from a function call
* `loop` - loop forever (until the loop hits a break)
Examples:
```
for i in 1..10 {
if $i == 5 {
continue
}
print $i
}
```
```
for i in 1..10 {
if $i == 5 {
break
}
print $i
}
```
```
def foo [x] {
if true {
return 2
}
$x
}
foo 100
```
```
loop { print "hello, forever" }
```
```
[1, 2, 3, 4, 5] | each {|x|
if $x > 3 { break }
$x
}
```
# User-Facing Changes
Adds the above commands.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-24 21:39:16 +01:00
|
|
|
let result = eval_block_with_early_return(
|
2022-03-02 13:52:24 +01:00
|
|
|
engine_state,
|
|
|
|
&mut callee_stack,
|
|
|
|
block,
|
|
|
|
input,
|
|
|
|
call.redirect_stdout,
|
|
|
|
call.redirect_stderr,
|
|
|
|
);
|
2022-02-04 19:02:03 +01:00
|
|
|
|
2022-01-29 21:45:46 +01:00
|
|
|
if block.redirect_env {
|
2022-08-18 22:24:39 +02:00
|
|
|
redirect_env(engine_state, caller_stack, &callee_stack);
|
2022-01-29 21:45:46 +01:00
|
|
|
}
|
2022-02-04 19:02:03 +01:00
|
|
|
|
2022-01-29 21:45:46 +01:00
|
|
|
result
|
2021-07-30 00:56:51 +02:00
|
|
|
} else {
|
2021-12-25 20:39:42 +01:00
|
|
|
// We pass caller_stack here with the knowledge that internal commands
|
|
|
|
// are going to be specifically looking for global state in the stack
|
|
|
|
// rather than any local state.
|
|
|
|
decl.run(engine_state, caller_stack, call, input)
|
2021-07-16 03:10:22 +02:00
|
|
|
}
|
2021-07-23 07:14:49 +02:00
|
|
|
}
|
2021-07-16 03:10:22 +02:00
|
|
|
|
2022-08-18 22:24:39 +02:00
|
|
|
/// Redirect the environment from callee to the caller.
|
|
|
|
pub fn redirect_env(engine_state: &EngineState, caller_stack: &mut Stack, callee_stack: &Stack) {
|
2022-08-31 22:32:56 +02:00
|
|
|
// Grab all environment variables from the callee
|
2022-08-18 22:24:39 +02:00
|
|
|
let caller_env_vars = caller_stack.get_env_var_names(engine_state);
|
|
|
|
|
|
|
|
// remove env vars that are present in the caller but not in the callee
|
|
|
|
// (the callee hid them)
|
|
|
|
for var in caller_env_vars.iter() {
|
|
|
|
if !callee_stack.has_env_var(engine_state, var) {
|
|
|
|
caller_stack.remove_env_var(engine_state, var);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add new env vars from callee to caller
|
|
|
|
for (var, value) in callee_stack.get_stack_env_vars() {
|
|
|
|
caller_stack.add_env_var(var, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
enum RedirectTarget {
|
|
|
|
Piped(bool, bool),
|
|
|
|
CombinedPipe,
|
|
|
|
}
|
|
|
|
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
#[allow(clippy::too_many_arguments)]
|
2021-09-19 23:48:33 +02:00
|
|
|
fn eval_external(
|
2021-10-25 08:31:39 +02:00
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
2022-01-13 09:17:45 +01:00
|
|
|
head: &Expression,
|
2021-10-08 23:51:47 +02:00
|
|
|
args: &[Expression],
|
2021-10-25 06:01:02 +02:00
|
|
|
input: PipelineData,
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
redirect_target: RedirectTarget,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
is_subexpression: bool,
|
2022-09-30 14:13:46 +02:00
|
|
|
) -> Result<PipelineData, ShellError> {
|
2021-10-25 08:31:39 +02:00
|
|
|
let decl_id = engine_state
|
2022-05-07 21:39:22 +02:00
|
|
|
.find_decl("run-external".as_bytes(), &[])
|
2023-03-06 18:33:09 +01:00
|
|
|
.ok_or(ShellError::ExternalNotSupported { span: head.span })?;
|
2021-09-19 23:48:33 +02:00
|
|
|
|
2021-10-25 08:31:39 +02:00
|
|
|
let command = engine_state.get_decl(decl_id);
|
2021-09-19 23:48:33 +02:00
|
|
|
|
2022-02-04 03:01:45 +01:00
|
|
|
let mut call = Call::new(head.span);
|
2021-10-08 23:51:47 +02:00
|
|
|
|
2022-04-09 04:55:02 +02:00
|
|
|
call.add_positional(head.clone());
|
2021-10-08 23:51:47 +02:00
|
|
|
|
|
|
|
for arg in args {
|
2022-04-09 04:55:02 +02:00
|
|
|
call.add_positional(arg.clone())
|
2021-10-08 23:51:47 +02:00
|
|
|
}
|
2021-09-19 23:48:33 +02:00
|
|
|
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
match redirect_target {
|
|
|
|
RedirectTarget::Piped(redirect_stdout, redirect_stderr) => {
|
|
|
|
if redirect_stdout {
|
|
|
|
call.add_named((
|
|
|
|
Spanned {
|
|
|
|
item: "redirect-stdout".into(),
|
|
|
|
span: head.span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
))
|
|
|
|
}
|
2022-02-21 23:22:21 +01:00
|
|
|
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
if redirect_stderr {
|
|
|
|
call.add_named((
|
|
|
|
Spanned {
|
|
|
|
item: "redirect-stderr".into(),
|
|
|
|
span: head.span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
RedirectTarget::CombinedPipe => call.add_named((
|
2022-02-21 23:22:21 +01:00
|
|
|
Spanned {
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
item: "redirect-combine".into(),
|
2022-01-13 09:17:45 +01:00
|
|
|
span: head.span,
|
2021-10-11 23:17:45 +02:00
|
|
|
},
|
|
|
|
None,
|
2022-04-09 07:17:48 +02:00
|
|
|
None,
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
)),
|
2021-09-23 18:42:03 +02:00
|
|
|
}
|
|
|
|
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
if is_subexpression {
|
|
|
|
call.add_named((
|
|
|
|
Spanned {
|
|
|
|
item: "trim-end-newline".into(),
|
|
|
|
span: head.span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
|
2022-09-30 14:13:46 +02:00
|
|
|
command.run(engine_state, stack, &call, input)
|
2021-09-19 23:48:33 +02:00
|
|
|
}
|
|
|
|
|
2021-09-03 00:58:15 +02:00
|
|
|
pub fn eval_expression(
|
2021-10-25 08:31:39 +02:00
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
2021-09-03 00:58:15 +02:00
|
|
|
expr: &Expression,
|
|
|
|
) -> Result<Value, ShellError> {
|
2021-07-23 07:14:49 +02:00
|
|
|
match &expr.expr {
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Expr::Bool(b) => Ok(Value::boolean(*b, expr.span)),
|
|
|
|
Expr::Int(i) => Ok(Value::int(*i, expr.span)),
|
|
|
|
Expr::Float(f) => Ok(Value::float(*f, expr.span)),
|
2022-03-01 00:31:53 +01:00
|
|
|
Expr::Binary(b) => Ok(Value::Binary {
|
|
|
|
val: b.clone(),
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
2021-10-25 08:31:39 +02:00
|
|
|
Expr::ValueWithUnit(e, unit) => match eval_expression(engine_state, stack, e)? {
|
2021-10-05 04:27:39 +02:00
|
|
|
Value::Int { val, .. } => Ok(compute(val, unit.item, unit.span)),
|
2023-03-06 18:33:09 +01:00
|
|
|
x => Err(ShellError::CantConvert {
|
|
|
|
to_type: "unit value".into(),
|
|
|
|
from_type: x.get_type().to_string(),
|
|
|
|
span: e.span,
|
|
|
|
help: None,
|
|
|
|
}),
|
2021-10-05 04:27:39 +02:00
|
|
|
},
|
2021-09-11 13:13:04 +02:00
|
|
|
Expr::Range(from, next, to, operator) => {
|
2021-09-04 23:52:57 +02:00
|
|
|
let from = if let Some(f) = from {
|
2021-10-25 08:31:39 +02:00
|
|
|
eval_expression(engine_state, stack, f)?
|
2021-09-04 23:52:57 +02:00
|
|
|
} else {
|
2021-12-19 08:46:13 +01:00
|
|
|
Value::Nothing { span: expr.span }
|
2021-09-04 23:52:57 +02:00
|
|
|
};
|
|
|
|
|
2021-09-11 13:13:04 +02:00
|
|
|
let next = if let Some(s) = next {
|
2021-10-25 08:31:39 +02:00
|
|
|
eval_expression(engine_state, stack, s)?
|
2021-09-04 23:52:57 +02:00
|
|
|
} else {
|
2021-12-19 08:46:13 +01:00
|
|
|
Value::Nothing { span: expr.span }
|
2021-09-04 23:52:57 +02:00
|
|
|
};
|
|
|
|
|
2021-09-11 13:13:04 +02:00
|
|
|
let to = if let Some(t) = to {
|
2021-10-25 08:31:39 +02:00
|
|
|
eval_expression(engine_state, stack, t)?
|
2021-09-11 13:13:04 +02:00
|
|
|
} else {
|
2021-12-19 08:46:13 +01:00
|
|
|
Value::Nothing { span: expr.span }
|
2021-09-04 23:52:57 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Value::Range {
|
2021-09-11 13:13:04 +02:00
|
|
|
val: Box::new(Range::new(expr.span, from, next, to, operator)?),
|
2021-09-04 23:52:57 +02:00
|
|
|
span: expr.span,
|
|
|
|
})
|
|
|
|
}
|
2021-10-29 20:15:17 +02:00
|
|
|
Expr::Var(var_id) => eval_variable(engine_state, stack, *var_id, expr.span),
|
2021-10-25 22:04:23 +02:00
|
|
|
Expr::VarDecl(_) => Ok(Value::Nothing { span: expr.span }),
|
2021-10-02 04:59:11 +02:00
|
|
|
Expr::CellPath(cell_path) => Ok(Value::CellPath {
|
|
|
|
val: cell_path.clone(),
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
2021-09-26 20:39:19 +02:00
|
|
|
Expr::FullCellPath(cell_path) => {
|
2021-10-25 08:31:39 +02:00
|
|
|
let value = eval_expression(engine_state, stack, &cell_path.head)?;
|
2021-09-07 00:02:24 +02:00
|
|
|
|
2023-03-16 04:50:58 +01:00
|
|
|
value.follow_cell_path(&cell_path.tail, false)
|
2021-09-07 00:02:24 +02:00
|
|
|
}
|
2021-12-19 08:46:13 +01:00
|
|
|
Expr::ImportPattern(_) => Ok(Value::Nothing { span: expr.span }),
|
2022-09-04 17:36:42 +02:00
|
|
|
Expr::Overlay(_) => {
|
|
|
|
let name =
|
|
|
|
String::from_utf8_lossy(engine_state.get_span_contents(&expr.span)).to_string();
|
|
|
|
|
|
|
|
Ok(Value::String {
|
|
|
|
val: name,
|
|
|
|
span: expr.span,
|
|
|
|
})
|
|
|
|
}
|
2021-10-25 06:01:02 +02:00
|
|
|
Expr::Call(call) => {
|
|
|
|
// FIXME: protect this collect with ctrl-c
|
2022-12-07 19:31:57 +01:00
|
|
|
Ok(eval_call(engine_state, stack, call, PipelineData::empty())?.into_value(call.head))
|
2021-10-25 06:01:02 +02:00
|
|
|
}
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
Expr::ExternalCall(head, args, is_subexpression) => {
|
2022-01-13 09:17:45 +01:00
|
|
|
let span = head.span;
|
2021-10-25 06:01:02 +02:00
|
|
|
// FIXME: protect this collect with ctrl-c
|
2021-10-25 23:14:21 +02:00
|
|
|
Ok(eval_external(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
2022-01-13 09:17:45 +01:00
|
|
|
head,
|
2021-10-25 23:14:21 +02:00
|
|
|
args,
|
2022-12-07 19:31:57 +01:00
|
|
|
PipelineData::empty(),
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
RedirectTarget::Piped(false, false),
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
*is_subexpression,
|
2021-10-25 23:14:21 +02:00
|
|
|
)?
|
2022-01-13 09:17:45 +01:00
|
|
|
.into_value(span))
|
2021-09-23 18:42:03 +02:00
|
|
|
}
|
2022-02-24 03:02:48 +01:00
|
|
|
Expr::DateTime(dt) => Ok(Value::Date {
|
|
|
|
val: *dt,
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
2021-08-08 23:02:47 +02:00
|
|
|
Expr::Operator(_) => Ok(Value::Nothing { span: expr.span }),
|
2023-03-24 02:52:01 +01:00
|
|
|
Expr::MatchPattern(pattern) => Ok(Value::MatchPattern {
|
|
|
|
val: pattern.clone(),
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
|
|
|
Expr::MatchBlock(_) => Ok(Value::Nothing { span: expr.span }), // match blocks are handled by `match`
|
2022-04-06 21:10:25 +02:00
|
|
|
Expr::UnaryNot(expr) => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, expr)?;
|
|
|
|
match lhs {
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Value::Bool { val, .. } => Ok(Value::boolean(!val, expr.span)),
|
2023-03-06 11:31:07 +01:00
|
|
|
_ => Err(ShellError::TypeMismatch {
|
|
|
|
err_message: "bool".to_string(),
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
2022-04-06 21:10:25 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-23 07:14:49 +02:00
|
|
|
Expr::BinaryOp(lhs, op, rhs) => {
|
2021-08-10 08:31:34 +02:00
|
|
|
let op_span = op.span;
|
2021-08-16 00:33:34 +02:00
|
|
|
let op = eval_operator(op)?;
|
2021-07-23 07:14:49 +02:00
|
|
|
|
|
|
|
match op {
|
2022-11-11 07:51:08 +01:00
|
|
|
Operator::Boolean(boolean) => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
|
|
|
match boolean {
|
|
|
|
Boolean::And => {
|
|
|
|
if lhs.is_false() {
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Ok(Value::boolean(false, expr.span))
|
2022-11-11 07:51:08 +01:00
|
|
|
} else {
|
|
|
|
let rhs = eval_expression(engine_state, stack, rhs)?;
|
|
|
|
lhs.and(op_span, &rhs, expr.span)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Boolean::Or => {
|
|
|
|
if lhs.is_true() {
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Ok(Value::boolean(true, expr.span))
|
2022-11-11 07:51:08 +01:00
|
|
|
} else {
|
|
|
|
let rhs = eval_expression(engine_state, stack, rhs)?;
|
|
|
|
lhs.or(op_span, &rhs, expr.span)
|
|
|
|
}
|
|
|
|
}
|
2022-11-26 17:02:37 +01:00
|
|
|
Boolean::Xor => {
|
|
|
|
let rhs = eval_expression(engine_state, stack, rhs)?;
|
|
|
|
lhs.xor(op_span, &rhs, expr.span)
|
|
|
|
}
|
2022-02-27 23:02:53 +01:00
|
|
|
}
|
|
|
|
}
|
2022-11-11 07:51:08 +01:00
|
|
|
Operator::Math(math) => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
2022-02-27 23:02:53 +01:00
|
|
|
let rhs = eval_expression(engine_state, stack, rhs)?;
|
2022-11-11 07:51:08 +01:00
|
|
|
|
|
|
|
match math {
|
|
|
|
Math::Plus => lhs.add(op_span, &rhs, expr.span),
|
|
|
|
Math::Minus => lhs.sub(op_span, &rhs, expr.span),
|
|
|
|
Math::Multiply => lhs.mul(op_span, &rhs, expr.span),
|
|
|
|
Math::Divide => lhs.div(op_span, &rhs, expr.span),
|
|
|
|
Math::Append => lhs.append(op_span, &rhs, expr.span),
|
|
|
|
Math::Modulo => lhs.modulo(op_span, &rhs, expr.span),
|
|
|
|
Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr.span),
|
|
|
|
Math::Pow => lhs.pow(op_span, &rhs, expr.span),
|
|
|
|
}
|
2022-07-03 13:45:20 +02:00
|
|
|
}
|
2022-11-11 07:51:08 +01:00
|
|
|
Operator::Comparison(comparison) => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
2022-07-02 20:03:36 +02:00
|
|
|
let rhs = eval_expression(engine_state, stack, rhs)?;
|
2022-11-11 07:51:08 +01:00
|
|
|
match comparison {
|
|
|
|
Comparison::LessThan => lhs.lt(op_span, &rhs, expr.span),
|
|
|
|
Comparison::LessThanOrEqual => lhs.lte(op_span, &rhs, expr.span),
|
|
|
|
Comparison::GreaterThan => lhs.gt(op_span, &rhs, expr.span),
|
|
|
|
Comparison::GreaterThanOrEqual => lhs.gte(op_span, &rhs, expr.span),
|
|
|
|
Comparison::Equal => lhs.eq(op_span, &rhs, expr.span),
|
|
|
|
Comparison::NotEqual => lhs.ne(op_span, &rhs, expr.span),
|
|
|
|
Comparison::In => lhs.r#in(op_span, &rhs, expr.span),
|
|
|
|
Comparison::NotIn => lhs.not_in(op_span, &rhs, expr.span),
|
Add LRU regex cache (#7587)
Closes #7572 by adding a cache for compiled regexes of type
`Arc<Mutex<LruCache<String, Regex>>>` to `EngineState` .
The cache is limited to 100 entries (limit chosen arbitrarily) and
evicts least-recently-used items first.
This PR makes a noticeable difference when using regexes for
`color_config`, e.g.:
```bash
#first set string formatting in config.nu like:
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }`
# then try displaying and exploring a table with many strings
# this is instant after the PR, but takes hundreds of milliseconds before
['#ff0033', '#0025ee', '#0087aa', 'string', '#4101ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff']
```
## New dependency (`lru`)
This uses [the popular `lru` crate](https://lib.rs/crates/lru). The new
dependency adds 19.8KB to a Linux release build of Nushell. I think this
is OK, especially since the crate can be useful elsewhere in Nu.
2022-12-23 23:30:04 +01:00
|
|
|
Comparison::RegexMatch => {
|
|
|
|
lhs.regex_match(engine_state, op_span, &rhs, false, expr.span)
|
|
|
|
}
|
2022-11-11 07:51:08 +01:00
|
|
|
Comparison::NotRegexMatch => {
|
Add LRU regex cache (#7587)
Closes #7572 by adding a cache for compiled regexes of type
`Arc<Mutex<LruCache<String, Regex>>>` to `EngineState` .
The cache is limited to 100 entries (limit chosen arbitrarily) and
evicts least-recently-used items first.
This PR makes a noticeable difference when using regexes for
`color_config`, e.g.:
```bash
#first set string formatting in config.nu like:
string: { if $in =~ '^#\w{6}$' { $in } else { 'white' } }`
# then try displaying and exploring a table with many strings
# this is instant after the PR, but takes hundreds of milliseconds before
['#ff0033', '#0025ee', '#0087aa', 'string', '#4101ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff', '#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff','#ff0033', '#0025ee', '#0087aa', 'string', '#6103ff']
```
## New dependency (`lru`)
This uses [the popular `lru` crate](https://lib.rs/crates/lru). The new
dependency adds 19.8KB to a Linux release build of Nushell. I think this
is OK, especially since the crate can be useful elsewhere in Nu.
2022-12-23 23:30:04 +01:00
|
|
|
lhs.regex_match(engine_state, op_span, &rhs, true, expr.span)
|
2022-11-11 07:51:08 +01:00
|
|
|
}
|
|
|
|
Comparison::StartsWith => lhs.starts_with(op_span, &rhs, expr.span),
|
|
|
|
Comparison::EndsWith => lhs.ends_with(op_span, &rhs, expr.span),
|
|
|
|
}
|
2022-07-02 20:03:36 +02:00
|
|
|
}
|
2022-11-11 07:51:08 +01:00
|
|
|
Operator::Bits(bits) => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
2022-07-02 13:48:43 +02:00
|
|
|
let rhs = eval_expression(engine_state, stack, rhs)?;
|
2022-11-11 07:51:08 +01:00
|
|
|
match bits {
|
|
|
|
Bits::BitAnd => lhs.bit_and(op_span, &rhs, expr.span),
|
|
|
|
Bits::BitOr => lhs.bit_or(op_span, &rhs, expr.span),
|
|
|
|
Bits::BitXor => lhs.bit_xor(op_span, &rhs, expr.span),
|
|
|
|
Bits::ShiftLeft => lhs.bit_shl(op_span, &rhs, expr.span),
|
|
|
|
Bits::ShiftRight => lhs.bit_shr(op_span, &rhs, expr.span),
|
|
|
|
}
|
2022-07-02 13:48:43 +02:00
|
|
|
}
|
2022-11-11 19:50:43 +01:00
|
|
|
Operator::Assignment(assignment) => {
|
2022-07-02 13:48:43 +02:00
|
|
|
let rhs = eval_expression(engine_state, stack, rhs)?;
|
2022-11-11 07:51:08 +01:00
|
|
|
|
2022-11-11 19:50:43 +01:00
|
|
|
let rhs = match assignment {
|
|
|
|
Assignment::Assign => rhs,
|
|
|
|
Assignment::PlusAssign => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
|
|
|
lhs.add(op_span, &rhs, op_span)?
|
|
|
|
}
|
|
|
|
Assignment::MinusAssign => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
|
|
|
lhs.sub(op_span, &rhs, op_span)?
|
|
|
|
}
|
|
|
|
Assignment::MultiplyAssign => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
|
|
|
lhs.mul(op_span, &rhs, op_span)?
|
|
|
|
}
|
|
|
|
Assignment::DivideAssign => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
|
|
|
lhs.div(op_span, &rhs, op_span)?
|
|
|
|
}
|
2022-12-09 17:20:58 +01:00
|
|
|
Assignment::AppendAssign => {
|
|
|
|
let lhs = eval_expression(engine_state, stack, lhs)?;
|
|
|
|
lhs.append(op_span, &rhs, op_span)?
|
|
|
|
}
|
2022-11-11 19:50:43 +01:00
|
|
|
};
|
|
|
|
|
2022-11-11 07:51:08 +01:00
|
|
|
match &lhs.expr {
|
|
|
|
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
|
|
|
|
let var_info = engine_state.get_var(*var_id);
|
|
|
|
if var_info.mutable {
|
Move variables to var stack (#8604)
# Description
This moves the representation of variables on the stack to a Vec, which
more closely resembles a stack. For small numbers of variables live at
any one point, this tends to be more efficient than a HashMap. Having a
stack-like vector also allows us to remember a stack position,
temporarily push variables on, then quickly drop the stack back to the
original size when we're done. We'll need this capability to allow
matching inside of conditions.
On this mac, a simple run of:
`timeit { mut x = 1; while $x < 1000000 { $x += 1 } }`
Went from 1 sec 86 ms, down to 1 sec 2 ms. Clearly, we have a lot more
ground we can make up in looping speed 😅 but it's nice that for fixing
this to make matching easier, we also get a win in terms of lookup speed
for small numbers of variables.
# User-Facing Changes
Likely users won't (hopefully) see any negative impact and may even see
a small positive impact.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-25 00:56:45 +01:00
|
|
|
stack.add_var(*var_id, rhs);
|
2022-11-11 07:51:08 +01:00
|
|
|
Ok(Value::nothing(lhs.span))
|
|
|
|
} else {
|
2023-03-06 11:31:07 +01:00
|
|
|
Err(ShellError::AssignmentRequiresMutableVar { lhs_span: lhs.span })
|
2022-11-11 07:51:08 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::FullCellPath(cell_path) => match &cell_path.head.expr {
|
|
|
|
Expr::Var(var_id) | Expr::VarDecl(var_id) => {
|
2022-12-06 18:51:55 +01:00
|
|
|
// The $env variable is considered "mutable" in Nushell.
|
|
|
|
// As such, give it special treatment here.
|
|
|
|
let is_env = var_id == &ENV_VARIABLE_ID;
|
|
|
|
if is_env || engine_state.get_var(*var_id).mutable {
|
|
|
|
let mut lhs =
|
|
|
|
eval_expression(engine_state, stack, &cell_path.head)?;
|
|
|
|
|
|
|
|
lhs.upsert_data_at_cell_path(&cell_path.tail, rhs)?;
|
|
|
|
if is_env {
|
2023-01-28 20:17:32 +01:00
|
|
|
if cell_path.tail.is_empty() {
|
2023-03-06 18:33:09 +01:00
|
|
|
return Err(ShellError::CannotReplaceEnv {
|
|
|
|
span: cell_path.head.span,
|
|
|
|
});
|
2023-01-28 20:17:32 +01:00
|
|
|
}
|
|
|
|
|
2022-12-06 18:51:55 +01:00
|
|
|
// The special $env treatment: for something like $env.config.history.max_size = 2000,
|
|
|
|
// get $env.config (or whichever one it is) AFTER the above mutation, and set it
|
|
|
|
// as the "config" environment variable.
|
|
|
|
let vardata = lhs.follow_cell_path(
|
|
|
|
&[cell_path.tail[0].clone()],
|
|
|
|
false,
|
|
|
|
)?;
|
|
|
|
match &cell_path.tail[0] {
|
|
|
|
PathMember::String { val, .. } => {
|
|
|
|
stack.add_env_var(val.to_string(), vardata);
|
|
|
|
}
|
|
|
|
// In case someone really wants an integer env-var
|
|
|
|
PathMember::Int { val, .. } => {
|
|
|
|
stack.add_env_var(val.to_string(), vardata);
|
|
|
|
}
|
2022-11-11 08:16:07 +01:00
|
|
|
}
|
2022-12-06 18:51:55 +01:00
|
|
|
} else {
|
Move variables to var stack (#8604)
# Description
This moves the representation of variables on the stack to a Vec, which
more closely resembles a stack. For small numbers of variables live at
any one point, this tends to be more efficient than a HashMap. Having a
stack-like vector also allows us to remember a stack position,
temporarily push variables on, then quickly drop the stack back to the
original size when we're done. We'll need this capability to allow
matching inside of conditions.
On this mac, a simple run of:
`timeit { mut x = 1; while $x < 1000000 { $x += 1 } }`
Went from 1 sec 86 ms, down to 1 sec 2 ms. Clearly, we have a lot more
ground we can make up in looping speed 😅 but it's nice that for fixing
this to make matching easier, we also get a win in terms of lookup speed
for small numbers of variables.
# User-Facing Changes
Likely users won't (hopefully) see any negative impact and may even see
a small positive impact.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-03-25 00:56:45 +01:00
|
|
|
stack.add_var(*var_id, lhs);
|
2022-11-11 08:16:07 +01:00
|
|
|
}
|
2022-11-11 07:51:08 +01:00
|
|
|
Ok(Value::nothing(cell_path.head.span))
|
|
|
|
} else {
|
2023-03-06 11:31:07 +01:00
|
|
|
Err(ShellError::AssignmentRequiresMutableVar {
|
|
|
|
lhs_span: lhs.span,
|
|
|
|
})
|
2022-11-11 07:51:08 +01:00
|
|
|
}
|
|
|
|
}
|
2023-03-06 11:31:07 +01:00
|
|
|
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
2022-11-11 07:51:08 +01:00
|
|
|
},
|
2023-03-06 11:31:07 +01:00
|
|
|
_ => Err(ShellError::AssignmentRequiresVar { lhs_span: lhs.span }),
|
2022-11-11 07:51:08 +01:00
|
|
|
}
|
2022-07-02 13:48:43 +02:00
|
|
|
}
|
2021-07-23 07:14:49 +02:00
|
|
|
}
|
2021-07-16 03:10:22 +02:00
|
|
|
}
|
2022-01-12 05:06:56 +01:00
|
|
|
Expr::Subexpression(block_id) => {
|
2021-10-25 08:31:39 +02:00
|
|
|
let block = engine_state.get_block(*block_id);
|
2021-07-23 07:14:49 +02:00
|
|
|
|
2021-10-25 06:01:02 +02:00
|
|
|
// FIXME: protect this collect with ctrl-c
|
2021-11-06 06:50:33 +01:00
|
|
|
Ok(
|
2022-12-07 19:31:57 +01:00
|
|
|
eval_subexpression(engine_state, stack, block, PipelineData::empty())?
|
2021-11-06 06:50:33 +01:00
|
|
|
.into_value(expr.span),
|
|
|
|
)
|
2021-07-23 07:14:49 +02:00
|
|
|
}
|
2022-11-10 09:21:49 +01:00
|
|
|
Expr::RowCondition(block_id) | Expr::Closure(block_id) => {
|
2022-01-12 05:06:56 +01:00
|
|
|
let mut captures = HashMap::new();
|
|
|
|
let block = engine_state.get_block(*block_id);
|
|
|
|
|
|
|
|
for var_id in &block.captures {
|
2022-01-29 14:00:48 +01:00
|
|
|
captures.insert(*var_id, stack.get_var(*var_id, expr.span)?);
|
2022-01-12 05:06:56 +01:00
|
|
|
}
|
2022-11-10 09:21:49 +01:00
|
|
|
Ok(Value::Closure {
|
2022-01-12 05:06:56 +01:00
|
|
|
val: *block_id,
|
|
|
|
captures,
|
|
|
|
span: expr.span,
|
|
|
|
})
|
|
|
|
}
|
2022-11-10 09:21:49 +01:00
|
|
|
Expr::Block(block_id) => Ok(Value::Block {
|
|
|
|
val: *block_id,
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
2021-07-23 23:19:30 +02:00
|
|
|
Expr::List(x) => {
|
|
|
|
let mut output = vec![];
|
|
|
|
for expr in x {
|
2021-10-25 08:31:39 +02:00
|
|
|
output.push(eval_expression(engine_state, stack, expr)?);
|
2021-07-23 23:19:30 +02:00
|
|
|
}
|
2021-08-08 23:02:47 +02:00
|
|
|
Ok(Value::List {
|
2021-09-07 09:07:11 +02:00
|
|
|
vals: output,
|
2021-08-08 23:02:47 +02:00
|
|
|
span: expr.span,
|
|
|
|
})
|
2021-07-23 23:19:30 +02:00
|
|
|
}
|
2021-11-11 00:14:00 +01:00
|
|
|
Expr::Record(fields) => {
|
|
|
|
let mut cols = vec![];
|
|
|
|
let mut vals = vec![];
|
|
|
|
for (col, val) in fields {
|
2022-05-26 02:10:31 +02:00
|
|
|
// avoid duplicate cols.
|
|
|
|
let col_name = eval_expression(engine_state, stack, col)?.as_string()?;
|
|
|
|
let pos = cols.iter().position(|c| c == &col_name);
|
|
|
|
match pos {
|
|
|
|
Some(index) => {
|
2023-04-01 20:09:33 +02:00
|
|
|
return Err(ShellError::ColumnDefinedTwice {
|
|
|
|
second_use: col.span,
|
|
|
|
first_use: fields[index].0.span,
|
|
|
|
})
|
2022-05-26 02:10:31 +02:00
|
|
|
}
|
|
|
|
None => {
|
|
|
|
cols.push(col_name);
|
|
|
|
vals.push(eval_expression(engine_state, stack, val)?);
|
|
|
|
}
|
|
|
|
}
|
2021-11-11 00:14:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(Value::Record {
|
|
|
|
cols,
|
|
|
|
vals,
|
|
|
|
span: expr.span,
|
|
|
|
})
|
|
|
|
}
|
2021-08-28 21:17:30 +02:00
|
|
|
Expr::Table(headers, vals) => {
|
|
|
|
let mut output_headers = vec![];
|
|
|
|
for expr in headers {
|
2021-10-25 08:31:39 +02:00
|
|
|
output_headers.push(eval_expression(engine_state, stack, expr)?.as_string()?);
|
2021-08-28 21:17:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let mut output_rows = vec![];
|
|
|
|
for val in vals {
|
|
|
|
let mut row = vec![];
|
|
|
|
for expr in val {
|
2021-10-25 08:31:39 +02:00
|
|
|
row.push(eval_expression(engine_state, stack, expr)?);
|
2021-08-28 21:17:30 +02:00
|
|
|
}
|
2021-09-07 09:07:11 +02:00
|
|
|
output_rows.push(Value::Record {
|
|
|
|
cols: output_headers.clone(),
|
|
|
|
vals: row,
|
|
|
|
span: expr.span,
|
|
|
|
});
|
2021-08-28 21:17:30 +02:00
|
|
|
}
|
2021-09-07 09:07:11 +02:00
|
|
|
Ok(Value::List {
|
|
|
|
vals: output_rows,
|
2021-08-28 21:17:30 +02:00
|
|
|
span: expr.span,
|
|
|
|
})
|
|
|
|
}
|
2021-10-25 08:31:39 +02:00
|
|
|
Expr::Keyword(_, _, expr) => eval_expression(engine_state, stack, expr),
|
2021-12-25 21:50:02 +01:00
|
|
|
Expr::StringInterpolation(exprs) => {
|
|
|
|
let mut parts = vec![];
|
|
|
|
for expr in exprs {
|
|
|
|
parts.push(eval_expression(engine_state, stack, expr)?);
|
|
|
|
}
|
|
|
|
|
2022-04-19 00:28:01 +02:00
|
|
|
let config = engine_state.get_config();
|
2021-12-25 21:50:02 +01:00
|
|
|
|
|
|
|
parts
|
|
|
|
.into_iter()
|
|
|
|
.into_pipeline_data(None)
|
2022-04-19 00:28:01 +02:00
|
|
|
.collect_string("", config)
|
2021-12-25 21:50:02 +01:00
|
|
|
.map(|x| Value::String {
|
|
|
|
val: x,
|
|
|
|
span: expr.span,
|
|
|
|
})
|
|
|
|
}
|
2021-07-23 23:19:30 +02:00
|
|
|
Expr::String(s) => Ok(Value::String {
|
|
|
|
val: s.clone(),
|
|
|
|
span: expr.span,
|
|
|
|
}),
|
2022-01-16 14:55:56 +01:00
|
|
|
Expr::Filepath(s) => {
|
|
|
|
let cwd = current_dir_str(engine_state, stack)?;
|
|
|
|
let path = expand_path_with(s, cwd);
|
|
|
|
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Ok(Value::string(path.to_string_lossy(), expr.span))
|
2022-01-16 14:55:56 +01:00
|
|
|
}
|
2022-04-22 22:18:51 +02:00
|
|
|
Expr::Directory(s) => {
|
2022-04-23 01:48:10 +02:00
|
|
|
if s == "-" {
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Ok(Value::string("-", expr.span))
|
2022-04-23 01:48:10 +02:00
|
|
|
} else {
|
|
|
|
let cwd = current_dir_str(engine_state, stack)?;
|
|
|
|
let path = expand_path_with(s, cwd);
|
2022-04-22 22:18:51 +02:00
|
|
|
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Ok(Value::string(path.to_string_lossy(), expr.span))
|
2022-04-23 01:48:10 +02:00
|
|
|
}
|
2022-04-22 22:18:51 +02:00
|
|
|
}
|
2022-01-16 14:55:56 +01:00
|
|
|
Expr::GlobPattern(s) => {
|
|
|
|
let cwd = current_dir_str(engine_state, stack)?;
|
|
|
|
let path = expand_path_with(s, cwd);
|
|
|
|
|
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412)
# Description
While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`,
`Value::boolean()` and `Value::string()` constructors, which seem
designed to make it easier to construct various Values, but which aren't
used often at all in the codebase. So, using a few find-replaces
regexes, I increased their usage. This reduces overall LOC because
structures like this:
```
Value::Int {
val: a,
span: head
}
```
are changed into
```
Value::int(a, head)
```
and are respected as such by the project's formatter.
There are little readability concerns because the second argument to all
of these is `span`, and it's almost always extremely obvious which is
the span at every callsite.
# User-Facing Changes
None.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 17:37:51 +01:00
|
|
|
Ok(Value::string(path.to_string_lossy(), expr.span))
|
2022-01-16 14:55:56 +01:00
|
|
|
}
|
2021-08-08 23:02:47 +02:00
|
|
|
Expr::Signature(_) => Ok(Value::Nothing { span: expr.span }),
|
|
|
|
Expr::Garbage => Ok(Value::Nothing { span: expr.span }),
|
2021-12-20 02:05:33 +01:00
|
|
|
Expr::Nothing => Ok(Value::Nothing { span: expr.span }),
|
2021-07-16 03:10:22 +02:00
|
|
|
}
|
2021-07-23 07:14:49 +02:00
|
|
|
}
|
|
|
|
|
2022-01-26 20:00:25 +01:00
|
|
|
/// Checks the expression to see if it's a internal or external call. If so, passes the input
|
|
|
|
/// into the call and gets out the result
|
|
|
|
/// Otherwise, invokes the expression
|
2022-07-20 14:44:42 +02:00
|
|
|
///
|
2022-11-24 04:58:15 +01:00
|
|
|
/// It returns PipelineData with a boolean flag, indicating if the external failed to run.
|
2022-07-20 14:44:42 +02:00
|
|
|
/// The boolean flag **may only be true** for external calls, for internal calls, it always to be false.
|
2022-01-26 20:00:25 +01:00
|
|
|
pub fn eval_expression_with_input(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
expr: &Expression,
|
|
|
|
mut input: PipelineData,
|
2022-02-21 23:22:21 +01:00
|
|
|
redirect_stdout: bool,
|
|
|
|
redirect_stderr: bool,
|
2022-07-20 14:44:42 +02:00
|
|
|
) -> Result<(PipelineData, bool), ShellError> {
|
2022-01-26 20:00:25 +01:00
|
|
|
match expr {
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
} => {
|
2022-02-21 23:22:21 +01:00
|
|
|
if !redirect_stdout || redirect_stderr {
|
|
|
|
// we're doing something different than the defaults
|
|
|
|
let mut call = call.clone();
|
|
|
|
call.redirect_stdout = redirect_stdout;
|
|
|
|
call.redirect_stderr = redirect_stderr;
|
|
|
|
input = eval_call(engine_state, stack, &call, input)?;
|
|
|
|
} else {
|
|
|
|
input = eval_call(engine_state, stack, call, input)?;
|
|
|
|
}
|
2022-01-26 20:00:25 +01:00
|
|
|
}
|
|
|
|
Expression {
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
expr: Expr::ExternalCall(head, args, is_subexpression),
|
2022-01-26 20:00:25 +01:00
|
|
|
..
|
|
|
|
} => {
|
2022-09-30 14:13:46 +02:00
|
|
|
input = eval_external(
|
2022-02-21 23:22:21 +01:00
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
head,
|
|
|
|
args,
|
|
|
|
input,
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
RedirectTarget::Piped(redirect_stdout, redirect_stderr),
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
*is_subexpression,
|
2022-02-21 23:22:21 +01:00
|
|
|
)?;
|
2022-01-26 20:00:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Subexpression(block_id),
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
let block = engine_state.get_block(*block_id);
|
|
|
|
|
|
|
|
// FIXME: protect this collect with ctrl-c
|
|
|
|
input = eval_subexpression(engine_state, stack, block, input)?;
|
|
|
|
}
|
|
|
|
|
2023-03-22 21:14:10 +01:00
|
|
|
elem @ Expression {
|
|
|
|
expr: Expr::FullCellPath(full_cell_path),
|
|
|
|
..
|
|
|
|
} => match &full_cell_path.head {
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Subexpression(block_id),
|
|
|
|
span,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
let block = engine_state.get_block(*block_id);
|
|
|
|
|
|
|
|
// FIXME: protect this collect with ctrl-c
|
|
|
|
input = eval_subexpression(engine_state, stack, block, input)?;
|
|
|
|
let value = input.into_value(*span);
|
|
|
|
input = value
|
|
|
|
.follow_cell_path(&full_cell_path.tail, false)?
|
|
|
|
.into_pipeline_data()
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2022-01-26 20:00:25 +01:00
|
|
|
elem => {
|
|
|
|
input = eval_expression(engine_state, stack, elem)?.into_pipeline_data();
|
|
|
|
}
|
2022-09-30 14:13:46 +02:00
|
|
|
};
|
|
|
|
|
2023-03-16 19:07:32 +01:00
|
|
|
Ok(might_consume_external_result(input))
|
2022-09-30 14:13:46 +02:00
|
|
|
}
|
2022-01-26 20:00:25 +01:00
|
|
|
|
2022-12-14 19:25:32 +01:00
|
|
|
// Try to catch and detect if external command runs to failed.
|
2022-09-30 14:13:46 +02:00
|
|
|
fn might_consume_external_result(input: PipelineData) -> (PipelineData, bool) {
|
2022-12-14 19:25:32 +01:00
|
|
|
input.is_external_failed()
|
2022-01-26 20:00:25 +01:00
|
|
|
}
|
|
|
|
|
2022-11-18 22:46:48 +01:00
|
|
|
pub fn eval_element_with_input(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
element: &PipelineElement,
|
2023-01-30 16:49:31 +01:00
|
|
|
mut input: PipelineData,
|
2022-11-18 22:46:48 +01:00
|
|
|
redirect_stdout: bool,
|
|
|
|
redirect_stderr: bool,
|
|
|
|
) -> Result<(PipelineData, bool), ShellError> {
|
|
|
|
match element {
|
2022-12-13 04:36:13 +01:00
|
|
|
PipelineElement::Expression(_, expr) => eval_expression_with_input(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
expr,
|
|
|
|
input,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
),
|
2022-11-22 19:26:13 +01:00
|
|
|
PipelineElement::Redirection(span, redirection, expr) => match &expr.expr {
|
|
|
|
Expr::String(_) => {
|
2023-01-30 16:49:31 +01:00
|
|
|
let exit_code = match &mut input {
|
|
|
|
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
|
|
|
_ => None,
|
|
|
|
};
|
2022-11-22 19:26:13 +01:00
|
|
|
let input = match (redirection, input) {
|
|
|
|
(
|
|
|
|
Redirection::Stderr,
|
|
|
|
PipelineData::ExternalStream {
|
|
|
|
stderr,
|
|
|
|
exit_code,
|
|
|
|
span,
|
|
|
|
metadata,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
trim_end_newline,
|
2022-11-22 19:26:13 +01:00
|
|
|
..
|
|
|
|
},
|
|
|
|
) => PipelineData::ExternalStream {
|
|
|
|
stdout: stderr,
|
|
|
|
stderr: None,
|
|
|
|
exit_code,
|
|
|
|
span,
|
|
|
|
metadata,
|
Make external command substitution works friendly(like fish shell, trailing ending newlines) (#7156)
# Description
As title, when execute external sub command, auto-trimming end
new-lines, like how fish shell does.
And if the command is executed directly like: `cat tmp`, the result
won't change.
Fixes: #6816
Fixes: #3980
Note that although nushell works correctly by directly replace output of
external command to variable(or other places like string interpolation),
it's not friendly to user, and users almost want to use `str trim` to
trim trailing newline, I think that's why fish shell do this
automatically.
If the pr is ok, as a result, no more `str trim -r` is required when
user is writing scripts which using external commands.
# User-Facing Changes
Before:
<img width="523" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468810-86b04dbb-c147-459a-96a5-e0095eeaab3d.png">
After:
<img width="505" alt="img"
src="https://user-images.githubusercontent.com/22256154/202468599-7b537488-3d6b-458e-9d75-d85780826db0.png">
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace --features=extra -- -D warnings -D
clippy::unwrap_used -A clippy::needless_collect` to check that you're
using the standard code style
- `cargo test --workspace --features=extra` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-23 04:51:57 +01:00
|
|
|
trim_end_newline,
|
2022-11-22 19:26:13 +01:00
|
|
|
},
|
|
|
|
(_, input) => input,
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(save_command) = engine_state.find_decl(b"save", &[]) {
|
|
|
|
eval_call(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
&Call {
|
|
|
|
decl_id: save_command,
|
|
|
|
head: *span,
|
|
|
|
arguments: vec![
|
|
|
|
Argument::Positional(expr.clone()),
|
|
|
|
Argument::Named((
|
|
|
|
Spanned {
|
2022-12-01 13:26:17 +01:00
|
|
|
item: "raw".into(),
|
|
|
|
span: *span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
)),
|
|
|
|
Argument::Named((
|
|
|
|
Spanned {
|
|
|
|
item: "force".into(),
|
2022-11-22 19:26:13 +01:00
|
|
|
span: *span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
)),
|
|
|
|
],
|
|
|
|
redirect_stdout: false,
|
|
|
|
redirect_stderr: false,
|
2023-04-05 18:56:48 +02:00
|
|
|
parser_info: HashMap::new(),
|
2022-11-22 19:26:13 +01:00
|
|
|
},
|
|
|
|
input,
|
|
|
|
)
|
2023-01-30 16:49:31 +01:00
|
|
|
.map(|_| {
|
|
|
|
// save is internal command, normally it exists with non-ExternalStream
|
|
|
|
// but here in redirection context, we make it returns ExternalStream
|
|
|
|
// So nu handles exit_code correctly
|
|
|
|
(
|
|
|
|
PipelineData::ExternalStream {
|
|
|
|
stdout: None,
|
|
|
|
stderr: None,
|
|
|
|
exit_code,
|
|
|
|
span: *span,
|
|
|
|
metadata: None,
|
|
|
|
trim_end_newline: false,
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
})
|
2022-11-22 19:26:13 +01:00
|
|
|
} else {
|
|
|
|
Err(ShellError::CommandNotFound(*span))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => Err(ShellError::CommandNotFound(*span)),
|
|
|
|
},
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
PipelineElement::SeparateRedirection {
|
|
|
|
out: (out_span, out_expr),
|
|
|
|
err: (err_span, err_expr),
|
|
|
|
} => match (&out_expr.expr, &err_expr.expr) {
|
|
|
|
(Expr::String(_), Expr::String(_)) => {
|
|
|
|
if let Some(save_command) = engine_state.find_decl(b"save", &[]) {
|
2023-01-30 16:49:31 +01:00
|
|
|
let exit_code = match &mut input {
|
|
|
|
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
|
|
|
_ => None,
|
|
|
|
};
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
eval_call(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
&Call {
|
|
|
|
decl_id: save_command,
|
|
|
|
head: *out_span,
|
|
|
|
arguments: vec![
|
|
|
|
Argument::Positional(out_expr.clone()),
|
|
|
|
Argument::Named((
|
|
|
|
Spanned {
|
|
|
|
item: "stderr".into(),
|
|
|
|
span: *err_span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
Some(err_expr.clone()),
|
|
|
|
)),
|
|
|
|
Argument::Named((
|
|
|
|
Spanned {
|
|
|
|
item: "raw".into(),
|
|
|
|
span: *out_span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
)),
|
|
|
|
Argument::Named((
|
|
|
|
Spanned {
|
|
|
|
item: "force".into(),
|
|
|
|
span: *out_span,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
)),
|
|
|
|
],
|
|
|
|
redirect_stdout: false,
|
|
|
|
redirect_stderr: false,
|
2023-04-05 18:56:48 +02:00
|
|
|
parser_info: HashMap::new(),
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
},
|
|
|
|
input,
|
|
|
|
)
|
2023-01-30 16:49:31 +01:00
|
|
|
.map(|_| {
|
|
|
|
// save is internal command, normally it exists with non-ExternalStream
|
|
|
|
// but here in redirection context, we make it returns ExternalStream
|
|
|
|
// So nu handles exit_code correctly
|
|
|
|
(
|
|
|
|
PipelineData::ExternalStream {
|
|
|
|
stdout: None,
|
|
|
|
stderr: None,
|
|
|
|
exit_code,
|
|
|
|
span: *out_span,
|
|
|
|
metadata: None,
|
|
|
|
trim_end_newline: false,
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
)
|
|
|
|
})
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
} else {
|
|
|
|
Err(ShellError::CommandNotFound(*out_span))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(_out_other, err_other) => {
|
|
|
|
if let Expr::String(_) = err_other {
|
|
|
|
Err(ShellError::CommandNotFound(*out_span))
|
|
|
|
} else {
|
|
|
|
Err(ShellError::CommandNotFound(*err_span))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
Avoid blocking when `o+e>` redirects too much stderr message (#8784)
# Description
Fixes: #8565
Here is another pr #7240 tried to address the issue, but it works in a
wrong way.
After this change `o+e>` won't redirect all stdout message then stderr
message and it works more like how bash does.
# User-Facing Changes
For the given python code:
```python
# test.py
import sys
print('aa'*300, flush=True)
print('bb'*999999, file=sys.stderr, flush=True)
print('cc'*300, flush=True)
```
Running `python test.py out+err> a.txt` shoudn't hang nushell, and
`a.txt` keeps output in the same order
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SameTargetRedirection` if we meet `out+err>` redirection
token(which is generated by lex function),
During converting from lite block to block,
LiteElement::SameTargetRedirection will be converted to
PipelineElement::SameTargetRedirection.
Then in the block eval process, if we get
PipelineElement::SameTargetRedirection, we'll invoke `run-external` with
`--redirect-combine` flag, then pipe the result into save command
## What happened internally?
Take the following command as example:
`^ls o+e> log.txt`
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline {
commands: [
SameTargetRedirection {
cmd: (None, LiteCommand { comments: [], parts: [Span { start: 147945, end: 147948}]}),
redirection: (Span { start: 147949, end: 147957 }, LiteCommand { comments: [], parts: [Span { start: 147958, end: 147965 }]})
}
]
}
]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, StdoutAndStderr, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
SameTargetRedirection {
cmd: (None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 147946, end: 147948 }, ty: String, custom_completion: None}, [], false),
span: Span { start: 147945, end: 147948},
ty: Any, custom_completion: None
}),
redirection: (Span { start: 147949, end: 147957}, Expression {expr: String("log.txt"), span: Span { start: 147958, end: 147965 },ty: String,custom_completion: None}
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-utils/standard_library/tests.nu` to run the
tests for the standard library
> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-05-18 00:47:03 +02:00
|
|
|
PipelineElement::SameTargetRedirection {
|
|
|
|
cmd: (cmd_span, cmd_exp),
|
|
|
|
redirection: (redirect_span, redirect_exp),
|
|
|
|
} => {
|
|
|
|
// general idea: eval cmd and call save command to redirect stdout to result.
|
|
|
|
input = match &cmd_exp.expr {
|
|
|
|
Expr::ExternalCall(head, args, is_subexpression) => {
|
|
|
|
// if cmd's expression is ExternalStream, then invoke run-external with
|
|
|
|
// special --redirect-combine flag.
|
|
|
|
eval_external(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
head,
|
|
|
|
args,
|
|
|
|
input,
|
|
|
|
RedirectTarget::CombinedPipe,
|
|
|
|
*is_subexpression,
|
|
|
|
)?
|
|
|
|
}
|
|
|
|
_ => eval_element_with_input(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
&PipelineElement::Expression(*cmd_span, cmd_exp.clone()),
|
|
|
|
input,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
)
|
|
|
|
.map(|x| x.0)?,
|
|
|
|
};
|
|
|
|
eval_element_with_input(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
&PipelineElement::Redirection(
|
|
|
|
*redirect_span,
|
|
|
|
Redirection::Stdout,
|
|
|
|
redirect_exp.clone(),
|
|
|
|
),
|
|
|
|
input,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
)
|
|
|
|
}
|
2022-12-13 04:36:13 +01:00
|
|
|
PipelineElement::And(_, expr) => eval_expression_with_input(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
expr,
|
|
|
|
input,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
),
|
|
|
|
PipelineElement::Or(_, expr) => eval_expression_with_input(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
expr,
|
|
|
|
input,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
),
|
2022-11-18 22:46:48 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
New commands: `break`, `continue`, `return`, and `loop` (#7230)
# Description
This adds `break`, `continue`, `return`, and `loop`.
* `break` - breaks out a loop
* `continue` - continues a loop at the next iteration
* `return` - early return from a function call
* `loop` - loop forever (until the loop hits a break)
Examples:
```
for i in 1..10 {
if $i == 5 {
continue
}
print $i
}
```
```
for i in 1..10 {
if $i == 5 {
break
}
print $i
}
```
```
def foo [x] {
if true {
return 2
}
$x
}
foo 100
```
```
loop { print "hello, forever" }
```
```
[1, 2, 3, 4, 5] | each {|x|
if $x > 3 { break }
$x
}
```
# User-Facing Changes
Adds the above commands.
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2022-11-24 21:39:16 +01:00
|
|
|
pub fn eval_block_with_early_return(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
block: &Block,
|
|
|
|
input: PipelineData,
|
|
|
|
redirect_stdout: bool,
|
|
|
|
redirect_stderr: bool,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
|
|
|
match eval_block(
|
|
|
|
engine_state,
|
|
|
|
stack,
|
|
|
|
block,
|
|
|
|
input,
|
|
|
|
redirect_stdout,
|
|
|
|
redirect_stderr,
|
|
|
|
) {
|
|
|
|
Err(ShellError::Return(_, value)) => Ok(PipelineData::Value(*value, None)),
|
|
|
|
x => x,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-03 04:15:01 +02:00
|
|
|
pub fn eval_block(
|
2021-10-25 08:31:39 +02:00
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
2021-09-03 04:15:01 +02:00
|
|
|
block: &Block,
|
2021-10-25 06:01:02 +02:00
|
|
|
mut input: PipelineData,
|
2022-02-21 23:22:21 +01:00
|
|
|
redirect_stdout: bool,
|
|
|
|
redirect_stderr: bool,
|
2021-10-25 06:01:02 +02:00
|
|
|
) -> Result<PipelineData, ShellError> {
|
2023-01-05 03:38:50 +01:00
|
|
|
// if Block contains recursion, make sure we don't recurse too deeply (to avoid stack overflow)
|
|
|
|
if let Some(recursive) = block.recursive {
|
|
|
|
// picked 50 arbitrarily, should work on all architectures
|
|
|
|
const RECURSION_LIMIT: u64 = 50;
|
|
|
|
if recursive {
|
|
|
|
if *stack.recursion_count >= RECURSION_LIMIT {
|
|
|
|
stack.recursion_count = Box::new(0);
|
|
|
|
return Err(ShellError::RecursionLimitReached {
|
|
|
|
recursion_limit: RECURSION_LIMIT,
|
|
|
|
span: block.span,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
*stack.recursion_count += 1;
|
|
|
|
}
|
|
|
|
}
|
2023-02-11 22:35:48 +01:00
|
|
|
|
2022-02-15 20:31:14 +01:00
|
|
|
let num_pipelines = block.len();
|
2023-02-11 22:35:48 +01:00
|
|
|
|
|
|
|
let mut input_metadata = if stack.profiling_config.should_debug() {
|
|
|
|
stack.profiling_config.enter_block();
|
|
|
|
input.metadata()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
|
2022-11-22 19:26:13 +01:00
|
|
|
let mut i = 0;
|
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
while i < pipeline.elements.len() {
|
2022-11-24 04:58:15 +01:00
|
|
|
let redirect_stderr = redirect_stderr
|
|
|
|
|| ((i < pipeline.elements.len() - 1)
|
|
|
|
&& (matches!(
|
|
|
|
pipeline.elements[i + 1],
|
|
|
|
PipelineElement::Redirection(_, Redirection::Stderr, _)
|
|
|
|
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
| PipelineElement::SeparateRedirection { .. }
|
2022-11-24 04:58:15 +01:00
|
|
|
)));
|
|
|
|
|
2023-02-11 22:35:48 +01:00
|
|
|
let start_time = if stack.profiling_config.should_debug() {
|
|
|
|
Some(Instant::now())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2022-07-20 14:44:42 +02:00
|
|
|
// if eval internal command failed, it can just make early return with `Err(ShellError)`.
|
2022-11-18 22:46:48 +01:00
|
|
|
let eval_result = eval_element_with_input(
|
2022-02-15 20:31:14 +01:00
|
|
|
engine_state,
|
|
|
|
stack,
|
2022-11-22 19:26:13 +01:00
|
|
|
&pipeline.elements[i],
|
2022-02-15 20:31:14 +01:00
|
|
|
input,
|
2022-11-23 03:18:34 +01:00
|
|
|
redirect_stdout
|
|
|
|
|| (i != pipeline.elements.len() - 1)
|
|
|
|
&& (matches!(
|
|
|
|
pipeline.elements[i + 1],
|
|
|
|
PipelineElement::Redirection(_, Redirection::Stdout, _)
|
|
|
|
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
|
|
|
|
| PipelineElement::Expression(..)
|
Support redirect `err` and `out` to different streams (#7685)
# Description
Closes: #7364
# User-Facing Changes
Given the following shell script:
```bash
x=$(printf '=%.0s' {1..100})
echo $x
echo $x 1>&2
```
It supports the following command:
```
bash test.sh out> out.txt err> err.txt
```
Then both `out.txt` and `err.txt` will contain `=`(100 times)
## About the change
The core idea is that when doing lite-parsing, introduce a new variant
`LiteElement::SeparateRedirection` if we meet two Redirection
token(which is generated by `lex` function),
During converting from lite block to block,
`LiteElement::SeparateRedirection` will be converted to
`PipelineElement::SeparateRedirection`.
Then in the block eval process, if we get
`PipelineElement::SeparateRedirection`, we invoke `save` command with
`--stderr` arguments to acthive our behavior.
## What happened internally?
Take the following command as example:
```
^ls out> out.txt err> err.txt
```
lex parsing result(`Tokens`) are not changed, but `LiteBlock` and
`Block` is changed after this pr.
### LiteBlock before
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(None, LiteCommand { comments: [], parts: [Span { start: 39041, end: 39044 }] }),
// actually the span of first Redirection is wrong too..
Redirection(Span { start: 39058, end: 39062 }, Stdout, LiteCommand { comments: [], parts: [Span { start: 39050, end: 39057 }] }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, LiteCommand { comments: [], parts: [Span { start: 39063, end: 39070 }] })
]
}]
}
```
### LiteBlock after
```rust
LiteBlock {
block: [
LitePipeline { commands: [
Command(
None,
LiteCommand { comments: [], parts: [Span { start: 38525, end: 38528 }] }),
// new one! two Redirection merged into one SeparateRedirection.
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, LiteCommand { comments: [], parts: [Span { start: 38534, end: 38541 }] }),
err: (Span { start: 38542, end: 38546 }, LiteCommand { comments: [], parts: [Span { start: 38547, end: 38554 }] })
}
]
}]
}
```
### Block before
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 39042, end: 39044 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 39041, end: 39044 },
ty: Any, custom_completion: None
}),
Redirection(Span { start: 39058, end: 39062 }, Stdout, Expression { expr: String("out.txt"), span: Span { start: 39050, end: 39057 }, ty: String, custom_completion: None }),
Redirection(Span { start: 39058, end: 39062 }, Stderr, Expression { expr: String("err.txt"), span: Span { start: 39063, end: 39070 }, ty: String, custom_completion: None })] }
```
### Block after
```rust
Pipeline {
elements: [
Expression(None, Expression {
expr: ExternalCall(Expression { expr: String("ls"), span: Span { start: 38526, end: 38528 }, ty: String, custom_completion: None }, [], false),
span: Span { start: 38525, end: 38528 },
ty: Any,
custom_completion: None
}),
// new one! SeparateRedirection
SeparateRedirection {
out: (Span { start: 38529, end: 38533 }, Expression { expr: String("out.txt"), span: Span { start: 38534, end: 38541 }, ty: String, custom_completion: None }),
err: (Span { start: 38542, end: 38546 }, Expression { expr: String("err.txt"), span: Span { start: 38547, end: 38554 }, ty: String, custom_completion: None })
}
]
}
```
# Tests + Formatting
Don't forget to add tests that cover your changes.
Make sure you've run and fixed any issues with these commands:
- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect` to check that you're using the standard code
style
- `cargo test --workspace` to check that all tests pass
# After Submitting
If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-01-12 10:22:30 +01:00
|
|
|
| PipelineElement::SeparateRedirection { .. }
|
2022-11-23 03:18:34 +01:00
|
|
|
)),
|
2022-11-24 04:58:15 +01:00
|
|
|
redirect_stderr,
|
|
|
|
);
|
|
|
|
|
2023-02-11 22:35:48 +01:00
|
|
|
let end_time = if stack.profiling_config.should_debug() {
|
|
|
|
Some(Instant::now())
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let (Some(start_time), Some(end_time), Some(input_metadata)) =
|
|
|
|
(start_time, end_time, input_metadata.as_deref_mut())
|
|
|
|
{
|
2023-02-24 23:29:07 +01:00
|
|
|
let element_span = pipeline.elements[i].span();
|
|
|
|
let element_str = String::from_utf8_lossy(
|
|
|
|
engine_state.get_span_contents(&pipeline.elements[i].span()),
|
|
|
|
)
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
collect_profiling_metadata(
|
|
|
|
pipeline_idx,
|
|
|
|
i,
|
|
|
|
element_str,
|
|
|
|
element_span,
|
|
|
|
start_time,
|
|
|
|
end_time,
|
|
|
|
&stack.profiling_config,
|
|
|
|
&eval_result,
|
|
|
|
input_metadata,
|
2023-02-11 22:35:48 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-11-24 04:58:15 +01:00
|
|
|
match (eval_result, redirect_stderr) {
|
|
|
|
(Ok((pipeline_data, _)), true) => {
|
|
|
|
input = pipeline_data;
|
|
|
|
|
|
|
|
// external command may runs to failed
|
|
|
|
// make early return so remaining commands will not be executed.
|
|
|
|
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
|
|
|
|
}
|
2023-03-12 09:57:27 +01:00
|
|
|
(Err(error), true) => {
|
|
|
|
input = PipelineData::Value(
|
|
|
|
Value::Error {
|
|
|
|
error: Box::new(error),
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
}
|
2022-11-24 04:58:15 +01:00
|
|
|
(output, false) => {
|
|
|
|
let output = output?;
|
|
|
|
input = output.0;
|
2022-12-13 04:36:13 +01:00
|
|
|
// external command may runs to failed
|
2022-11-24 04:58:15 +01:00
|
|
|
// make early return so remaining commands will not be executed.
|
|
|
|
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
|
|
|
|
if output.1 {
|
2023-02-11 22:35:48 +01:00
|
|
|
if stack.profiling_config.should_debug() {
|
|
|
|
stack.profiling_config.leave_block();
|
|
|
|
}
|
2022-11-24 04:58:15 +01:00
|
|
|
return Ok(input);
|
|
|
|
}
|
|
|
|
}
|
2022-07-20 14:44:42 +02:00
|
|
|
}
|
2022-11-22 19:26:13 +01:00
|
|
|
|
|
|
|
i += 1;
|
2021-11-06 06:50:33 +01:00
|
|
|
}
|
2022-02-06 03:03:06 +01:00
|
|
|
|
2022-02-15 20:31:14 +01:00
|
|
|
if pipeline_idx < (num_pipelines) - 1 {
|
2022-12-13 04:36:13 +01:00
|
|
|
match input {
|
|
|
|
PipelineData::Value(Value::Nothing { .. }, ..) => {}
|
|
|
|
PipelineData::ExternalStream {
|
|
|
|
ref mut exit_code, ..
|
|
|
|
} => {
|
|
|
|
let exit_code = exit_code.take();
|
|
|
|
|
2023-03-16 23:53:46 +01:00
|
|
|
input.drain()?;
|
2022-12-12 21:53:46 +01:00
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
if let Some(exit_code) = exit_code {
|
|
|
|
let mut v: Vec<_> = exit_code.collect();
|
2022-12-12 21:53:46 +01:00
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
if let Some(v) = v.pop() {
|
|
|
|
stack.add_env_var("LAST_EXIT_CODE".into(), v);
|
|
|
|
}
|
|
|
|
}
|
2022-12-12 21:53:46 +01:00
|
|
|
}
|
2023-03-16 23:53:46 +01:00
|
|
|
_ => input.drain()?,
|
2022-02-06 03:03:06 +01:00
|
|
|
}
|
|
|
|
|
2022-12-13 04:36:13 +01:00
|
|
|
input = PipelineData::empty()
|
2022-02-06 03:03:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-11 22:35:48 +01:00
|
|
|
if stack.profiling_config.should_debug() {
|
|
|
|
stack.profiling_config.leave_block();
|
|
|
|
Ok(input.set_metadata(input_metadata))
|
|
|
|
} else {
|
|
|
|
Ok(input)
|
|
|
|
}
|
2022-02-06 03:03:06 +01:00
|
|
|
}
|
|
|
|
|
2021-11-06 06:50:33 +01:00
|
|
|
pub fn eval_subexpression(
|
|
|
|
engine_state: &EngineState,
|
|
|
|
stack: &mut Stack,
|
|
|
|
block: &Block,
|
|
|
|
mut input: PipelineData,
|
|
|
|
) -> Result<PipelineData, ShellError> {
|
2022-02-15 20:31:14 +01:00
|
|
|
for pipeline in block.pipelines.iter() {
|
2023-03-16 19:07:32 +01:00
|
|
|
for expr in pipeline.elements.iter() {
|
|
|
|
input = eval_element_with_input(engine_state, stack, expr, input, true, false)?.0
|
2021-07-16 03:10:22 +02:00
|
|
|
}
|
|
|
|
}
|
2021-07-23 07:14:49 +02:00
|
|
|
|
2021-09-03 04:15:01 +02:00
|
|
|
Ok(input)
|
2021-07-16 03:10:22 +02:00
|
|
|
}
|
2021-10-05 04:27:39 +02:00
|
|
|
|
2021-10-29 20:15:17 +02:00
|
|
|
pub fn eval_variable(
|
2021-11-02 04:08:05 +01:00
|
|
|
engine_state: &EngineState,
|
2021-10-29 20:15:17 +02:00
|
|
|
stack: &Stack,
|
|
|
|
var_id: VarId,
|
|
|
|
span: Span,
|
|
|
|
) -> Result<Value, ShellError> {
|
2022-01-29 14:00:48 +01:00
|
|
|
match var_id {
|
LazyRecord (#7619)
This is an attempt to implement a new `Value::LazyRecord` variant for
performance reasons.
`LazyRecord` is like a regular `Record`, but it's possible to access
individual columns without evaluating other columns. I've implemented
`LazyRecord` for the special `$nu` variable; accessing `$nu` is
relatively slow because of all the information in `scope`, and [`$nu`
accounts for about 2/3 of Nu's startup time on
Linux](https://github.com/nushell/nushell/issues/6677#issuecomment-1364618122).
### Benchmarks
I ran some benchmarks on my desktop (Linux, 12900K) and the results are
very pleasing.
Nu's time to start up and run a command (`cargo build --release;
hyperfine 'target/release/nu -c "echo \"Hello, world!\""' --shell=none
--warmup 10`) goes from **8.8ms to 3.2ms, about 2.8x faster**.
Tests are also much faster! Running `cargo nextest` (with our very slow
`proptest` tests disabled) goes from **7.2s to 4.4s (1.6x faster)**,
because most tests involve launching a new instance of Nu.
### Design (updated)
I've added a new `LazyRecord` trait and added a `Value` variant wrapping
those trait objects, much like `CustomValue`. `LazyRecord`
implementations must implement these 2 functions:
```rust
// All column names
fn column_names(&self) -> Vec<&'static str>;
// Get 1 specific column value
fn get_column_value(&self, column: &str) -> Result<Value, ShellError>;
```
### Serializability
`Value` variants must implement `Serializable` and `Deserializable`, which poses some problems because I want to use unserializable things like `EngineState` in `LazyRecord`s. To work around this, I basically lie to the type system:
1. Add `#[typetag::serde(tag = "type")]` to `LazyRecord` to make it serializable
2. Any unserializable fields in `LazyRecord` implementations get marked with `#[serde(skip)]`
3. At the point where a `LazyRecord` normally would get serialized and sent to a plugin, I instead collect it into a regular `Value::Record` (which can be serialized)
2023-01-19 04:27:26 +01:00
|
|
|
// $nu
|
|
|
|
nu_protocol::NU_VARIABLE_ID => Ok(Value::LazyRecord {
|
|
|
|
val: Box::new(NuVariable {
|
|
|
|
engine_state: engine_state.clone(),
|
|
|
|
stack: stack.clone(),
|
2022-04-19 21:11:58 +02:00
|
|
|
span,
|
LazyRecord (#7619)
This is an attempt to implement a new `Value::LazyRecord` variant for
performance reasons.
`LazyRecord` is like a regular `Record`, but it's possible to access
individual columns without evaluating other columns. I've implemented
`LazyRecord` for the special `$nu` variable; accessing `$nu` is
relatively slow because of all the information in `scope`, and [`$nu`
accounts for about 2/3 of Nu's startup time on
Linux](https://github.com/nushell/nushell/issues/6677#issuecomment-1364618122).
### Benchmarks
I ran some benchmarks on my desktop (Linux, 12900K) and the results are
very pleasing.
Nu's time to start up and run a command (`cargo build --release;
hyperfine 'target/release/nu -c "echo \"Hello, world!\""' --shell=none
--warmup 10`) goes from **8.8ms to 3.2ms, about 2.8x faster**.
Tests are also much faster! Running `cargo nextest` (with our very slow
`proptest` tests disabled) goes from **7.2s to 4.4s (1.6x faster)**,
because most tests involve launching a new instance of Nu.
### Design (updated)
I've added a new `LazyRecord` trait and added a `Value` variant wrapping
those trait objects, much like `CustomValue`. `LazyRecord`
implementations must implement these 2 functions:
```rust
// All column names
fn column_names(&self) -> Vec<&'static str>;
// Get 1 specific column value
fn get_column_value(&self, column: &str) -> Result<Value, ShellError>;
```
### Serializability
`Value` variants must implement `Serializable` and `Deserializable`, which poses some problems because I want to use unserializable things like `EngineState` in `LazyRecord`s. To work around this, I basically lie to the type system:
1. Add `#[typetag::serde(tag = "type")]` to `LazyRecord` to make it serializable
2. Any unserializable fields in `LazyRecord` implementations get marked with `#[serde(skip)]`
3. At the point where a `LazyRecord` normally would get serialized and sent to a plugin, I instead collect it into a regular `Value::Record` (which can be serialized)
2023-01-19 04:27:26 +01:00
|
|
|
}),
|
|
|
|
span,
|
|
|
|
}),
|
2022-01-29 14:00:48 +01:00
|
|
|
ENV_VARIABLE_ID => {
|
|
|
|
let env_vars = stack.get_env_vars(engine_state);
|
|
|
|
let env_columns = env_vars.keys();
|
|
|
|
let env_values = env_vars.values();
|
|
|
|
|
|
|
|
let mut pairs = env_columns
|
|
|
|
.map(|x| x.to_string())
|
|
|
|
.zip(env_values.cloned())
|
|
|
|
.collect::<Vec<(String, Value)>>();
|
|
|
|
|
|
|
|
pairs.sort_by(|a, b| a.0.cmp(&b.0));
|
|
|
|
|
|
|
|
let (env_columns, env_values) = pairs.into_iter().unzip();
|
|
|
|
|
|
|
|
Ok(Value::Record {
|
|
|
|
cols: env_columns,
|
|
|
|
vals: env_values,
|
|
|
|
span,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
var_id => stack.get_var(var_id, span),
|
2021-10-29 20:15:17 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-17 02:04:54 +01:00
|
|
|
fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
2023-05-12 19:18:11 +02:00
|
|
|
unit.to_value(size, span)
|
2021-10-05 04:27:39 +02:00
|
|
|
}
|
2023-02-24 23:29:07 +01:00
|
|
|
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
|
|
fn collect_profiling_metadata(
|
|
|
|
pipeline_idx: usize,
|
|
|
|
element_idx: usize,
|
|
|
|
element_str: String,
|
|
|
|
element_span: Span,
|
|
|
|
start_time: Instant,
|
|
|
|
end_time: Instant,
|
|
|
|
profiling_config: &ProfilingConfig,
|
|
|
|
eval_result: &Result<(PipelineData, bool), ShellError>,
|
|
|
|
input_metadata: &mut PipelineMetadata,
|
|
|
|
) {
|
|
|
|
let element_str = Value::string(element_str, element_span);
|
|
|
|
let time_ns = (end_time - start_time).as_nanos() as i64;
|
|
|
|
|
|
|
|
let mut cols = vec![
|
|
|
|
"pipeline_idx".to_string(),
|
|
|
|
"element_idx".to_string(),
|
|
|
|
"depth".to_string(),
|
|
|
|
"span".to_string(),
|
|
|
|
];
|
|
|
|
|
|
|
|
let mut vals = vec![
|
|
|
|
Value::int(pipeline_idx as i64, element_span),
|
|
|
|
Value::int(element_idx as i64, element_span),
|
|
|
|
Value::int(profiling_config.depth, element_span),
|
|
|
|
Value::record(
|
|
|
|
vec!["start".to_string(), "end".to_string()],
|
|
|
|
vec![
|
|
|
|
Value::int(element_span.start as i64, element_span),
|
|
|
|
Value::int(element_span.end as i64, element_span),
|
|
|
|
],
|
|
|
|
element_span,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
|
|
|
if profiling_config.collect_source {
|
|
|
|
cols.push("source".to_string());
|
|
|
|
vals.push(element_str);
|
|
|
|
}
|
|
|
|
|
|
|
|
if profiling_config.collect_values {
|
|
|
|
let value = match &eval_result {
|
|
|
|
Ok((PipelineData::Value(val, ..), ..)) => val.clone(),
|
|
|
|
Ok((PipelineData::ListStream(..), ..)) => Value::string("list stream", element_span),
|
|
|
|
Ok((PipelineData::ExternalStream { .. }, ..)) => {
|
|
|
|
Value::string("raw stream", element_span)
|
|
|
|
}
|
|
|
|
Ok((PipelineData::Empty, ..)) => Value::Nothing { span: element_span },
|
2023-03-12 09:57:27 +01:00
|
|
|
Err(err) => Value::Error {
|
|
|
|
error: Box::new(err.clone()),
|
|
|
|
},
|
2023-02-24 23:29:07 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
cols.push("value".to_string());
|
|
|
|
vals.push(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
cols.push("time".to_string());
|
|
|
|
vals.push(Value::Duration {
|
|
|
|
val: time_ns,
|
|
|
|
span: element_span,
|
|
|
|
});
|
|
|
|
|
|
|
|
let record = Value::Record {
|
|
|
|
cols,
|
|
|
|
vals,
|
|
|
|
span: element_span,
|
|
|
|
};
|
|
|
|
|
|
|
|
let element_metadata = if let Ok((pipeline_data, ..)) = &eval_result {
|
|
|
|
pipeline_data.metadata()
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
if let PipelineMetadata {
|
|
|
|
data_source: DataSource::Profiling(tgt_vals),
|
|
|
|
} = input_metadata
|
|
|
|
{
|
|
|
|
tgt_vals.push(record);
|
|
|
|
} else {
|
|
|
|
*input_metadata = PipelineMetadata {
|
|
|
|
data_source: DataSource::Profiling(vec![record]),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(PipelineMetadata {
|
|
|
|
data_source: DataSource::Profiling(element_vals),
|
|
|
|
}) = element_metadata.map(|m| *m)
|
|
|
|
{
|
|
|
|
if let PipelineMetadata {
|
|
|
|
data_source: DataSource::Profiling(tgt_vals),
|
|
|
|
} = input_metadata
|
|
|
|
{
|
|
|
|
tgt_vals.extend(element_vals);
|
|
|
|
} else {
|
|
|
|
*input_metadata = PipelineMetadata {
|
|
|
|
data_source: DataSource::Profiling(element_vals),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|