2022-12-10 17:23:24 +00:00
|
|
|
use log::trace;
|
2022-01-05 00:26:01 +00:00
|
|
|
use nu_path::canonicalize_with;
|
2021-09-26 18:39:19 +00:00
|
|
|
use nu_protocol::{
|
2021-11-15 23:16:06 +00:00
|
|
|
ast::{
|
2022-04-09 02:55:02 +00:00
|
|
|
Argument, Block, Call, Expr, Expression, ImportPattern, ImportPatternHead,
|
2022-11-18 21:46:48 +00:00
|
|
|
ImportPatternMember, PathMember, Pipeline, PipelineElement,
|
2021-11-15 23:16:06 +00:00
|
|
|
},
|
2022-05-07 19:39:22 +00:00
|
|
|
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
span, Alias, BlockId, Exportable, Module, PositionalArg, Span, Spanned, SyntaxShape, Type,
|
2021-09-26 18:39:19 +00:00
|
|
|
};
|
2022-01-14 21:06:32 +00:00
|
|
|
use std::collections::HashSet;
|
2022-03-12 20:12:15 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
|
|
|
static LIB_DIRS_ENV: &str = "NU_LIB_DIRS";
|
|
|
|
#[cfg(feature = "plugin")]
|
|
|
|
static PLUGIN_DIRS_ENV: &str = "NU_PLUGIN_DIRS";
|
2021-09-26 18:39:19 +00:00
|
|
|
|
|
|
|
use crate::{
|
2022-12-22 14:36:13 +00:00
|
|
|
eval::{eval_constant, value_as_string},
|
2022-02-11 18:38:10 +00:00
|
|
|
known_external::KnownExternal,
|
2022-11-18 21:46:48 +00:00
|
|
|
lex,
|
2022-12-22 11:41:44 +00:00
|
|
|
lite_parser::{lite_parse, LiteCommand, LiteElement},
|
2021-09-26 18:39:19 +00:00
|
|
|
parser::{
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
check_call, check_name, garbage, garbage_pipeline, parse, parse_call, parse_import_pattern,
|
2022-12-22 14:36:13 +00:00
|
|
|
parse_internal_call, parse_multispan_value, parse_signature, parse_string, parse_value,
|
|
|
|
parse_var_with_opt_type, trim_quotes, ParsedInternalCall,
|
2021-09-26 18:39:19 +00:00
|
|
|
},
|
2022-12-30 15:44:37 +00:00
|
|
|
unescape_unquote_string, ParseError, Token, TokenContents,
|
2021-09-26 18:39:19 +00:00
|
|
|
};
|
|
|
|
|
2023-03-10 21:20:31 +00:00
|
|
|
/// These parser keywords can be aliased
|
|
|
|
pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[b"overlay hide", b"overlay new", b"overlay use"];
|
|
|
|
|
|
|
|
/// These parser keywords cannot be aliased (either not possible, or support not yet added)
|
|
|
|
pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
|
|
|
|
b"export",
|
|
|
|
b"def",
|
|
|
|
b"export def",
|
|
|
|
b"for",
|
|
|
|
b"extern",
|
|
|
|
b"export extern",
|
|
|
|
b"alias",
|
|
|
|
b"export alias",
|
|
|
|
b"export-env",
|
|
|
|
b"module",
|
|
|
|
b"use",
|
|
|
|
b"export use",
|
|
|
|
b"hide",
|
|
|
|
// b"overlay",
|
|
|
|
// b"overlay hide",
|
|
|
|
// b"overlay new",
|
|
|
|
// b"overlay use",
|
|
|
|
b"let",
|
|
|
|
b"const",
|
|
|
|
b"mut",
|
|
|
|
b"source",
|
|
|
|
b"where",
|
|
|
|
b"register",
|
|
|
|
];
|
|
|
|
|
|
|
|
/// Check whether spans start with a parser keyword that can be aliased
|
|
|
|
pub fn is_unaliasable_parser_keyword(working_set: &StateWorkingSet, spans: &[Span]) -> bool {
|
|
|
|
// try two words
|
|
|
|
if let (Some(span1), Some(span2)) = (spans.get(0), spans.get(1)) {
|
|
|
|
let cmd_name = working_set.get_span_contents(span(&[*span1, *span2]));
|
|
|
|
return UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// try one word
|
|
|
|
if let Some(span1) = spans.get(0) {
|
|
|
|
let cmd_name = working_set.get_span_contents(*span1);
|
|
|
|
UNALIASABLE_PARSER_KEYWORDS.contains(&cmd_name)
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This is a new more compact method of calling parse_xxx() functions without repeating the
|
|
|
|
/// parse_call() in each function. Remaining keywords can be moved here.
|
|
|
|
pub fn parse_keyword(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
lite_command: &LiteCommand,
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
is_subexpression: bool,
|
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
|
|
|
let (call_expr, err) = parse_call(
|
|
|
|
working_set,
|
|
|
|
&lite_command.parts,
|
|
|
|
lite_command.parts[0],
|
|
|
|
expand_aliases_denylist,
|
|
|
|
is_subexpression,
|
|
|
|
);
|
|
|
|
|
|
|
|
if err.is_some() {
|
|
|
|
return (Pipeline::from_vec(vec![call_expr]), err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
..
|
|
|
|
} = call_expr.clone()
|
|
|
|
{
|
|
|
|
// Apply parse keyword side effects
|
|
|
|
let cmd = working_set.get_decl(call.decl_id);
|
|
|
|
|
|
|
|
match cmd.name() {
|
|
|
|
"overlay hide" => parse_overlay_hide(working_set, call),
|
|
|
|
"overlay new" => parse_overlay_new(working_set, call),
|
|
|
|
"overlay use" => parse_overlay_use(working_set, call, expand_aliases_denylist),
|
|
|
|
_ => (Pipeline::from_vec(vec![call_expr]), err),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
(Pipeline::from_vec(vec![call_expr]), err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 19:03:57 +00:00
|
|
|
pub fn parse_def_predecl(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> Option<ParseError> {
|
2021-09-26 18:39:19 +00:00
|
|
|
let name = working_set.get_span_contents(spans[0]);
|
|
|
|
|
2021-09-28 17:29:38 +00:00
|
|
|
// handle "export def" same as "def"
|
|
|
|
let (name, spans) = if name == b"export" && spans.len() >= 2 {
|
|
|
|
(working_set.get_span_contents(spans[1]), &spans[1..])
|
|
|
|
} else {
|
|
|
|
(name, spans)
|
|
|
|
};
|
|
|
|
|
2022-01-29 20:45:46 +00:00
|
|
|
if (name == b"def" || name == b"def-env") && spans.len() >= 4 {
|
2022-04-25 23:44:44 +00:00
|
|
|
let (name_expr, ..) = parse_string(working_set, spans[1], expand_aliases_denylist);
|
2021-09-26 18:39:19 +00:00
|
|
|
let name = name_expr.as_string();
|
|
|
|
|
|
|
|
working_set.enter_scope();
|
|
|
|
// FIXME: because parse_signature will update the scope with the variables it sees
|
|
|
|
// we end up parsing the signature twice per def. The first time is during the predecl
|
|
|
|
// so that we can see the types that are part of the signature, which we need for parsing.
|
|
|
|
// The second time is when we actually parse the body itworking_set.
|
|
|
|
// We can't reuse the first time because the variables that are created during parse_signature
|
|
|
|
// are lost when we exit the scope below.
|
2022-03-18 19:03:57 +00:00
|
|
|
let (sig, ..) = parse_signature(working_set, spans[2], expand_aliases_denylist);
|
2021-09-26 18:39:19 +00:00
|
|
|
let signature = sig.as_signature();
|
|
|
|
working_set.exit_scope();
|
|
|
|
if let (Some(name), Some(mut signature)) = (name, signature) {
|
2022-12-22 20:31:34 +00:00
|
|
|
if name.contains('#')
|
2022-12-27 23:00:44 +00:00
|
|
|
|| name.contains('^')
|
2022-12-22 20:31:34 +00:00
|
|
|
|| name.parse::<bytesize::ByteSize>().is_ok()
|
|
|
|
|| name.parse::<f64>().is_ok()
|
|
|
|
{
|
|
|
|
return Some(ParseError::CommandDefNotValid(spans[1]));
|
|
|
|
}
|
|
|
|
|
2021-09-26 18:39:19 +00:00
|
|
|
signature.name = name;
|
|
|
|
let decl = signature.predeclare();
|
|
|
|
|
2021-10-01 20:16:27 +00:00
|
|
|
if working_set.add_predecl(decl).is_some() {
|
|
|
|
return Some(ParseError::DuplicateCommandDef(spans[1]));
|
|
|
|
}
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
2022-02-11 18:38:10 +00:00
|
|
|
} else if name == b"extern" && spans.len() == 3 {
|
2022-04-25 23:44:44 +00:00
|
|
|
let (name_expr, ..) = parse_string(working_set, spans[1], expand_aliases_denylist);
|
2022-02-11 18:38:10 +00:00
|
|
|
let name = name_expr.as_string();
|
|
|
|
|
|
|
|
working_set.enter_scope();
|
|
|
|
// FIXME: because parse_signature will update the scope with the variables it sees
|
|
|
|
// we end up parsing the signature twice per def. The first time is during the predecl
|
|
|
|
// so that we can see the types that are part of the signature, which we need for parsing.
|
|
|
|
// The second time is when we actually parse the body itworking_set.
|
|
|
|
// We can't reuse the first time because the variables that are created during parse_signature
|
|
|
|
// are lost when we exit the scope below.
|
2022-03-18 19:03:57 +00:00
|
|
|
let (sig, ..) = parse_signature(working_set, spans[2], expand_aliases_denylist);
|
2022-02-11 18:38:10 +00:00
|
|
|
let signature = sig.as_signature();
|
|
|
|
working_set.exit_scope();
|
|
|
|
|
|
|
|
if let (Some(name), Some(mut signature)) = (name, signature) {
|
2022-12-22 20:31:34 +00:00
|
|
|
if name.contains('#')
|
|
|
|
|| name.parse::<bytesize::ByteSize>().is_ok()
|
|
|
|
|| name.parse::<f64>().is_ok()
|
|
|
|
{
|
|
|
|
return Some(ParseError::CommandDefNotValid(spans[1]));
|
|
|
|
}
|
|
|
|
|
2022-02-11 18:38:10 +00:00
|
|
|
signature.name = name.clone();
|
|
|
|
//let decl = signature.predeclare();
|
|
|
|
let decl = KnownExternal {
|
|
|
|
name,
|
|
|
|
usage: "run external command".into(),
|
|
|
|
signature,
|
|
|
|
};
|
|
|
|
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
if working_set.add_predecl(Box::new(decl)).is_some() {
|
|
|
|
return Some(ParseError::DuplicateCommandDef(spans[1]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if name == b"alias" && spans.len() >= 4 {
|
|
|
|
let (name_expr, ..) = parse_string(working_set, spans[1], expand_aliases_denylist);
|
|
|
|
let name = name_expr.as_string();
|
|
|
|
|
|
|
|
if let Some(name) = name {
|
|
|
|
if name.contains('#')
|
|
|
|
|| name.contains('^')
|
|
|
|
|| name.parse::<bytesize::ByteSize>().is_ok()
|
|
|
|
|| name.parse::<f64>().is_ok()
|
|
|
|
{
|
|
|
|
return Some(ParseError::CommandDefNotValid(spans[1]));
|
|
|
|
}
|
|
|
|
|
|
|
|
// The signature will get replaced by the replacement signature
|
|
|
|
// let mut signature = Signature::new(name.clone());
|
|
|
|
// signature.name = name;
|
|
|
|
|
|
|
|
// The fields get replaced during parsing
|
|
|
|
let decl = Alias {
|
|
|
|
name,
|
|
|
|
command: None,
|
|
|
|
wrapped_call: Expression::garbage(name_expr.span),
|
|
|
|
};
|
|
|
|
|
2022-02-11 18:38:10 +00:00
|
|
|
if working_set.add_predecl(Box::new(decl)).is_some() {
|
|
|
|
return Some(ParseError::DuplicateCommandDef(spans[1]));
|
|
|
|
}
|
|
|
|
}
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
2021-10-01 20:16:27 +00:00
|
|
|
|
|
|
|
None
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2022-01-12 04:06:56 +00:00
|
|
|
pub fn parse_for(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-01-15 15:26:52 +00:00
|
|
|
) -> (Expression, Option<ParseError>) {
|
2022-01-12 04:06:56 +00:00
|
|
|
// Checking that the function is used with the correct name
|
|
|
|
// Maybe this is not necessary but it is a sanity check
|
|
|
|
if working_set.get_span_contents(spans[0]) != b"for" {
|
|
|
|
return (
|
2022-01-15 15:26:52 +00:00
|
|
|
garbage(spans[0]),
|
2022-01-12 04:06:56 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for 'for' function".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parsing the spans and checking that they match the register signature
|
|
|
|
// Using a parsed call makes more sense than checking for how many spans are in the call
|
|
|
|
// Also, by creating a call, it can be checked if it matches the declaration signature
|
2022-06-10 15:59:35 +00:00
|
|
|
let (call, call_span) = match working_set.find_decl(b"for", &Type::Any) {
|
2022-01-12 04:06:56 +00:00
|
|
|
None => {
|
|
|
|
return (
|
2022-01-15 15:26:52 +00:00
|
|
|
garbage(spans[0]),
|
2022-01-12 04:06:56 +00:00
|
|
|
Some(ParseError::UnknownState(
|
2022-02-11 18:38:10 +00:00
|
|
|
"internal error: for declaration not found".into(),
|
2022-01-12 04:06:56 +00:00
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Some(decl_id) => {
|
|
|
|
working_set.enter_scope();
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&spans[1..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-06-12 19:18:00 +00:00
|
|
|
|
2022-01-12 04:06:56 +00:00
|
|
|
working_set.exit_scope();
|
|
|
|
|
|
|
|
let call_span = span(spans);
|
|
|
|
let decl = working_set.get_decl(decl_id);
|
|
|
|
let sig = decl.signature();
|
|
|
|
|
|
|
|
// Let's get our block and make sure it has the right signature
|
2022-04-09 02:55:02 +00:00
|
|
|
if let Some(arg) = call.positional_nth(2) {
|
2022-01-12 04:06:56 +00:00
|
|
|
match arg {
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Block(block_id),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Expression {
|
|
|
|
expr: Expr::RowCondition(block_id),
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
let block = working_set.get_block_mut(*block_id);
|
|
|
|
|
|
|
|
block.signature = Box::new(sig.clone());
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = check_call(call_span, &sig, &call).or(err);
|
|
|
|
if err.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
2022-01-15 15:26:52 +00:00
|
|
|
Expression {
|
2022-01-12 04:06:56 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-06-12 19:18:00 +00:00
|
|
|
ty: output,
|
2022-01-12 04:06:56 +00:00
|
|
|
custom_completion: None,
|
2022-01-15 15:26:52 +00:00
|
|
|
},
|
2022-01-12 04:06:56 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
(call, call_span)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// All positional arguments must be in the call positional vector by this point
|
2022-04-09 02:55:02 +00:00
|
|
|
let var_decl = call.positional_nth(0).expect("for call already checked");
|
|
|
|
let block = call.positional_nth(2).expect("for call already checked");
|
2022-01-12 04:06:56 +00:00
|
|
|
|
|
|
|
let error = None;
|
|
|
|
if let (Some(var_id), Some(block_id)) = (&var_decl.as_var(), block.as_block()) {
|
|
|
|
let block = working_set.get_block_mut(block_id);
|
|
|
|
|
|
|
|
block.signature.required_positional.insert(
|
|
|
|
0,
|
|
|
|
PositionalArg {
|
|
|
|
name: String::new(),
|
|
|
|
desc: String::new(),
|
|
|
|
shape: SyntaxShape::Any,
|
|
|
|
var_id: Some(*var_id),
|
2022-03-07 20:08:56 +00:00
|
|
|
default_value: None,
|
2022-01-12 04:06:56 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
2022-01-15 15:26:52 +00:00
|
|
|
Expression {
|
2022-01-12 04:06:56 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2022-01-12 04:06:56 +00:00
|
|
|
custom_completion: None,
|
2022-01-15 15:26:52 +00:00
|
|
|
},
|
2022-01-12 04:06:56 +00:00
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-09-26 18:39:19 +00:00
|
|
|
pub fn parse_def(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2022-01-22 18:24:47 +00:00
|
|
|
lite_command: &LiteCommand,
|
2023-01-22 19:34:15 +00:00
|
|
|
module_name: Option<&[u8]>,
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2022-01-22 18:24:47 +00:00
|
|
|
let spans = &lite_command.parts[..];
|
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
let (usage, extra_usage) = working_set.build_usage(&lite_command.comments);
|
2022-01-22 18:24:47 +00:00
|
|
|
|
2021-12-27 19:13:52 +00:00
|
|
|
// Checking that the function is used with the correct name
|
|
|
|
// Maybe this is not necessary but it is a sanity check
|
2022-08-22 21:19:47 +00:00
|
|
|
// Note: "export def" is treated the same as "def"
|
2022-01-29 20:45:46 +00:00
|
|
|
|
2022-08-22 21:19:47 +00:00
|
|
|
let (name_span, split_id) =
|
|
|
|
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
|
|
|
|
(spans[1], 2)
|
|
|
|
} else {
|
|
|
|
(spans[0], 1)
|
|
|
|
};
|
|
|
|
|
|
|
|
let def_call = working_set.get_span_contents(name_span).to_vec();
|
2022-01-29 20:45:46 +00:00
|
|
|
if def_call != b"def" && def_call != b"def-env" {
|
2021-12-27 19:13:52 +00:00
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-12-27 19:13:52 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for def function".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
2021-09-27 01:03:50 +00:00
|
|
|
|
2021-12-27 19:13:52 +00:00
|
|
|
// Parsing the spans and checking that they match the register signature
|
|
|
|
// Using a parsed call makes more sense than checking for how many spans are in the call
|
|
|
|
// Also, by creating a call, it can be checked if it matches the declaration signature
|
2022-06-10 15:59:35 +00:00
|
|
|
let (call, call_span) = match working_set.find_decl(&def_call, &Type::Any) {
|
2021-12-27 19:13:52 +00:00
|
|
|
None => {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-12-27 19:13:52 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: def declaration not found".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Some(decl_id) => {
|
|
|
|
working_set.enter_scope();
|
2022-08-22 21:19:47 +00:00
|
|
|
let (command_spans, rest_spans) = spans.split_at(split_id);
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
2022-08-22 21:19:47 +00:00
|
|
|
span(command_spans),
|
|
|
|
rest_spans,
|
2022-03-18 19:03:57 +00:00
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-06-12 19:18:00 +00:00
|
|
|
|
2021-12-27 19:13:52 +00:00
|
|
|
working_set.exit_scope();
|
2021-09-27 01:03:50 +00:00
|
|
|
|
2021-12-27 19:13:52 +00:00
|
|
|
let call_span = span(spans);
|
|
|
|
let decl = working_set.get_decl(decl_id);
|
2022-01-12 04:06:56 +00:00
|
|
|
let sig = decl.signature();
|
|
|
|
|
|
|
|
// Let's get our block and make sure it has the right signature
|
2022-04-09 02:55:02 +00:00
|
|
|
if let Some(arg) = call.positional_nth(2) {
|
2022-01-12 04:06:56 +00:00
|
|
|
match arg {
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Block(block_id),
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| Expression {
|
|
|
|
expr: Expr::RowCondition(block_id),
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
let block = working_set.get_block_mut(*block_id);
|
|
|
|
|
|
|
|
block.signature = Box::new(sig.clone());
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
2021-09-27 01:03:50 +00:00
|
|
|
|
2022-01-12 04:06:56 +00:00
|
|
|
err = check_call(call_span, &sig, &call).or(err);
|
2021-12-27 19:13:52 +00:00
|
|
|
if err.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-12-27 19:13:52 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-06-12 19:18:00 +00:00
|
|
|
ty: output,
|
2021-12-27 19:13:52 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-12-27 19:13:52 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
2021-09-27 01:03:50 +00:00
|
|
|
|
2021-12-27 19:13:52 +00:00
|
|
|
(call, call_span)
|
|
|
|
}
|
|
|
|
};
|
2021-09-27 01:03:50 +00:00
|
|
|
|
2021-12-27 19:13:52 +00:00
|
|
|
// All positional arguments must be in the call positional vector by this point
|
2022-04-09 02:55:02 +00:00
|
|
|
let name_expr = call.positional_nth(0).expect("def call already checked");
|
|
|
|
let sig = call.positional_nth(1).expect("def call already checked");
|
|
|
|
let block = call.positional_nth(2).expect("def call already checked");
|
2021-09-27 01:03:50 +00:00
|
|
|
|
2021-12-27 19:13:52 +00:00
|
|
|
let mut error = None;
|
2022-02-21 21:42:31 +00:00
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
let name = if let Some(name) = name_expr.as_string() {
|
|
|
|
if let Some(mod_name) = module_name {
|
|
|
|
if name.as_bytes() == mod_name {
|
|
|
|
let name_expr_span = name_expr.span;
|
|
|
|
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
Some(ParseError::NamedAsModule(
|
|
|
|
"command".to_string(),
|
|
|
|
name,
|
|
|
|
name_expr_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
name
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"Could not get string from string expression".into(),
|
|
|
|
name_expr.span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
if let (Some(mut signature), Some(block_id)) = (sig.as_signature(), block.as_block()) {
|
2022-02-21 21:05:20 +00:00
|
|
|
if let Some(decl_id) = working_set.find_predecl(name.as_bytes()) {
|
2021-12-27 19:13:52 +00:00
|
|
|
let declaration = working_set.get_decl_mut(decl_id);
|
|
|
|
|
|
|
|
signature.name = name.clone();
|
2022-05-29 13:14:15 +00:00
|
|
|
*signature = signature.add_help();
|
2022-01-22 18:24:47 +00:00
|
|
|
signature.usage = usage;
|
2022-12-30 15:44:37 +00:00
|
|
|
signature.extra_usage = extra_usage;
|
2022-01-12 04:06:56 +00:00
|
|
|
|
|
|
|
*declaration = signature.clone().into_block_command(block_id);
|
|
|
|
|
|
|
|
let mut block = working_set.get_block_mut(block_id);
|
2023-01-05 02:38:50 +00:00
|
|
|
let calls_itself = block.pipelines.iter().any(|pipeline| {
|
|
|
|
pipeline
|
|
|
|
.elements
|
|
|
|
.iter()
|
|
|
|
.any(|pipe_element| match pipe_element {
|
|
|
|
PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call_expr),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
) => {
|
|
|
|
if call_expr.decl_id == decl_id {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
call_expr.arguments.iter().any(|arg| match arg {
|
|
|
|
Argument::Positional(Expression { expr, .. }) => match expr {
|
|
|
|
Expr::Keyword(.., expr) => {
|
|
|
|
let expr = expr.as_ref();
|
|
|
|
let Expression { expr, .. } = expr;
|
|
|
|
match expr {
|
|
|
|
Expr::Call(call_expr2) => call_expr2.decl_id == decl_id,
|
|
|
|
_ => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Expr::Call(call_expr2) => call_expr2.decl_id == decl_id,
|
|
|
|
_ => false,
|
|
|
|
},
|
|
|
|
_ => false,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
_ => false,
|
|
|
|
})
|
|
|
|
});
|
|
|
|
block.recursive = Some(calls_itself);
|
2022-01-12 04:06:56 +00:00
|
|
|
block.signature = signature;
|
2022-01-29 20:45:46 +00:00
|
|
|
block.redirect_env = def_call == b"def-env";
|
2021-09-27 01:03:50 +00:00
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
2021-12-27 19:13:52 +00:00
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"Predeclaration failed to add declaration".into(),
|
2022-08-22 21:19:47 +00:00
|
|
|
name_expr.span,
|
2021-09-27 01:03:50 +00:00
|
|
|
))
|
|
|
|
});
|
2021-12-27 19:13:52 +00:00
|
|
|
};
|
|
|
|
}
|
2021-09-27 01:03:50 +00:00
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
// It's OK if it returns None: The decl was already merged in previous parse pass.
|
|
|
|
working_set.merge_predecl(name.as_bytes());
|
2021-12-27 19:13:52 +00:00
|
|
|
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-12-27 19:13:52 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-12-27 19:13:52 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-12-27 19:13:52 +00:00
|
|
|
error,
|
|
|
|
)
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2022-02-11 18:38:10 +00:00
|
|
|
pub fn parse_extern(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
lite_command: &LiteCommand,
|
2023-01-22 19:34:15 +00:00
|
|
|
module_name: Option<&[u8]>,
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2022-08-22 21:19:47 +00:00
|
|
|
let spans = &lite_command.parts;
|
2022-02-11 18:38:10 +00:00
|
|
|
let mut error = None;
|
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
let (usage, extra_usage) = working_set.build_usage(&lite_command.comments);
|
2022-02-11 18:38:10 +00:00
|
|
|
|
|
|
|
// Checking that the function is used with the correct name
|
|
|
|
// Maybe this is not necessary but it is a sanity check
|
|
|
|
|
2022-08-22 21:19:47 +00:00
|
|
|
let (name_span, split_id) =
|
|
|
|
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
|
|
|
|
(spans[1], 2)
|
|
|
|
} else {
|
|
|
|
(spans[0], 1)
|
|
|
|
};
|
|
|
|
|
|
|
|
let extern_call = working_set.get_span_contents(name_span).to_vec();
|
2022-02-11 18:38:10 +00:00
|
|
|
if extern_call != b"extern" {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-02-11 18:38:10 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for extern function".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parsing the spans and checking that they match the register signature
|
|
|
|
// Using a parsed call makes more sense than checking for how many spans are in the call
|
|
|
|
// Also, by creating a call, it can be checked if it matches the declaration signature
|
2022-06-10 15:59:35 +00:00
|
|
|
let (call, call_span) = match working_set.find_decl(&extern_call, &Type::Any) {
|
2022-02-11 18:38:10 +00:00
|
|
|
None => {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-02-11 18:38:10 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: def declaration not found".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
Some(decl_id) => {
|
|
|
|
working_set.enter_scope();
|
2022-08-22 21:19:47 +00:00
|
|
|
|
|
|
|
let (command_spans, rest_spans) = spans.split_at(split_id);
|
|
|
|
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call, error: err, ..
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
2022-08-22 21:19:47 +00:00
|
|
|
span(command_spans),
|
|
|
|
rest_spans,
|
2022-03-18 19:03:57 +00:00
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-02-11 18:38:10 +00:00
|
|
|
working_set.exit_scope();
|
|
|
|
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let call_span = span(spans);
|
|
|
|
//let decl = working_set.get_decl(decl_id);
|
|
|
|
//let sig = decl.signature();
|
|
|
|
|
|
|
|
(call, call_span)
|
|
|
|
}
|
|
|
|
};
|
2022-04-09 02:55:02 +00:00
|
|
|
let name_expr = call.positional_nth(0);
|
|
|
|
let sig = call.positional_nth(1);
|
2022-02-11 18:38:10 +00:00
|
|
|
|
|
|
|
if let (Some(name_expr), Some(sig)) = (name_expr, sig) {
|
|
|
|
if let (Some(name), Some(mut signature)) = (&name_expr.as_string(), sig.as_signature()) {
|
2023-01-22 19:34:15 +00:00
|
|
|
if let Some(mod_name) = module_name {
|
|
|
|
if name.as_bytes() == mod_name {
|
|
|
|
let name_expr_span = name_expr.span;
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
Some(ParseError::NamedAsModule(
|
|
|
|
"known external".to_string(),
|
|
|
|
name.clone(),
|
|
|
|
name_expr_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-21 21:05:20 +00:00
|
|
|
if let Some(decl_id) = working_set.find_predecl(name.as_bytes()) {
|
2022-02-11 18:38:10 +00:00
|
|
|
let declaration = working_set.get_decl_mut(decl_id);
|
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
let external_name = if let Some(mod_name) = module_name {
|
|
|
|
if name.as_bytes() == b"main" {
|
|
|
|
String::from_utf8_lossy(mod_name).to_string()
|
|
|
|
} else {
|
|
|
|
name.clone()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
name.clone()
|
|
|
|
};
|
|
|
|
|
|
|
|
signature.name = external_name.clone();
|
2022-02-11 18:38:10 +00:00
|
|
|
signature.usage = usage.clone();
|
2022-12-30 15:44:37 +00:00
|
|
|
signature.extra_usage = extra_usage.clone();
|
2022-12-21 22:33:26 +00:00
|
|
|
signature.allows_unknown_args = true;
|
2022-02-11 18:38:10 +00:00
|
|
|
|
|
|
|
let decl = KnownExternal {
|
2023-01-22 19:34:15 +00:00
|
|
|
name: external_name,
|
2022-12-30 15:44:37 +00:00
|
|
|
usage: [usage, extra_usage].join("\n"),
|
2022-02-11 18:38:10 +00:00
|
|
|
signature,
|
|
|
|
};
|
|
|
|
|
|
|
|
*declaration = Box::new(decl);
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"Predeclaration failed to add declaration".into(),
|
2022-08-22 21:19:47 +00:00
|
|
|
spans[split_id],
|
2022-02-11 18:38:10 +00:00
|
|
|
))
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if let Some(name) = name_expr.as_string() {
|
|
|
|
// It's OK if it returns None: The decl was already merged in previous parse pass.
|
|
|
|
working_set.merge_predecl(name.as_bytes());
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"Could not get string from string expression".into(),
|
|
|
|
name_expr.span,
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2022-02-11 18:38:10 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2022-02-11 18:38:10 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2022-02-11 18:38:10 +00:00
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-09-26 18:39:19 +00:00
|
|
|
pub fn parse_alias(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2022-12-30 15:44:37 +00:00
|
|
|
lite_command: &LiteCommand,
|
2023-01-22 19:34:15 +00:00
|
|
|
module_name: Option<&[u8]>,
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2022-12-30 15:44:37 +00:00
|
|
|
let spans = &lite_command.parts;
|
|
|
|
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
let (name_span, split_id) =
|
|
|
|
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
|
|
|
|
(spans[1], 2)
|
|
|
|
} else {
|
|
|
|
(spans[0], 1)
|
|
|
|
};
|
|
|
|
|
|
|
|
let name = working_set.get_span_contents(name_span);
|
|
|
|
|
|
|
|
if name != b"alias" {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"Alias statement unparsable".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some((span, err)) = check_name(working_set, spans) {
|
|
|
|
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) {
|
|
|
|
let (command_spans, rest_spans) = spans.split_at(split_id);
|
|
|
|
|
2023-03-10 21:20:31 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call: alias_call,
|
|
|
|
output,
|
|
|
|
..
|
|
|
|
} = parse_internal_call(
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
working_set,
|
|
|
|
span(command_spans),
|
|
|
|
rest_spans,
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
2023-03-10 21:20:31 +00:00
|
|
|
let has_help_flag = alias_call.has_flag("help");
|
|
|
|
|
|
|
|
let alias_pipeline = Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(alias_call),
|
|
|
|
span: span(spans),
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
}]);
|
|
|
|
|
|
|
|
if has_help_flag {
|
|
|
|
return (alias_pipeline, None);
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if spans.len() >= split_id + 3 {
|
|
|
|
let alias_name = working_set.get_span_contents(spans[split_id]);
|
|
|
|
|
|
|
|
let alias_name = if alias_name.starts_with(b"\"")
|
|
|
|
&& alias_name.ends_with(b"\"")
|
|
|
|
&& alias_name.len() > 1
|
|
|
|
{
|
|
|
|
alias_name[1..(alias_name.len() - 1)].to_vec()
|
|
|
|
} else {
|
|
|
|
alias_name.to_vec()
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(mod_name) = module_name {
|
|
|
|
if alias_name == mod_name {
|
|
|
|
return (
|
2023-03-10 21:20:31 +00:00
|
|
|
alias_pipeline,
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
Some(ParseError::NamedAsModule(
|
|
|
|
"alias".to_string(),
|
|
|
|
String::from_utf8_lossy(&alias_name).to_string(),
|
|
|
|
spans[split_id],
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if &alias_name == b"main" {
|
|
|
|
return (
|
2023-03-10 21:20:31 +00:00
|
|
|
alias_pipeline,
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
Some(ParseError::ExportMainAliasNotAllowed(spans[split_id])),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let _equals = working_set.get_span_contents(spans[split_id + 1]);
|
|
|
|
|
|
|
|
let replacement_spans = &spans[(split_id + 2)..];
|
|
|
|
|
|
|
|
let (expr, err) = parse_call(
|
|
|
|
working_set,
|
|
|
|
replacement_spans,
|
|
|
|
replacement_spans[0],
|
|
|
|
expand_aliases_denylist,
|
|
|
|
false, // TODO: Should this be set properly???
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Some(e) = err {
|
|
|
|
if let ParseError::MissingPositional(..) = e {
|
|
|
|
// ignore missing required positional
|
|
|
|
} else {
|
|
|
|
return (garbage_pipeline(replacement_spans), Some(e));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let (command, wrapped_call) = match expr {
|
|
|
|
Expression {
|
2023-03-10 21:20:31 +00:00
|
|
|
expr: Expr::Call(ref rhs_call),
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
..
|
2023-03-10 21:20:31 +00:00
|
|
|
} => {
|
|
|
|
let cmd = working_set.get_decl(rhs_call.decl_id);
|
|
|
|
|
|
|
|
if cmd.is_parser_keyword()
|
|
|
|
&& !ALIASABLE_PARSER_KEYWORDS.contains(&cmd.name().as_bytes())
|
|
|
|
{
|
|
|
|
return (
|
|
|
|
alias_pipeline,
|
|
|
|
Some(ParseError::CantAliasKeyword(
|
|
|
|
ALIASABLE_PARSER_KEYWORDS
|
|
|
|
.iter()
|
|
|
|
.map(|bytes| String::from_utf8_lossy(bytes).to_string())
|
|
|
|
.collect::<Vec<String>>()
|
|
|
|
.join(", "),
|
|
|
|
rhs_call.head,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
(Some(cmd.clone_box()), expr)
|
|
|
|
}
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
Expression {
|
|
|
|
expr: Expr::ExternalCall(..),
|
|
|
|
..
|
|
|
|
} => (None, expr),
|
|
|
|
_ => {
|
|
|
|
return (
|
2023-03-10 21:20:31 +00:00
|
|
|
alias_pipeline,
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"Parsed call not a call".into(),
|
|
|
|
expr.span,
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_predecl(&alias_name) {
|
|
|
|
let alias_decl = working_set.get_decl_mut(decl_id);
|
|
|
|
|
|
|
|
let alias = Alias {
|
|
|
|
name: String::from_utf8_lossy(&alias_name).to_string(),
|
|
|
|
command,
|
|
|
|
wrapped_call,
|
|
|
|
};
|
|
|
|
|
|
|
|
*alias_decl = Box::new(alias);
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"Predeclaration failed to add declaration".into(),
|
|
|
|
spans[split_id],
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's OK if it returns None: The decl was already merged in previous parse pass.
|
|
|
|
working_set.merge_predecl(&alias_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
let err = if spans.len() < 4 {
|
|
|
|
Some(ParseError::IncorrectValue(
|
|
|
|
"Incomplete alias".into(),
|
|
|
|
span(&spans[..split_id]),
|
|
|
|
"incomplete alias".into(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2023-03-10 21:20:31 +00:00
|
|
|
return (alias_pipeline, err);
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
(
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"Alias statement unparsable".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_old_alias(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
lite_command: &LiteCommand,
|
|
|
|
module_name: Option<&[u8]>,
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
|
|
|
let spans = &lite_command.parts;
|
|
|
|
|
2022-10-14 19:51:44 +00:00
|
|
|
// if the call is "alias", turn it into "print $nu.scope.aliases"
|
|
|
|
if spans.len() == 1 {
|
|
|
|
let head = Expression {
|
|
|
|
expr: Expr::Var(nu_protocol::NU_VARIABLE_ID),
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
};
|
|
|
|
let tail = vec![
|
|
|
|
PathMember::String {
|
|
|
|
val: "scope".to_string(),
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
},
|
|
|
|
PathMember::String {
|
|
|
|
val: "aliases".to_string(),
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
let expr = Expression {
|
|
|
|
ty: Type::Any,
|
|
|
|
expr: Expr::FullCellPath(Box::new(nu_protocol::ast::FullCellPath { head, tail })),
|
|
|
|
span: Span::new(0, 0),
|
|
|
|
custom_completion: None,
|
|
|
|
};
|
|
|
|
if let Some(decl_id) = working_set.find_decl(b"print", &Type::Any) {
|
|
|
|
let print_call = Expr::Call(Box::new(Call {
|
|
|
|
head: spans[0],
|
|
|
|
arguments: vec![Argument::Positional(expr)],
|
|
|
|
decl_id,
|
|
|
|
redirect_stdout: true,
|
|
|
|
redirect_stderr: false,
|
2022-12-22 14:36:13 +00:00
|
|
|
parser_info: vec![],
|
2022-10-14 19:51:44 +00:00
|
|
|
}));
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: print_call,
|
|
|
|
span: spans[0],
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return (Pipeline::from_vec(vec![expr]), None);
|
|
|
|
}
|
|
|
|
|
2022-12-22 20:31:34 +00:00
|
|
|
let (name_span, split_id) =
|
2022-08-22 21:19:47 +00:00
|
|
|
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
|
2022-12-22 20:31:34 +00:00
|
|
|
(spans[1], 2)
|
2022-08-22 21:19:47 +00:00
|
|
|
} else {
|
2022-12-22 20:31:34 +00:00
|
|
|
(spans[0], 1)
|
2022-08-22 21:19:47 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
let name = working_set.get_span_contents(name_span);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
if name == b"old-alias" {
|
2021-09-26 18:39:19 +00:00
|
|
|
if let Some((span, err)) = check_name(working_set, spans) {
|
2022-02-15 19:31:14 +00:00
|
|
|
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2022-06-10 15:59:35 +00:00
|
|
|
if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) {
|
2022-08-22 21:19:47 +00:00
|
|
|
let (command_spans, rest_spans) = spans.split_at(split_id);
|
|
|
|
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall { call, output, .. } = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
2022-08-22 21:19:47 +00:00
|
|
|
span(command_spans),
|
|
|
|
rest_spans,
|
2022-03-18 19:03:57 +00:00
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-04-26 16:51:49 +00:00
|
|
|
if call.has_flag("help") {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
2022-06-12 19:18:00 +00:00
|
|
|
ty: output,
|
2022-04-26 16:51:49 +00:00
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
None,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-22 21:19:47 +00:00
|
|
|
if spans.len() >= split_id + 3 {
|
|
|
|
let alias_name = working_set.get_span_contents(spans[split_id]);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
|
|
|
let alias_name = if alias_name.starts_with(b"\"")
|
|
|
|
&& alias_name.ends_with(b"\"")
|
|
|
|
&& alias_name.len() > 1
|
|
|
|
{
|
|
|
|
alias_name[1..(alias_name.len() - 1)].to_vec()
|
|
|
|
} else {
|
|
|
|
alias_name.to_vec()
|
|
|
|
};
|
2023-01-22 19:34:15 +00:00
|
|
|
|
|
|
|
if let Some(mod_name) = module_name {
|
|
|
|
if alias_name == mod_name {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
Some(ParseError::NamedAsModule(
|
|
|
|
"alias".to_string(),
|
|
|
|
String::from_utf8_lossy(&alias_name).to_string(),
|
|
|
|
spans[split_id],
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if &alias_name == b"main" {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
Some(ParseError::ExportMainAliasNotAllowed(spans[split_id])),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-22 21:19:47 +00:00
|
|
|
let _equals = working_set.get_span_contents(spans[split_id + 1]);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-08-22 21:19:47 +00:00
|
|
|
let replacement = spans[(split_id + 2)..].to_vec();
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-12-22 20:31:34 +00:00
|
|
|
let checked_name = String::from_utf8_lossy(&alias_name);
|
|
|
|
if checked_name.contains('#')
|
2022-12-27 23:00:44 +00:00
|
|
|
|| checked_name.contains('^')
|
2022-12-22 20:31:34 +00:00
|
|
|
|| checked_name.parse::<bytesize::ByteSize>().is_ok()
|
|
|
|
|| checked_name.parse::<f64>().is_ok()
|
|
|
|
{
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![garbage(name_span)]),
|
|
|
|
Some(ParseError::AliasNotValid(name_span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
working_set.add_alias(alias_name, replacement, lite_command.comments.clone());
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2022-03-19 18:58:01 +00:00
|
|
|
let err = if spans.len() < 4 {
|
|
|
|
Some(ParseError::IncorrectValue(
|
|
|
|
"Incomplete alias".into(),
|
2022-08-22 21:19:47 +00:00
|
|
|
span(&spans[..split_id]),
|
2022-03-19 18:58:01 +00:00
|
|
|
"incomplete alias".into(),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2021-09-26 18:39:19 +00:00
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-09-26 18:39:19 +00:00
|
|
|
expr: Expr::Call(call),
|
2021-12-18 20:10:40 +00:00
|
|
|
span: span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-09-26 18:39:19 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2022-03-19 18:58:01 +00:00
|
|
|
err,
|
2021-09-26 18:39:19 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-11-15 23:16:06 +00:00
|
|
|
Some(ParseError::InternalError(
|
Fix typos by codespell (#7600)
# Description
Found via `codespell -S target -L
crate,ser,numer,falsy,ro,te,nd,bu,ndoes,statics,ons,fo,rouge,pard`
# User-Facing Changes
None.
# Tests + Formatting
None and done.
# After Submitting
None.
2022-12-26 07:31:26 +00:00
|
|
|
"Alias statement unparsable".into(),
|
2021-09-26 18:39:19 +00:00
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-08-22 21:19:47 +00:00
|
|
|
// This one will trigger if `export` appears during eval, e.g., in a script
|
|
|
|
pub fn parse_export_in_block(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
lite_command: &LiteCommand,
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
|
|
|
let call_span = span(&lite_command.parts);
|
|
|
|
|
|
|
|
let full_name = if lite_command.parts.len() > 1 {
|
|
|
|
let sub = working_set.get_span_contents(lite_command.parts[1]);
|
|
|
|
match sub {
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
b"old-alias" | b"alias" | b"def" | b"def-env" | b"extern" | b"use" => {
|
|
|
|
[b"export ", sub].concat()
|
|
|
|
}
|
2022-08-22 21:19:47 +00:00
|
|
|
_ => b"export".to_vec(),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
b"export".to_vec()
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_decl(&full_name, &Type::Any) {
|
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
..
|
|
|
|
} = parse_internal_call(
|
|
|
|
working_set,
|
|
|
|
if full_name == b"export" {
|
|
|
|
lite_command.parts[0]
|
|
|
|
} else {
|
|
|
|
span(&lite_command.parts[0..2])
|
|
|
|
},
|
|
|
|
if full_name == b"export" {
|
|
|
|
&lite_command.parts[1..]
|
|
|
|
} else {
|
|
|
|
&lite_command.parts[2..]
|
|
|
|
},
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
let decl = working_set.get_decl(decl_id);
|
|
|
|
err = check_call(call_span, &decl.signature(), &call).or(err);
|
|
|
|
|
|
|
|
if err.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(&lite_command.parts),
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
format!(
|
|
|
|
"internal error: '{}' declaration not found",
|
|
|
|
String::from_utf8_lossy(&full_name)
|
|
|
|
),
|
|
|
|
span(&lite_command.parts),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
if &full_name == b"export" {
|
|
|
|
// export by itself is meaningless
|
|
|
|
return (
|
|
|
|
garbage_pipeline(&lite_command.parts),
|
|
|
|
Some(ParseError::UnexpectedKeyword(
|
|
|
|
"export".into(),
|
|
|
|
lite_command.parts[0],
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
match full_name.as_slice() {
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
b"export old-alias" => {
|
|
|
|
parse_old_alias(working_set, lite_command, None, expand_aliases_denylist)
|
|
|
|
}
|
2023-01-22 19:34:15 +00:00
|
|
|
b"export alias" => parse_alias(working_set, lite_command, None, expand_aliases_denylist),
|
2022-08-22 21:19:47 +00:00
|
|
|
b"export def" | b"export def-env" => {
|
2023-01-22 19:34:15 +00:00
|
|
|
parse_def(working_set, lite_command, None, expand_aliases_denylist)
|
2022-08-22 21:19:47 +00:00
|
|
|
}
|
|
|
|
b"export use" => {
|
|
|
|
let (pipeline, _, err) =
|
|
|
|
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
|
|
|
|
(pipeline, err)
|
|
|
|
}
|
2023-01-22 19:34:15 +00:00
|
|
|
b"export extern" => parse_extern(working_set, lite_command, None, expand_aliases_denylist),
|
2022-08-22 21:19:47 +00:00
|
|
|
_ => (
|
|
|
|
garbage_pipeline(&lite_command.parts),
|
|
|
|
Some(ParseError::UnexpectedKeyword(
|
|
|
|
String::from_utf8_lossy(&full_name).to_string(),
|
|
|
|
lite_command.parts[0],
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This one will trigger only in a module
|
|
|
|
pub fn parse_export_in_module(
|
2021-09-28 17:29:38 +00:00
|
|
|
working_set: &mut StateWorkingSet,
|
2022-01-22 18:24:47 +00:00
|
|
|
lite_command: &LiteCommand,
|
2023-01-22 19:34:15 +00:00
|
|
|
module_name: &[u8],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-07-29 08:57:10 +00:00
|
|
|
) -> (Pipeline, Vec<Exportable>, Option<ParseError>) {
|
2022-01-22 18:24:47 +00:00
|
|
|
let spans = &lite_command.parts[..];
|
2021-11-15 23:16:06 +00:00
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
let export_span = if let Some(sp) = spans.get(0) {
|
|
|
|
if working_set.get_span_contents(*sp) != b"export" {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2021-11-15 23:16:06 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"expected export statement".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
*sp
|
|
|
|
} else {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2021-11-15 23:16:06 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"got empty input for parsing export statement".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-06-10 15:59:35 +00:00
|
|
|
let export_decl_id = if let Some(id) = working_set.find_decl(b"export", &Type::Any) {
|
2021-11-15 23:16:06 +00:00
|
|
|
id
|
|
|
|
} else {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2021-11-15 23:16:06 +00:00
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"missing export command".into(),
|
|
|
|
export_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
2021-09-28 17:29:38 +00:00
|
|
|
|
2021-11-15 23:16:06 +00:00
|
|
|
let mut call = Box::new(Call {
|
|
|
|
head: spans[0],
|
|
|
|
decl_id: export_decl_id,
|
2022-04-09 02:55:02 +00:00
|
|
|
arguments: vec![],
|
2022-02-21 22:22:21 +00:00
|
|
|
redirect_stdout: true,
|
|
|
|
redirect_stderr: false,
|
2022-12-22 14:36:13 +00:00
|
|
|
parser_info: vec![],
|
2021-11-15 23:16:06 +00:00
|
|
|
});
|
2021-09-28 17:29:38 +00:00
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
let exportables = if let Some(kw_span) = spans.get(1) {
|
2021-11-15 23:16:06 +00:00
|
|
|
let kw_name = working_set.get_span_contents(*kw_span);
|
|
|
|
match kw_name {
|
2021-09-28 18:03:53 +00:00
|
|
|
b"def" => {
|
2022-01-22 18:24:47 +00:00
|
|
|
let lite_command = LiteCommand {
|
|
|
|
comments: lite_command.comments.clone(),
|
|
|
|
parts: spans[1..].to_vec(),
|
|
|
|
};
|
2023-01-22 19:34:15 +00:00
|
|
|
let (pipeline, err) = parse_def(
|
|
|
|
working_set,
|
|
|
|
&lite_command,
|
|
|
|
Some(module_name),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-11-15 23:16:06 +00:00
|
|
|
error = error.or(err);
|
2021-09-28 18:03:53 +00:00
|
|
|
|
2022-06-10 15:59:35 +00:00
|
|
|
let export_def_decl_id =
|
|
|
|
if let Some(id) = working_set.find_decl(b"export def", &Type::Any) {
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-06-10 15:59:35 +00:00
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"missing 'export def' command".into(),
|
|
|
|
export_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
2021-09-28 18:03:53 +00:00
|
|
|
|
|
|
|
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
|
2022-11-22 18:26:13 +00:00
|
|
|
if let Some(PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(ref def_call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
)) = pipeline.elements.get(0)
|
2022-02-15 19:31:14 +00:00
|
|
|
{
|
|
|
|
call = def_call.clone();
|
2021-11-15 23:16:06 +00:00
|
|
|
|
2022-02-15 19:31:14 +00:00
|
|
|
call.head = span(&spans[0..=1]);
|
|
|
|
call.decl_id = export_def_decl_id;
|
2021-09-28 18:03:53 +00:00
|
|
|
} else {
|
2021-11-15 23:16:06 +00:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"unexpected output from parsing a definition".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
2021-09-28 18:03:53 +00:00
|
|
|
};
|
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
let mut result = vec![];
|
|
|
|
|
2022-08-12 18:06:51 +00:00
|
|
|
if let Some(decl_name_span) = spans.get(2) {
|
|
|
|
let decl_name = working_set.get_span_contents(*decl_name_span);
|
|
|
|
let decl_name = trim_quotes(decl_name);
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
|
|
|
result.push(Exportable::Decl {
|
|
|
|
name: decl_name.to_vec(),
|
|
|
|
id: decl_id,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"failed to find added declaration".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
2021-11-15 23:16:06 +00:00
|
|
|
}
|
2022-07-29 08:57:10 +00:00
|
|
|
|
|
|
|
result
|
2021-11-15 23:16:06 +00:00
|
|
|
}
|
2022-01-29 20:45:46 +00:00
|
|
|
b"def-env" => {
|
|
|
|
let lite_command = LiteCommand {
|
|
|
|
comments: lite_command.comments.clone(),
|
|
|
|
parts: spans[1..].to_vec(),
|
|
|
|
};
|
2023-01-22 19:34:15 +00:00
|
|
|
let (pipeline, err) = parse_def(
|
|
|
|
working_set,
|
|
|
|
&lite_command,
|
|
|
|
Some(module_name),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-01-29 20:45:46 +00:00
|
|
|
error = error.or(err);
|
|
|
|
|
2022-06-10 15:59:35 +00:00
|
|
|
let export_def_decl_id =
|
|
|
|
if let Some(id) = working_set.find_decl(b"export def-env", &Type::Any) {
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-06-10 15:59:35 +00:00
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"missing 'export def-env' command".into(),
|
|
|
|
export_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
2022-01-29 20:45:46 +00:00
|
|
|
|
|
|
|
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
|
2022-11-22 18:26:13 +00:00
|
|
|
if let Some(PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(ref def_call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
)) = pipeline.elements.get(0)
|
2022-02-15 19:31:14 +00:00
|
|
|
{
|
|
|
|
call = def_call.clone();
|
2022-01-29 20:45:46 +00:00
|
|
|
|
2022-02-15 19:31:14 +00:00
|
|
|
call.head = span(&spans[0..=1]);
|
|
|
|
call.decl_id = export_def_decl_id;
|
2022-01-29 20:45:46 +00:00
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"unexpected output from parsing a definition".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
let mut result = vec![];
|
|
|
|
|
2022-09-08 09:27:11 +00:00
|
|
|
let decl_name = match spans.get(2) {
|
|
|
|
Some(span) => working_set.get_span_contents(*span),
|
|
|
|
None => &[],
|
|
|
|
};
|
2022-07-29 08:57:10 +00:00
|
|
|
let decl_name = trim_quotes(decl_name);
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
|
|
|
result.push(Exportable::Decl {
|
|
|
|
name: decl_name.to_vec(),
|
|
|
|
id: decl_id,
|
|
|
|
});
|
2022-01-29 20:45:46 +00:00
|
|
|
} else {
|
2022-07-29 08:57:10 +00:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"failed to find added declaration".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
2022-01-29 20:45:46 +00:00
|
|
|
}
|
2022-07-29 08:57:10 +00:00
|
|
|
|
|
|
|
result
|
2022-01-29 20:45:46 +00:00
|
|
|
}
|
2022-03-19 18:58:01 +00:00
|
|
|
b"extern" => {
|
|
|
|
let lite_command = LiteCommand {
|
|
|
|
comments: lite_command.comments.clone(),
|
|
|
|
parts: spans[1..].to_vec(),
|
|
|
|
};
|
2023-01-22 19:34:15 +00:00
|
|
|
let (pipeline, err) = parse_extern(
|
|
|
|
working_set,
|
|
|
|
&lite_command,
|
|
|
|
Some(module_name),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-03-19 18:58:01 +00:00
|
|
|
error = error.or(err);
|
|
|
|
|
2022-06-10 15:59:35 +00:00
|
|
|
let export_def_decl_id =
|
|
|
|
if let Some(id) = working_set.find_decl(b"export extern", &Type::Any) {
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-06-10 15:59:35 +00:00
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"missing 'export extern' command".into(),
|
|
|
|
export_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
2022-03-19 18:58:01 +00:00
|
|
|
|
|
|
|
// Trying to warp the 'def' call into the 'export def' in a very clumsy way
|
2022-11-22 18:26:13 +00:00
|
|
|
if let Some(PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(ref def_call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
)) = pipeline.elements.get(0)
|
2022-03-19 18:58:01 +00:00
|
|
|
{
|
|
|
|
call = def_call.clone();
|
|
|
|
|
|
|
|
call.head = span(&spans[0..=1]);
|
|
|
|
call.decl_id = export_def_decl_id;
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"unexpected output from parsing a definition".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
let mut result = vec![];
|
|
|
|
|
2022-09-08 09:27:11 +00:00
|
|
|
let decl_name = match spans.get(2) {
|
|
|
|
Some(span) => working_set.get_span_contents(*span),
|
|
|
|
None => &[],
|
|
|
|
};
|
2022-07-29 08:57:10 +00:00
|
|
|
let decl_name = trim_quotes(decl_name);
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) {
|
|
|
|
result.push(Exportable::Decl {
|
|
|
|
name: decl_name.to_vec(),
|
|
|
|
id: decl_id,
|
|
|
|
});
|
2022-03-19 18:58:01 +00:00
|
|
|
} else {
|
2022-07-29 08:57:10 +00:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"failed to find added declaration".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
2022-03-19 18:58:01 +00:00
|
|
|
}
|
2022-07-29 08:57:10 +00:00
|
|
|
|
|
|
|
result
|
2022-03-19 18:58:01 +00:00
|
|
|
}
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
b"old-alias" => {
|
|
|
|
let lite_command = LiteCommand {
|
|
|
|
comments: lite_command.comments.clone(),
|
|
|
|
parts: spans[1..].to_vec(),
|
|
|
|
};
|
|
|
|
let (pipeline, err) = parse_old_alias(
|
|
|
|
working_set,
|
|
|
|
&lite_command,
|
|
|
|
Some(module_name),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let export_alias_decl_id =
|
|
|
|
if let Some(id) = working_set.find_decl(b"export old-alias", &Type::Any) {
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
vec![],
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"missing 'export old-alias' command".into(),
|
|
|
|
export_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Trying to warp the 'old-alias' call into the 'export old-alias' in a very clumsy way
|
|
|
|
if let Some(PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(ref alias_call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
)) = pipeline.elements.get(0)
|
|
|
|
{
|
|
|
|
call = alias_call.clone();
|
|
|
|
|
|
|
|
call.head = span(&spans[0..=1]);
|
|
|
|
call.decl_id = export_alias_decl_id;
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"unexpected output from parsing a definition".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut result = vec![];
|
|
|
|
|
|
|
|
let alias_name = match spans.get(2) {
|
|
|
|
Some(span) => working_set.get_span_contents(*span),
|
|
|
|
None => &[],
|
|
|
|
};
|
|
|
|
let alias_name = trim_quotes(alias_name);
|
|
|
|
|
|
|
|
if let Some(alias_id) = working_set.find_alias(alias_name) {
|
|
|
|
result.push(Exportable::Alias {
|
|
|
|
name: alias_name.to_vec(),
|
|
|
|
id: alias_id,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"failed to find added alias".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
2022-03-19 18:58:01 +00:00
|
|
|
b"alias" => {
|
|
|
|
let lite_command = LiteCommand {
|
|
|
|
comments: lite_command.comments.clone(),
|
|
|
|
parts: spans[1..].to_vec(),
|
|
|
|
};
|
2023-01-22 19:34:15 +00:00
|
|
|
let (pipeline, err) = parse_alias(
|
|
|
|
working_set,
|
|
|
|
&lite_command,
|
|
|
|
Some(module_name),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-03-19 18:58:01 +00:00
|
|
|
error = error.or(err);
|
|
|
|
|
2022-06-10 15:59:35 +00:00
|
|
|
let export_alias_decl_id =
|
|
|
|
if let Some(id) = working_set.find_decl(b"export alias", &Type::Any) {
|
|
|
|
id
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-06-10 15:59:35 +00:00
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"missing 'export alias' command".into(),
|
|
|
|
export_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
2022-03-19 18:58:01 +00:00
|
|
|
|
|
|
|
// Trying to warp the 'alias' call into the 'export alias' in a very clumsy way
|
2022-11-22 18:26:13 +00:00
|
|
|
if let Some(PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(ref alias_call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
)) = pipeline.elements.get(0)
|
2022-03-19 18:58:01 +00:00
|
|
|
{
|
|
|
|
call = alias_call.clone();
|
|
|
|
|
|
|
|
call.head = span(&spans[0..=1]);
|
|
|
|
call.decl_id = export_alias_decl_id;
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"unexpected output from parsing a definition".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
let mut result = vec![];
|
|
|
|
|
2022-09-08 09:27:11 +00:00
|
|
|
let alias_name = match spans.get(2) {
|
|
|
|
Some(span) => working_set.get_span_contents(*span),
|
|
|
|
None => &[],
|
|
|
|
};
|
2022-07-29 08:57:10 +00:00
|
|
|
let alias_name = trim_quotes(alias_name);
|
|
|
|
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
if let Some(alias_id) = working_set.find_decl(alias_name, &Type::Any) {
|
|
|
|
result.push(Exportable::Decl {
|
2022-07-29 08:57:10 +00:00
|
|
|
name: alias_name.to_vec(),
|
|
|
|
id: alias_id,
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"failed to find added alias".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
result
|
|
|
|
}
|
|
|
|
b"use" => {
|
|
|
|
let lite_command = LiteCommand {
|
|
|
|
comments: lite_command.comments.clone(),
|
|
|
|
parts: spans[1..].to_vec(),
|
|
|
|
};
|
|
|
|
let (pipeline, exportables, err) =
|
|
|
|
parse_use(working_set, &lite_command.parts, expand_aliases_denylist);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let export_use_decl_id =
|
|
|
|
if let Some(id) = working_set.find_decl(b"export use", &Type::Any) {
|
|
|
|
id
|
2022-03-19 18:58:01 +00:00
|
|
|
} else {
|
2022-07-29 08:57:10 +00:00
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
vec![],
|
2022-03-19 18:58:01 +00:00
|
|
|
Some(ParseError::InternalError(
|
2022-07-29 08:57:10 +00:00
|
|
|
"missing 'export use' command".into(),
|
|
|
|
export_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
// Trying to warp the 'use' call into the 'export use' in a very clumsy way
|
2022-11-22 18:26:13 +00:00
|
|
|
if let Some(PipelineElement::Expression(
|
|
|
|
_,
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(ref use_call),
|
|
|
|
..
|
|
|
|
},
|
|
|
|
)) = pipeline.elements.get(0)
|
2022-07-29 08:57:10 +00:00
|
|
|
{
|
|
|
|
call = use_call.clone();
|
|
|
|
|
|
|
|
call.head = span(&spans[0..=1]);
|
|
|
|
call.decl_id = export_use_decl_id;
|
2022-03-19 18:58:01 +00:00
|
|
|
} else {
|
2022-07-29 08:57:10 +00:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::InternalError(
|
|
|
|
"unexpected output from parsing a definition".into(),
|
|
|
|
span(&spans[1..]),
|
|
|
|
))
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
exportables
|
2022-03-19 18:58:01 +00:00
|
|
|
}
|
2021-11-15 23:16:06 +00:00
|
|
|
_ => {
|
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::Expected(
|
|
|
|
// TODO: Fill in more keywords as they come
|
2022-07-29 08:57:10 +00:00
|
|
|
"def, def-env, alias, use, or env keyword".into(),
|
2021-11-15 23:16:06 +00:00
|
|
|
spans[1],
|
|
|
|
))
|
|
|
|
});
|
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![]
|
2021-09-28 18:03:53 +00:00
|
|
|
}
|
2021-09-28 17:29:38 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-11-15 23:16:06 +00:00
|
|
|
error = error.or_else(|| {
|
|
|
|
Some(ParseError::MissingPositional(
|
2022-03-19 18:58:01 +00:00
|
|
|
"def, def-env, alias, or env keyword".into(), // TODO: keep filling more keywords as they come
|
2022-12-03 09:44:12 +00:00
|
|
|
Span::new(export_span.end, export_span.end),
|
2022-03-19 18:58:01 +00:00
|
|
|
"'def', `def-env`, `alias`, or 'env' keyword.".to_string(),
|
2021-11-15 23:16:06 +00:00
|
|
|
))
|
|
|
|
});
|
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![]
|
2021-11-15 23:16:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-11-15 23:16:06 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-11-15 23:16:06 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2022-07-29 08:57:10 +00:00
|
|
|
exportables,
|
2021-11-15 23:16:06 +00:00
|
|
|
error,
|
|
|
|
)
|
2021-09-28 17:29:38 +00:00
|
|
|
}
|
|
|
|
|
2022-08-23 07:45:17 +00:00
|
|
|
pub fn parse_export_env(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
expand_aliases_denylist: &[usize],
|
2022-08-26 22:32:19 +00:00
|
|
|
) -> (Pipeline, Option<BlockId>, Option<ParseError>) {
|
2022-08-23 07:45:17 +00:00
|
|
|
if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"export-env" {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
2022-08-26 22:32:19 +00:00
|
|
|
None,
|
2022-08-23 07:45:17 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for 'export-env' command".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if spans.len() < 2 {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
2022-08-26 22:32:19 +00:00
|
|
|
None,
|
2022-08-23 07:45:17 +00:00
|
|
|
Some(ParseError::MissingPositional(
|
|
|
|
"block".into(),
|
|
|
|
span(spans),
|
|
|
|
"export-env <block>".into(),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let call = match working_set.find_decl(b"export-env", &Type::Any) {
|
|
|
|
Some(decl_id) => {
|
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&[spans[1]],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
let decl = working_set.get_decl(decl_id);
|
|
|
|
|
|
|
|
let call_span = span(spans);
|
|
|
|
|
|
|
|
err = check_call(call_span, &decl.signature(), &call).or(err);
|
|
|
|
if err.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
2022-08-26 22:32:19 +00:00
|
|
|
None,
|
2022-08-23 07:45:17 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
call
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
2022-08-26 22:32:19 +00:00
|
|
|
None,
|
2022-08-23 07:45:17 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: 'export-env' declaration not found".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-08-26 22:32:19 +00:00
|
|
|
let block_id = if let Some(block) = call.positional_nth(0) {
|
|
|
|
if let Some(block_id) = block.as_block() {
|
|
|
|
block_id
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
None,
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: 'export-env' block is not a block".into(),
|
|
|
|
block.span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
None,
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: 'export-env' block is missing".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-08-23 07:45:17 +00:00
|
|
|
let pipeline = Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]);
|
|
|
|
|
2022-08-26 22:32:19 +00:00
|
|
|
(pipeline, Some(block_id), None)
|
2022-08-23 07:45:17 +00:00
|
|
|
}
|
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
fn collect_first_comments(tokens: &[Token]) -> Vec<Span> {
|
|
|
|
let mut comments = vec![];
|
|
|
|
|
|
|
|
let mut tokens_iter = tokens.iter().peekable();
|
|
|
|
while let Some(token) = tokens_iter.next() {
|
|
|
|
match token.contents {
|
|
|
|
TokenContents::Comment => {
|
|
|
|
comments.push(token.span);
|
|
|
|
}
|
|
|
|
TokenContents::Eol => {
|
|
|
|
if let Some(Token {
|
|
|
|
contents: TokenContents::Eol,
|
|
|
|
..
|
|
|
|
}) = tokens_iter.peek()
|
|
|
|
{
|
|
|
|
if !comments.is_empty() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
comments.clear();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
comments
|
|
|
|
}
|
|
|
|
|
2021-10-18 20:19:25 +00:00
|
|
|
pub fn parse_module_block(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2021-10-31 15:22:10 +00:00
|
|
|
span: Span,
|
2023-01-22 19:34:15 +00:00
|
|
|
module_name: &[u8],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-12-30 15:44:37 +00:00
|
|
|
) -> (Block, Module, Vec<Span>, Option<ParseError>) {
|
2021-10-18 20:19:25 +00:00
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
working_set.enter_scope();
|
|
|
|
|
2021-10-31 15:22:10 +00:00
|
|
|
let source = working_set.get_span_contents(span);
|
2021-10-18 20:19:25 +00:00
|
|
|
|
2022-01-22 18:24:47 +00:00
|
|
|
let (output, err) = lex(source, span.start, &[], &[], false);
|
2021-10-18 20:19:25 +00:00
|
|
|
error = error.or(err);
|
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
let module_comments = collect_first_comments(&output);
|
|
|
|
|
2021-10-18 20:19:25 +00:00
|
|
|
let (output, err) = lite_parse(&output);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
for pipeline in &output.block {
|
|
|
|
if pipeline.commands.len() == 1 {
|
2022-11-22 18:26:13 +00:00
|
|
|
if let LiteElement::Command(_, command) = &pipeline.commands[0] {
|
2022-11-18 21:46:48 +00:00
|
|
|
parse_def_predecl(working_set, &command.parts, expand_aliases_denylist);
|
|
|
|
}
|
2021-10-18 20:19:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
let mut module = Module::from_span(module_name.to_vec(), span);
|
2021-10-18 20:19:25 +00:00
|
|
|
|
|
|
|
let block: Block = output
|
|
|
|
.block
|
|
|
|
.iter()
|
|
|
|
.map(|pipeline| {
|
|
|
|
if pipeline.commands.len() == 1 {
|
2022-11-18 21:46:48 +00:00
|
|
|
match &pipeline.commands[0] {
|
2022-11-22 18:26:13 +00:00
|
|
|
LiteElement::Command(_, command) => {
|
2022-11-18 21:46:48 +00:00
|
|
|
let name = working_set.get_span_contents(command.parts[0]);
|
2021-10-18 20:19:25 +00:00
|
|
|
|
2022-11-18 21:46:48 +00:00
|
|
|
let (pipeline, err) = match name {
|
|
|
|
b"def" | b"def-env" => {
|
2023-01-22 19:34:15 +00:00
|
|
|
let (pipeline, err) = parse_def(
|
|
|
|
working_set,
|
|
|
|
command,
|
|
|
|
None, // using commands named as the module locally is OK
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-10-18 20:19:25 +00:00
|
|
|
|
2022-11-18 21:46:48 +00:00
|
|
|
(pipeline, err)
|
|
|
|
}
|
|
|
|
b"extern" => {
|
2023-01-22 19:34:15 +00:00
|
|
|
let (pipeline, err) = parse_extern(
|
|
|
|
working_set,
|
|
|
|
command,
|
|
|
|
None,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-07-29 08:57:10 +00:00
|
|
|
|
2022-11-18 21:46:48 +00:00
|
|
|
(pipeline, err)
|
|
|
|
}
|
Re-implement aliases (#8123)
# Description
This PR adds an alternative alias implementation. Old aliases still work
but you need to use `old-alias` instead of `alias`.
Instead of replacing spans in the original code and re-parsing, which
proved to be extremely error-prone and a constant source of panics, the
new implementation creates a new command that references the old
command. Consider the new alias defined as `alias ll = ls -l`. The
parser creates a new command called `ll` and remembers that it is
actually a `ls` command called with the `-l` flag. Then, when the parser
sees the `ll` command, it will translate it to `ls -l` and passes to it
any parameters that were passed to the call to `ll`. It works quite
similar to how known externals defined with `extern` are implemented.
The new alias implementation should work the same way as the old
aliases, including exporting from modules, referencing both known and
unknown externals. It seems to preserve custom completions and pipeline
metadata. It is quite robust in most cases but there are some rough
edges (see later).
Fixes https://github.com/nushell/nushell/issues/7648,
https://github.com/nushell/nushell/issues/8026,
https://github.com/nushell/nushell/issues/7512,
https://github.com/nushell/nushell/issues/5780,
https://github.com/nushell/nushell/issues/7754
No effect: https://github.com/nushell/nushell/issues/8122 (we might
revisit the completions code after this PR)
Should use custom command instead:
https://github.com/nushell/nushell/issues/6048
# User-Facing Changes
Since aliases are now basically commands, it has some new implications:
1. `alias spam = "spam"` (requires command call)
* **workaround**: use `alias spam = echo "spam"`
2. `def foo [] { 'foo' }; alias foo = ls -l` (foo defined more than
once)
* **workaround**: use different name (commands also have this
limitation)
4. `alias ls = (ls | sort-by type name -i)`
* **workaround**: Use custom command. _The common issue with this is
that it is currently not easy to pass flags through custom commands and
command referencing itself will lead to stack overflow. Both of these
issues are meant to be addressed._
5. TODO: Help messages, `which` command, `$nu.scope.aliases`, etc.
* Should we treat the aliases as commands or should they be separated
from regular commands?
6. Needs better error message and syntax highlight for recursed alias
(`alias f = f`)
7. Can't create alias with the same name as existing command (`alias ls
= ls -a`)
* Might be possible to add support for it (not 100% sure)
8. Standalone `alias` doesn't list aliases anymore
9. Can't alias parser keywords (e.g., stuff like `alias ou = overlay
use` won't work)
* TODO: Needs a better error message when attempting to do so
# 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-02-27 07:44:05 +00:00
|
|
|
b"old-alias" => {
|
|
|
|
let (pipeline, err) = parse_old_alias(
|
|
|
|
working_set,
|
|
|
|
command,
|
|
|
|
None, // using aliases named as the module locally is OK
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
(pipeline, err)
|
|
|
|
}
|
2022-11-18 21:46:48 +00:00
|
|
|
b"alias" => {
|
2023-01-22 19:34:15 +00:00
|
|
|
let (pipeline, err) = parse_alias(
|
|
|
|
working_set,
|
|
|
|
command,
|
|
|
|
None, // using aliases named as the module locally is OK
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-11-18 21:46:48 +00:00
|
|
|
|
|
|
|
(pipeline, err)
|
|
|
|
}
|
|
|
|
b"use" => {
|
|
|
|
let (pipeline, _, err) =
|
|
|
|
parse_use(working_set, &command.parts, expand_aliases_denylist);
|
2021-10-18 20:19:25 +00:00
|
|
|
|
2022-11-18 21:46:48 +00:00
|
|
|
(pipeline, err)
|
|
|
|
}
|
|
|
|
b"export" => {
|
|
|
|
let (pipe, exportables, err) = parse_export_in_module(
|
|
|
|
working_set,
|
|
|
|
command,
|
2023-01-22 19:34:15 +00:00
|
|
|
module_name,
|
2022-11-18 21:46:48 +00:00
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
if err.is_none() {
|
|
|
|
for exportable in exportables {
|
|
|
|
match exportable {
|
|
|
|
Exportable::Decl { name, id } => {
|
2023-01-22 19:34:15 +00:00
|
|
|
if &name == b"main" {
|
|
|
|
module.main = Some(id);
|
|
|
|
} else {
|
|
|
|
module.add_decl(name, id);
|
|
|
|
}
|
2022-11-18 21:46:48 +00:00
|
|
|
}
|
|
|
|
Exportable::Alias { name, id } => {
|
|
|
|
module.add_alias(name, id);
|
|
|
|
}
|
|
|
|
}
|
2022-07-29 08:57:10 +00:00
|
|
|
}
|
2022-03-19 18:58:01 +00:00
|
|
|
}
|
2021-10-18 20:19:25 +00:00
|
|
|
|
2022-11-18 21:46:48 +00:00
|
|
|
(pipe, err)
|
|
|
|
}
|
|
|
|
b"export-env" => {
|
|
|
|
let (pipe, maybe_env_block, err) = parse_export_env(
|
|
|
|
working_set,
|
|
|
|
&command.parts,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
if let Some(block_id) = maybe_env_block {
|
|
|
|
module.add_env_block(block_id);
|
|
|
|
}
|
2022-08-26 22:32:19 +00:00
|
|
|
|
2022-11-18 21:46:48 +00:00
|
|
|
(pipe, err)
|
|
|
|
}
|
|
|
|
_ => (
|
|
|
|
garbage_pipeline(&command.parts),
|
|
|
|
Some(ParseError::ExpectedKeyword(
|
|
|
|
"def or export keyword".into(),
|
|
|
|
command.parts[0],
|
|
|
|
)),
|
|
|
|
),
|
|
|
|
};
|
|
|
|
if error.is_none() {
|
|
|
|
error = err;
|
2022-08-26 22:32:19 +00:00
|
|
|
}
|
|
|
|
|
2022-11-18 21:46:48 +00:00
|
|
|
pipeline
|
2022-08-26 22:32:19 +00:00
|
|
|
}
|
2022-12-13 03:36:13 +00:00
|
|
|
LiteElement::Redirection(_, _, command) => garbage_pipeline(&command.parts),
|
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 09:22:30 +00:00
|
|
|
LiteElement::SeparateRedirection {
|
|
|
|
out: (_, command), ..
|
|
|
|
} => garbage_pipeline(&command.parts),
|
2021-10-18 20:19:25 +00:00
|
|
|
}
|
|
|
|
} else {
|
2021-10-31 15:22:10 +00:00
|
|
|
error = Some(ParseError::Expected("not a pipeline".into(), span));
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(&[span])
|
2021-10-18 20:19:25 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.into();
|
|
|
|
|
|
|
|
working_set.exit_scope();
|
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
(block, module, module_comments, error)
|
2021-10-18 20:19:25 +00:00
|
|
|
}
|
|
|
|
|
2021-09-26 18:39:19 +00:00
|
|
|
pub fn parse_module(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2022-12-30 15:44:37 +00:00
|
|
|
lite_command: &LiteCommand,
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2021-09-26 18:39:19 +00:00
|
|
|
// TODO: Currently, module is closing over its parent scope (i.e., defs in the parent scope are
|
2021-10-10 11:31:13 +00:00
|
|
|
// visible and usable in this module's scope). We want to disable that for files.
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
let spans = &lite_command.parts;
|
|
|
|
let mut module_comments = lite_command.comments.clone();
|
|
|
|
|
2021-09-26 18:39:19 +00:00
|
|
|
let mut error = None;
|
|
|
|
let bytes = working_set.get_span_contents(spans[0]);
|
|
|
|
|
|
|
|
if bytes == b"module" && spans.len() >= 3 {
|
2022-04-25 23:44:44 +00:00
|
|
|
let (module_name_expr, err) = parse_string(working_set, spans[1], expand_aliases_denylist);
|
2021-09-26 18:39:19 +00:00
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let module_name = module_name_expr
|
|
|
|
.as_string()
|
|
|
|
.expect("internal error: module name is not a string");
|
|
|
|
|
|
|
|
let block_span = spans[2];
|
|
|
|
let block_bytes = working_set.get_span_contents(block_span);
|
|
|
|
let mut start = block_span.start;
|
|
|
|
let mut end = block_span.end;
|
|
|
|
|
|
|
|
if block_bytes.starts_with(b"{") {
|
|
|
|
start += 1;
|
|
|
|
} else {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-09-26 18:39:19 +00:00
|
|
|
Some(ParseError::Expected("block".into(), block_span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if block_bytes.ends_with(b"}") {
|
|
|
|
end -= 1;
|
|
|
|
} else {
|
2022-12-03 09:44:12 +00:00
|
|
|
error = error.or_else(|| Some(ParseError::Unclosed("}".into(), Span::new(end, end))));
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2022-12-03 09:44:12 +00:00
|
|
|
let block_span = Span::new(start, end);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
let (block, module, inner_comments, err) = parse_module_block(
|
|
|
|
working_set,
|
|
|
|
block_span,
|
|
|
|
module_name.as_bytes(),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-09-26 18:39:19 +00:00
|
|
|
error = error.or(err);
|
|
|
|
|
2021-11-17 04:23:55 +00:00
|
|
|
let block_id = working_set.add_block(block);
|
2022-12-30 15:44:37 +00:00
|
|
|
|
|
|
|
module_comments.extend(inner_comments);
|
|
|
|
let _ = working_set.add_module(&module_name, module, module_comments);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
|
|
|
let block_expr = Expression {
|
|
|
|
expr: Expr::Block(block_id),
|
|
|
|
span: block_span,
|
|
|
|
ty: Type::Block,
|
|
|
|
custom_completion: None,
|
|
|
|
};
|
|
|
|
|
|
|
|
let module_decl_id = working_set
|
2022-06-10 15:59:35 +00:00
|
|
|
.find_decl(b"module", &Type::Any)
|
2021-09-26 18:39:19 +00:00
|
|
|
.expect("internal error: missing module command");
|
|
|
|
|
|
|
|
let call = Box::new(Call {
|
|
|
|
head: spans[0],
|
|
|
|
decl_id: module_decl_id,
|
2022-04-09 02:55:02 +00:00
|
|
|
arguments: vec![
|
|
|
|
Argument::Positional(module_name_expr),
|
|
|
|
Argument::Positional(block_expr),
|
|
|
|
],
|
2022-02-21 22:22:21 +00:00
|
|
|
redirect_stdout: true,
|
|
|
|
redirect_stderr: false,
|
2022-12-22 14:36:13 +00:00
|
|
|
parser_info: vec![],
|
2021-09-26 18:39:19 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-09-26 18:39:19 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-09-26 18:39:19 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-09-26 18:39:19 +00:00
|
|
|
error,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-09-26 18:39:19 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"Expected structure: module <name> {}".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_use(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-07-29 08:57:10 +00:00
|
|
|
) -> (Pipeline, Vec<Exportable>, Option<ParseError>) {
|
2022-08-22 21:19:47 +00:00
|
|
|
let (name_span, split_id) =
|
|
|
|
if spans.len() > 1 && working_set.get_span_contents(spans[0]) == b"export" {
|
|
|
|
(spans[1], 2)
|
|
|
|
} else {
|
|
|
|
(spans[0], 1)
|
|
|
|
};
|
|
|
|
|
|
|
|
let use_call = working_set.get_span_contents(name_span).to_vec();
|
|
|
|
if use_call != b"use" {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
vec![],
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for 'use' command".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if working_set.get_span_contents(name_span) != b"use" {
|
2022-01-10 01:39:25 +00:00
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-01-10 01:39:25 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for 'use' command".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
let (call, call_span, args_spans) = match working_set.find_decl(b"use", &Type::Any) {
|
2022-01-10 01:39:25 +00:00
|
|
|
Some(decl_id) => {
|
2022-08-22 21:19:47 +00:00
|
|
|
let (command_spans, rest_spans) = spans.split_at(split_id);
|
|
|
|
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
2022-08-22 21:19:47 +00:00
|
|
|
span(command_spans),
|
|
|
|
rest_spans,
|
2022-03-18 19:03:57 +00:00
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-01-10 01:39:25 +00:00
|
|
|
let decl = working_set.get_decl(decl_id);
|
|
|
|
|
|
|
|
let call_span = span(spans);
|
|
|
|
|
|
|
|
err = check_call(call_span, &decl.signature(), &call).or(err);
|
|
|
|
if err.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2022-01-10 01:39:25 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-06-12 19:18:00 +00:00
|
|
|
ty: output,
|
2022-01-10 01:39:25 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-01-10 01:39:25 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
(call, call_span, rest_spans)
|
2022-01-10 01:39:25 +00:00
|
|
|
}
|
|
|
|
None => {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-01-10 01:39:25 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: 'use' declaration not found".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
2021-10-26 21:30:39 +00:00
|
|
|
}
|
2022-01-10 01:39:25 +00:00
|
|
|
};
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
let (import_pattern_expr, err) =
|
|
|
|
parse_import_pattern(working_set, args_spans, expand_aliases_denylist);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let import_pattern = if let Expression {
|
|
|
|
expr: Expr::ImportPattern(import_pattern),
|
|
|
|
..
|
|
|
|
} = &import_pattern_expr
|
|
|
|
{
|
|
|
|
import_pattern.clone()
|
2022-01-10 01:39:25 +00:00
|
|
|
} else {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-07-29 08:57:10 +00:00
|
|
|
vec![],
|
2022-01-10 01:39:25 +00:00
|
|
|
Some(ParseError::UnknownState(
|
2022-12-22 14:36:13 +00:00
|
|
|
"internal error: Import pattern positional is not import pattern".into(),
|
|
|
|
import_pattern_expr.span,
|
2022-01-10 01:39:25 +00:00
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-01-10 01:39:25 +00:00
|
|
|
let cwd = working_set.get_cwd();
|
|
|
|
|
|
|
|
// TODO: Add checking for importing too long import patterns, e.g.:
|
|
|
|
// > use spam foo non existent names here do not throw error
|
2022-12-21 22:21:03 +00:00
|
|
|
let (import_pattern, module) = if let Some(module_id) = import_pattern.head.id {
|
|
|
|
(import_pattern, working_set.get_module(module_id).clone())
|
|
|
|
} else {
|
|
|
|
// It could be a file
|
|
|
|
// TODO: Do not close over when loading module from file?
|
2022-05-01 18:37:20 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
let (module_filename, err) =
|
|
|
|
unescape_unquote_string(&import_pattern.head.name, import_pattern.head.span);
|
2022-07-27 15:36:56 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
if err.is_none() {
|
|
|
|
if let Some(module_path) =
|
|
|
|
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV)
|
|
|
|
{
|
|
|
|
if let Some(i) = working_set
|
|
|
|
.parsed_module_files
|
|
|
|
.iter()
|
|
|
|
.rposition(|p| p == &module_path)
|
2022-03-12 20:12:15 +00:00
|
|
|
{
|
2022-12-21 22:21:03 +00:00
|
|
|
let mut files: Vec<String> = working_set
|
2022-09-04 20:19:20 +00:00
|
|
|
.parsed_module_files
|
2022-12-21 22:21:03 +00:00
|
|
|
.split_off(i)
|
2022-09-04 20:19:20 +00:00
|
|
|
.iter()
|
2022-12-21 22:21:03 +00:00
|
|
|
.map(|p| p.to_string_lossy().to_string())
|
|
|
|
.collect();
|
2022-09-04 20:19:20 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
files.push(module_path.to_string_lossy().to_string());
|
2022-09-04 20:19:20 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
let msg = files.join("\nuses ");
|
2022-09-04 20:19:20 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
vec![],
|
|
|
|
Some(ParseError::CyclicalModuleImport(
|
|
|
|
msg,
|
|
|
|
import_pattern.head.span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
2022-09-04 20:19:20 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
let module_name = if let Some(stem) = module_path.file_stem() {
|
|
|
|
stem.to_string_lossy().to_string()
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
vec![],
|
|
|
|
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
|
|
|
|
);
|
|
|
|
};
|
2021-10-19 21:23:59 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
if let Ok(contents) = std::fs::read(&module_path) {
|
|
|
|
let span_start = working_set.next_span_start();
|
|
|
|
working_set.add_file(module_filename, &contents);
|
|
|
|
let span_end = working_set.next_span_start();
|
2022-01-10 01:39:25 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
// Change the currently parsed directory
|
|
|
|
let prev_currently_parsed_cwd = if let Some(parent) = module_path.parent() {
|
|
|
|
let prev = working_set.currently_parsed_cwd.clone();
|
2022-07-27 15:36:56 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
working_set.currently_parsed_cwd = Some(parent.into());
|
2022-07-27 15:36:56 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
prev
|
|
|
|
} else {
|
|
|
|
working_set.currently_parsed_cwd.clone()
|
|
|
|
};
|
2022-07-27 15:36:56 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
// Add the file to the stack of parsed module files
|
|
|
|
working_set.parsed_module_files.push(module_path);
|
2022-09-04 20:19:20 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
// Parse the module
|
2022-12-30 15:44:37 +00:00
|
|
|
let (block, module, module_comments, err) = parse_module_block(
|
2022-12-21 22:21:03 +00:00
|
|
|
working_set,
|
|
|
|
Span::new(span_start, span_end),
|
2023-01-22 19:34:15 +00:00
|
|
|
module_name.as_bytes(),
|
2022-12-21 22:21:03 +00:00
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
2022-01-10 01:39:25 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
// Remove the file from the stack of parsed module files
|
|
|
|
working_set.parsed_module_files.pop();
|
2022-09-04 20:19:20 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
// Restore the currently parsed directory back
|
|
|
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
2022-07-27 15:36:56 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
let _ = working_set.add_block(block);
|
2022-12-30 15:44:37 +00:00
|
|
|
let module_id =
|
|
|
|
working_set.add_module(&module_name, module.clone(), module_comments);
|
2022-01-10 01:39:25 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
(
|
|
|
|
ImportPattern {
|
|
|
|
head: ImportPatternHead {
|
|
|
|
name: module_name.into(),
|
|
|
|
id: Some(module_id),
|
|
|
|
span: import_pattern.head.span,
|
2022-01-10 01:39:25 +00:00
|
|
|
},
|
2022-12-21 22:21:03 +00:00
|
|
|
members: import_pattern.members,
|
|
|
|
hidden: HashSet::new(),
|
|
|
|
},
|
|
|
|
module,
|
|
|
|
)
|
2021-10-31 15:53:53 +00:00
|
|
|
} else {
|
2022-12-21 22:21:03 +00:00
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
vec![],
|
|
|
|
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
2023-01-22 19:34:15 +00:00
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
vec![],
|
|
|
|
Some(ParseError::ModuleNotFound(import_pattern.head.span)),
|
|
|
|
);
|
2022-01-10 01:39:25 +00:00
|
|
|
}
|
2022-12-21 22:21:03 +00:00
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
vec![],
|
|
|
|
Some(ParseError::NonUtf8(import_pattern.head.span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-03-19 18:58:01 +00:00
|
|
|
let (decls_to_use, aliases_to_use) = if import_pattern.members.is_empty() {
|
|
|
|
(
|
2022-05-07 19:39:22 +00:00
|
|
|
module.decls_with_head(&import_pattern.head.name),
|
|
|
|
module.aliases_with_head(&import_pattern.head.name),
|
2022-03-19 18:58:01 +00:00
|
|
|
)
|
2022-01-10 01:39:25 +00:00
|
|
|
} else {
|
|
|
|
match &import_pattern.members[0] {
|
2022-05-07 19:39:22 +00:00
|
|
|
ImportPatternMember::Glob { .. } => (module.decls(), module.aliases()),
|
2022-01-10 01:39:25 +00:00
|
|
|
ImportPatternMember::Name { name, span } => {
|
2022-03-19 18:58:01 +00:00
|
|
|
let mut decl_output = vec![];
|
|
|
|
let mut alias_output = vec![];
|
2022-01-10 01:39:25 +00:00
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
if name == b"main" {
|
|
|
|
if let Some(id) = &module.main {
|
|
|
|
decl_output.push((import_pattern.head.name.clone(), *id));
|
|
|
|
} else {
|
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
|
|
|
}
|
|
|
|
} else if let Some(id) = module.get_decl_id(name) {
|
2022-03-19 18:58:01 +00:00
|
|
|
decl_output.push((name.clone(), id));
|
2022-05-07 19:39:22 +00:00
|
|
|
} else if let Some(id) = module.get_alias_id(name) {
|
2022-03-19 18:58:01 +00:00
|
|
|
alias_output.push((name.clone(), id));
|
2022-09-25 16:52:43 +00:00
|
|
|
} else {
|
2023-01-22 19:34:15 +00:00
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
2022-01-10 01:39:25 +00:00
|
|
|
}
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-03-19 18:58:01 +00:00
|
|
|
(decl_output, alias_output)
|
2022-01-10 01:39:25 +00:00
|
|
|
}
|
|
|
|
ImportPatternMember::List { names } => {
|
2022-03-19 18:58:01 +00:00
|
|
|
let mut decl_output = vec![];
|
|
|
|
let mut alias_output = vec![];
|
2022-01-10 01:39:25 +00:00
|
|
|
|
|
|
|
for (name, span) in names {
|
2023-01-22 19:34:15 +00:00
|
|
|
if name == b"main" {
|
|
|
|
if let Some(id) = &module.main {
|
|
|
|
decl_output.push((import_pattern.head.name.clone(), *id));
|
|
|
|
} else {
|
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
|
|
|
}
|
|
|
|
} else if let Some(id) = module.get_decl_id(name) {
|
2022-03-19 18:58:01 +00:00
|
|
|
decl_output.push((name.clone(), id));
|
2022-05-07 19:39:22 +00:00
|
|
|
} else if let Some(id) = module.get_alias_id(name) {
|
2022-03-19 18:58:01 +00:00
|
|
|
alias_output.push((name.clone(), id));
|
2022-09-25 16:52:43 +00:00
|
|
|
} else {
|
2022-01-10 01:39:25 +00:00
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
|
|
|
break;
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
}
|
2021-09-27 00:23:22 +00:00
|
|
|
|
2022-03-19 18:58:01 +00:00
|
|
|
(decl_output, alias_output)
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
2022-01-10 01:39:25 +00:00
|
|
|
}
|
|
|
|
};
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-07-29 08:57:10 +00:00
|
|
|
let exportables = decls_to_use
|
|
|
|
.iter()
|
|
|
|
.map(|(name, decl_id)| Exportable::Decl {
|
|
|
|
name: name.clone(),
|
|
|
|
id: *decl_id,
|
|
|
|
})
|
|
|
|
.chain(
|
|
|
|
aliases_to_use
|
|
|
|
.iter()
|
|
|
|
.map(|(name, alias_id)| Exportable::Alias {
|
|
|
|
name: name.clone(),
|
|
|
|
id: *alias_id,
|
|
|
|
}),
|
|
|
|
)
|
|
|
|
.collect();
|
|
|
|
|
2022-05-07 19:39:22 +00:00
|
|
|
// Extend the current scope with the module's exportables
|
2022-01-10 01:39:25 +00:00
|
|
|
working_set.use_decls(decls_to_use);
|
2022-03-19 18:58:01 +00:00
|
|
|
working_set.use_aliases(aliases_to_use);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-01-10 01:39:25 +00:00
|
|
|
// Create a new Use command call to pass the new import pattern
|
|
|
|
let import_pattern_expr = Expression {
|
|
|
|
expr: Expr::ImportPattern(import_pattern),
|
2022-12-22 14:36:13 +00:00
|
|
|
span: span(args_spans),
|
|
|
|
ty: Type::Any,
|
2022-01-10 01:39:25 +00:00
|
|
|
custom_completion: None,
|
|
|
|
};
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
let mut call = call;
|
|
|
|
call.add_parser_info(import_pattern_expr);
|
2021-11-15 23:16:06 +00:00
|
|
|
|
2022-01-10 01:39:25 +00:00
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2022-01-10 01:39:25 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2022-01-10 01:39:25 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2022-07-29 08:57:10 +00:00
|
|
|
exportables,
|
2022-01-10 01:39:25 +00:00
|
|
|
error,
|
|
|
|
)
|
|
|
|
}
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2022-01-10 01:39:25 +00:00
|
|
|
pub fn parse_hide(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2022-01-10 01:39:25 +00:00
|
|
|
if working_set.get_span_contents(spans[0]) != b"hide" {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-09-26 18:39:19 +00:00
|
|
|
Some(ParseError::UnknownState(
|
2022-01-10 01:39:25 +00:00
|
|
|
"internal error: Wrong call name for 'hide' command".into(),
|
2021-09-26 18:39:19 +00:00
|
|
|
span(spans),
|
|
|
|
)),
|
2022-01-10 01:39:25 +00:00
|
|
|
);
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
let (call, args_spans) = match working_set.find_decl(b"hide", &Type::Any) {
|
2022-01-10 01:39:25 +00:00
|
|
|
Some(decl_id) => {
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&spans[1..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2022-01-10 01:39:25 +00:00
|
|
|
let decl = working_set.get_decl(decl_id);
|
|
|
|
|
|
|
|
let call_span = span(spans);
|
|
|
|
|
|
|
|
err = check_call(call_span, &decl.signature(), &call).or(err);
|
|
|
|
if err.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2022-01-10 01:39:25 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-06-12 19:18:00 +00:00
|
|
|
ty: output,
|
2022-01-10 01:39:25 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2022-01-10 01:39:25 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
(call, &spans[1..])
|
2022-01-10 01:39:25 +00:00
|
|
|
}
|
|
|
|
None => {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-01-10 01:39:25 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: 'hide' declaration not found".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
let (import_pattern_expr, err) =
|
|
|
|
parse_import_pattern(working_set, args_spans, expand_aliases_denylist);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let import_pattern = if let Expression {
|
|
|
|
expr: Expr::ImportPattern(import_pattern),
|
|
|
|
..
|
|
|
|
} = &import_pattern_expr
|
|
|
|
{
|
|
|
|
import_pattern.clone()
|
2022-01-10 01:39:25 +00:00
|
|
|
} else {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2022-01-10 01:39:25 +00:00
|
|
|
Some(ParseError::UnknownState(
|
2022-12-22 14:36:13 +00:00
|
|
|
"internal error: Import pattern positional is not import pattern".into(),
|
|
|
|
import_pattern_expr.span,
|
2022-01-10 01:39:25 +00:00
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2021-09-28 20:18:48 +00:00
|
|
|
let bytes = working_set.get_span_contents(spans[0]);
|
|
|
|
|
|
|
|
if bytes == b"hide" && spans.len() >= 2 {
|
2021-11-15 23:16:06 +00:00
|
|
|
for span in spans[1..].iter() {
|
2022-04-25 23:44:44 +00:00
|
|
|
let (_, err) = parse_string(working_set, *span, expand_aliases_denylist);
|
2021-11-15 23:16:06 +00:00
|
|
|
error = error.or(err);
|
|
|
|
}
|
2021-09-28 20:18:48 +00:00
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
// module used only internally, not saved anywhere
|
2022-06-10 15:59:35 +00:00
|
|
|
let (is_module, module) = if let Some(module_id) =
|
|
|
|
working_set.find_module(&import_pattern.head.name)
|
|
|
|
{
|
|
|
|
(true, working_set.get_module(module_id).clone())
|
|
|
|
} else if import_pattern.members.is_empty() {
|
|
|
|
// The pattern head can be:
|
|
|
|
if let Some(id) = working_set.find_alias(&import_pattern.head.name) {
|
|
|
|
// an alias,
|
2023-01-22 19:34:15 +00:00
|
|
|
let mut module = Module::new(b"tmp".to_vec());
|
2022-07-29 08:57:10 +00:00
|
|
|
module.add_alias(import_pattern.head.name.clone(), id);
|
2022-06-10 15:59:35 +00:00
|
|
|
|
|
|
|
(false, module)
|
|
|
|
} else if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Any) {
|
|
|
|
// a custom command,
|
2023-01-22 19:34:15 +00:00
|
|
|
let mut module = Module::new(b"tmp".to_vec());
|
2022-07-29 08:57:10 +00:00
|
|
|
module.add_decl(import_pattern.head.name.clone(), id);
|
2022-06-10 15:59:35 +00:00
|
|
|
|
|
|
|
(false, module)
|
2021-10-04 17:08:24 +00:00
|
|
|
} else {
|
2022-06-10 15:59:35 +00:00
|
|
|
// , or it could be an env var (handled by the engine)
|
2023-01-22 19:34:15 +00:00
|
|
|
(false, Module::new(b"tmp".to_vec()))
|
2022-06-10 15:59:35 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
Some(ParseError::ModuleNotFound(spans[1])),
|
|
|
|
);
|
|
|
|
};
|
2021-09-28 20:18:48 +00:00
|
|
|
|
2021-10-04 17:08:24 +00:00
|
|
|
// This kind of inverts the import pattern matching found in parse_use()
|
2022-02-12 09:50:37 +00:00
|
|
|
let (aliases_to_hide, decls_to_hide) = if import_pattern.members.is_empty() {
|
2021-10-31 15:38:00 +00:00
|
|
|
if is_module {
|
2022-02-12 09:50:37 +00:00
|
|
|
(
|
2022-05-07 19:39:22 +00:00
|
|
|
module.alias_names_with_head(&import_pattern.head.name),
|
|
|
|
module.decl_names_with_head(&import_pattern.head.name),
|
2022-02-12 09:50:37 +00:00
|
|
|
)
|
2021-10-31 15:38:00 +00:00
|
|
|
} else {
|
2022-05-07 19:39:22 +00:00
|
|
|
(module.alias_names(), module.decl_names())
|
2021-10-31 15:38:00 +00:00
|
|
|
}
|
2021-10-04 17:08:24 +00:00
|
|
|
} else {
|
|
|
|
match &import_pattern.members[0] {
|
2022-05-07 19:39:22 +00:00
|
|
|
ImportPatternMember::Glob { .. } => (module.alias_names(), module.decl_names()),
|
2021-10-04 17:08:24 +00:00
|
|
|
ImportPatternMember::Name { name, span } => {
|
2022-02-12 09:50:37 +00:00
|
|
|
let mut aliases = vec![];
|
|
|
|
let mut decls = vec![];
|
2021-11-15 23:16:06 +00:00
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
if name == b"main" {
|
|
|
|
if module.main.is_some() {
|
|
|
|
decls.push(import_pattern.head.name.clone());
|
|
|
|
} else {
|
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
|
|
|
}
|
|
|
|
} else if let Some(item) =
|
|
|
|
module.alias_name_with_head(name, &import_pattern.head.name)
|
2022-02-12 09:50:37 +00:00
|
|
|
{
|
|
|
|
aliases.push(item);
|
|
|
|
} else if let Some(item) =
|
2022-05-07 19:39:22 +00:00
|
|
|
module.decl_name_with_head(name, &import_pattern.head.name)
|
2022-02-12 09:50:37 +00:00
|
|
|
{
|
|
|
|
decls.push(item);
|
2022-09-25 16:52:43 +00:00
|
|
|
} else {
|
2021-11-15 23:16:06 +00:00
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
2021-10-04 17:08:24 +00:00
|
|
|
}
|
|
|
|
|
2022-02-12 09:50:37 +00:00
|
|
|
(aliases, decls)
|
2021-10-04 17:08:24 +00:00
|
|
|
}
|
|
|
|
ImportPatternMember::List { names } => {
|
2022-02-12 09:50:37 +00:00
|
|
|
let mut aliases = vec![];
|
|
|
|
let mut decls = vec![];
|
2021-09-28 20:18:48 +00:00
|
|
|
|
2021-10-04 17:08:24 +00:00
|
|
|
for (name, span) in names {
|
2023-01-22 19:34:15 +00:00
|
|
|
if name == b"main" {
|
|
|
|
if module.main.is_some() {
|
|
|
|
decls.push(import_pattern.head.name.clone());
|
|
|
|
} else {
|
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if let Some(item) =
|
2022-05-07 19:39:22 +00:00
|
|
|
module.alias_name_with_head(name, &import_pattern.head.name)
|
2021-11-15 23:16:06 +00:00
|
|
|
{
|
2022-02-12 09:50:37 +00:00
|
|
|
aliases.push(item);
|
|
|
|
} else if let Some(item) =
|
2022-05-07 19:39:22 +00:00
|
|
|
module.decl_name_with_head(name, &import_pattern.head.name)
|
2022-02-12 09:50:37 +00:00
|
|
|
{
|
|
|
|
decls.push(item);
|
2022-09-25 16:52:43 +00:00
|
|
|
} else {
|
2021-11-15 23:16:06 +00:00
|
|
|
error = error.or(Some(ParseError::ExportNotFound(*span)));
|
|
|
|
break;
|
2021-10-04 17:08:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-12 09:50:37 +00:00
|
|
|
(aliases, decls)
|
2021-10-04 17:08:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-02-12 09:50:37 +00:00
|
|
|
let import_pattern = {
|
|
|
|
let aliases: HashSet<Vec<u8>> = aliases_to_hide.iter().cloned().collect();
|
|
|
|
let decls: HashSet<Vec<u8>> = decls_to_hide.iter().cloned().collect();
|
|
|
|
|
|
|
|
import_pattern.with_hidden(decls.union(&aliases).cloned().collect())
|
|
|
|
};
|
|
|
|
|
2021-11-15 23:16:06 +00:00
|
|
|
// TODO: `use spam; use spam foo; hide foo` will hide both `foo` and `spam foo` since
|
|
|
|
// they point to the same DeclId. Do we want to keep it that way?
|
|
|
|
working_set.hide_decls(&decls_to_hide);
|
2022-02-12 09:50:37 +00:00
|
|
|
working_set.hide_aliases(&aliases_to_hide);
|
2021-09-28 20:18:48 +00:00
|
|
|
|
2022-01-10 01:39:25 +00:00
|
|
|
// Create a new Use command call to pass the new import pattern
|
2021-11-15 23:16:06 +00:00
|
|
|
let import_pattern_expr = Expression {
|
|
|
|
expr: Expr::ImportPattern(import_pattern),
|
2022-12-22 14:36:13 +00:00
|
|
|
span: span(args_spans),
|
|
|
|
ty: Type::Any,
|
2021-11-15 23:16:06 +00:00
|
|
|
custom_completion: None,
|
|
|
|
};
|
|
|
|
|
2022-12-22 14:36:13 +00:00
|
|
|
let mut call = call;
|
|
|
|
call.add_parser_info(import_pattern_expr);
|
2021-09-28 20:18:48 +00:00
|
|
|
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-09-28 20:18:48 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-09-28 20:18:48 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-09-28 20:18:48 +00:00
|
|
|
error,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-09-28 20:18:48 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"Expected structure: hide <name>".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-26 14:47:04 +00:00
|
|
|
pub fn parse_overlay_new(
|
|
|
|
working_set: &mut StateWorkingSet,
|
2023-03-10 21:20:31 +00:00
|
|
|
call: Box<Call>,
|
2022-05-26 14:47:04 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2023-03-10 21:20:31 +00:00
|
|
|
let call_span = call.span();
|
2022-05-26 14:47:04 +00:00
|
|
|
|
|
|
|
let (overlay_name, _) = if let Some(expr) = call.positional_nth(0) {
|
2023-01-11 22:18:06 +00:00
|
|
|
match eval_constant(working_set, expr) {
|
|
|
|
Ok(val) => match value_as_string(val, expr.span) {
|
|
|
|
Ok(s) => (s, expr.span),
|
|
|
|
Err(err) => {
|
2023-03-10 21:20:31 +00:00
|
|
|
return (garbage_pipeline(&[call_span]), Some(err));
|
2023-01-11 22:18:06 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(err) => {
|
2023-03-10 21:20:31 +00:00
|
|
|
return (garbage_pipeline(&[call_span]), Some(err));
|
2023-01-11 22:18:06 +00:00
|
|
|
}
|
2022-05-26 14:47:04 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return (
|
2023-03-10 21:20:31 +00:00
|
|
|
garbage_pipeline(&[call_span]),
|
2022-05-26 14:47:04 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Missing required positional after call parsing".into(),
|
|
|
|
call_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
let pipeline = Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
2023-03-10 21:20:31 +00:00
|
|
|
span: call_span,
|
2022-05-26 14:47:04 +00:00
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]);
|
|
|
|
|
2023-01-22 19:34:15 +00:00
|
|
|
let module_id = working_set.add_module(
|
|
|
|
&overlay_name,
|
|
|
|
Module::new(overlay_name.as_bytes().to_vec()),
|
|
|
|
vec![],
|
|
|
|
);
|
2022-05-26 14:47:04 +00:00
|
|
|
|
2022-08-12 18:06:51 +00:00
|
|
|
working_set.add_overlay(
|
|
|
|
overlay_name.as_bytes().to_vec(),
|
|
|
|
module_id,
|
|
|
|
vec![],
|
|
|
|
vec![],
|
|
|
|
false,
|
|
|
|
);
|
2022-05-26 14:47:04 +00:00
|
|
|
|
|
|
|
(pipeline, None)
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:27:56 +00:00
|
|
|
pub fn parse_overlay_use(
|
2022-05-07 19:39:22 +00:00
|
|
|
working_set: &mut StateWorkingSet,
|
2023-03-10 21:20:31 +00:00
|
|
|
call: Box<Call>,
|
2022-05-07 19:39:22 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2023-03-10 21:20:31 +00:00
|
|
|
let call_span = call.span();
|
2022-05-07 19:39:22 +00:00
|
|
|
|
|
|
|
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
2022-12-21 22:21:03 +00:00
|
|
|
match eval_constant(working_set, expr) {
|
|
|
|
Ok(val) => match value_as_string(val, expr.span) {
|
|
|
|
Ok(s) => (s, expr.span),
|
|
|
|
Err(err) => {
|
2023-03-10 21:20:31 +00:00
|
|
|
return (garbage_pipeline(&[call_span]), Some(err));
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(err) => {
|
2023-03-10 21:20:31 +00:00
|
|
|
return (garbage_pipeline(&[call_span]), Some(err));
|
2022-12-21 22:21:03 +00:00
|
|
|
}
|
2022-05-07 19:39:22 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return (
|
2023-03-10 21:20:31 +00:00
|
|
|
garbage_pipeline(&[call_span]),
|
2022-05-07 19:39:22 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Missing required positional after call parsing".into(),
|
|
|
|
call_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-08-13 14:28:18 +00:00
|
|
|
let new_name = if let Some(kw_expression) = call.positional_nth(1) {
|
|
|
|
if let Some(new_name_expression) = kw_expression.as_keyword() {
|
2022-12-21 22:21:03 +00:00
|
|
|
match eval_constant(working_set, new_name_expression) {
|
|
|
|
Ok(val) => match value_as_string(val, new_name_expression.span) {
|
|
|
|
Ok(s) => Some(Spanned {
|
|
|
|
item: s,
|
|
|
|
span: new_name_expression.span,
|
|
|
|
}),
|
2023-03-10 21:20:31 +00:00
|
|
|
Err(err) => return (garbage_pipeline(&[call_span]), Some(err)),
|
2022-12-21 22:21:03 +00:00
|
|
|
},
|
2023-03-10 21:20:31 +00:00
|
|
|
Err(err) => return (garbage_pipeline(&[call_span]), Some(err)),
|
2022-08-13 14:28:18 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return (
|
2023-03-10 21:20:31 +00:00
|
|
|
garbage_pipeline(&[call_span]),
|
2022-08-13 14:28:18 +00:00
|
|
|
Some(ParseError::ExpectedKeyword(
|
|
|
|
"as keyword".to_string(),
|
|
|
|
kw_expression.span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2022-08-12 18:06:51 +00:00
|
|
|
let has_prefix = call.has_flag("prefix");
|
Reorder export-env eval and allow reloading an overlay (#7231)
# Description
This PR is a response to the issues raised in
https://github.com/nushell/nushell/pull/7087. It consists of two
changes:
* `export-env`, when evaluated in `overlay use`, will see the original
environment. Previously, it would see the environment from previous
overlay activation.
* Added a new `--reload` flag that reloads the overlay. Custom
definitions will be kept but the original definitions and environment
will be reloaded.
This enables a pattern when an overlay is supposed to shadow an existing
environment variable, such as `PROMPT_COMMAND`, but `overlay use` would
keep loading the value from the first activation. You can easily test it
by defining a module
```
module prompt {
export-env {
let-env PROMPT_COMMAND = (date now | into string)
}
}
```
Calling `overlay use prompt` for the first time changes the prompt to
the current time, however, subsequent calls of `overlay use` won't
change the time. That's because overlays, once activated, store their
state so they can be hidden and restored at later time. To force-reload
the environment, use the new flag: Calling `overlay use --reload prompt`
repeatedly now updates the prompt with the current time each time.
# User-Facing Changes
* When calling `overlay use`, if the module has an `export-env` block,
the block will see the environment as it is _before_ the overlay is
activated. Previously, it was _after_.
* A new `overlay use --reload` flag.
# 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 22:45:24 +00:00
|
|
|
let do_reload = call.has_flag("reload");
|
2022-08-12 18:06:51 +00:00
|
|
|
|
2022-05-07 19:39:22 +00:00
|
|
|
let pipeline = Pipeline::from_vec(vec![Expression {
|
2022-09-04 15:36:42 +00:00
|
|
|
expr: Expr::Call(call.clone()),
|
2023-03-10 21:20:31 +00:00
|
|
|
span: call_span,
|
2022-05-07 19:39:22 +00:00
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]);
|
|
|
|
|
|
|
|
let cwd = working_set.get_cwd();
|
|
|
|
|
|
|
|
let mut error = None;
|
|
|
|
|
2022-09-04 15:36:42 +00:00
|
|
|
let (final_overlay_name, origin_module, origin_module_id, is_module_updated) = if let Some(
|
|
|
|
overlay_frame,
|
|
|
|
) =
|
|
|
|
working_set.find_overlay(overlay_name.as_bytes())
|
|
|
|
{
|
|
|
|
// Activate existing overlay
|
|
|
|
|
|
|
|
// First, check for errors
|
2022-08-12 18:06:51 +00:00
|
|
|
if has_prefix && !overlay_frame.prefixed {
|
|
|
|
return (
|
|
|
|
pipeline,
|
|
|
|
Some(ParseError::OverlayPrefixMismatch(
|
|
|
|
overlay_name,
|
|
|
|
"without".to_string(),
|
|
|
|
overlay_name_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if !has_prefix && overlay_frame.prefixed {
|
|
|
|
return (
|
|
|
|
pipeline,
|
|
|
|
Some(ParseError::OverlayPrefixMismatch(
|
|
|
|
overlay_name,
|
|
|
|
"with".to_string(),
|
|
|
|
overlay_name_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-08-13 14:28:18 +00:00
|
|
|
if let Some(new_name) = new_name {
|
|
|
|
if new_name.item != overlay_name {
|
|
|
|
return (
|
|
|
|
pipeline,
|
|
|
|
Some(ParseError::CantAddOverlayHelp(
|
Fix typos by codespell (#7600)
# Description
Found via `codespell -S target -L
crate,ser,numer,falsy,ro,te,nd,bu,ndoes,statics,ons,fo,rouge,pard`
# User-Facing Changes
None.
# Tests + Formatting
None and done.
# After Submitting
None.
2022-12-26 07:31:26 +00:00
|
|
|
format!("Cannot add overlay as '{}' because it already exists under the name '{}'", new_name.item, overlay_name),
|
2022-08-13 14:28:18 +00:00
|
|
|
new_name.span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-12 18:06:51 +00:00
|
|
|
let module_id = overlay_frame.origin;
|
|
|
|
|
2022-05-07 19:39:22 +00:00
|
|
|
if let Some(new_module_id) = working_set.find_module(overlay_name.as_bytes()) {
|
Reorder export-env eval and allow reloading an overlay (#7231)
# Description
This PR is a response to the issues raised in
https://github.com/nushell/nushell/pull/7087. It consists of two
changes:
* `export-env`, when evaluated in `overlay use`, will see the original
environment. Previously, it would see the environment from previous
overlay activation.
* Added a new `--reload` flag that reloads the overlay. Custom
definitions will be kept but the original definitions and environment
will be reloaded.
This enables a pattern when an overlay is supposed to shadow an existing
environment variable, such as `PROMPT_COMMAND`, but `overlay use` would
keep loading the value from the first activation. You can easily test it
by defining a module
```
module prompt {
export-env {
let-env PROMPT_COMMAND = (date now | into string)
}
}
```
Calling `overlay use prompt` for the first time changes the prompt to
the current time, however, subsequent calls of `overlay use` won't
change the time. That's because overlays, once activated, store their
state so they can be hidden and restored at later time. To force-reload
the environment, use the new flag: Calling `overlay use --reload prompt`
repeatedly now updates the prompt with the current time each time.
# User-Facing Changes
* When calling `overlay use`, if the module has an `export-env` block,
the block will see the environment as it is _before_ the overlay is
activated. Previously, it was _after_.
* A new `overlay use --reload` flag.
# 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 22:45:24 +00:00
|
|
|
if !do_reload && (module_id == new_module_id) {
|
2023-01-22 19:34:15 +00:00
|
|
|
(
|
|
|
|
overlay_name,
|
|
|
|
Module::new(working_set.get_module(module_id).name.clone()),
|
|
|
|
module_id,
|
|
|
|
false,
|
|
|
|
)
|
2022-05-07 19:39:22 +00:00
|
|
|
} else {
|
|
|
|
// The origin module of an overlay changed => update it
|
2022-09-04 15:36:42 +00:00
|
|
|
(
|
2022-05-07 19:39:22 +00:00
|
|
|
overlay_name,
|
|
|
|
working_set.get_module(new_module_id).clone(),
|
|
|
|
new_module_id,
|
2022-09-04 15:36:42 +00:00
|
|
|
true,
|
|
|
|
)
|
2022-05-07 19:39:22 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-01-22 19:34:15 +00:00
|
|
|
let module_name = overlay_name.as_bytes().to_vec();
|
|
|
|
(overlay_name, Module::new(module_name), module_id, true)
|
2022-05-07 19:39:22 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Create a new overlay from a module
|
|
|
|
if let Some(module_id) =
|
|
|
|
// the name is a module
|
|
|
|
working_set.find_module(overlay_name.as_bytes())
|
|
|
|
{
|
2022-09-04 15:36:42 +00:00
|
|
|
(
|
2022-08-13 14:28:18 +00:00
|
|
|
new_name.map(|spanned| spanned.item).unwrap_or(overlay_name),
|
2022-05-07 19:39:22 +00:00
|
|
|
working_set.get_module(module_id).clone(),
|
|
|
|
module_id,
|
2022-09-04 15:36:42 +00:00
|
|
|
true,
|
|
|
|
)
|
2022-05-07 19:39:22 +00:00
|
|
|
} else {
|
|
|
|
// try if the name is a file
|
|
|
|
if let Ok(module_filename) =
|
|
|
|
String::from_utf8(trim_quotes(overlay_name.as_bytes()).to_vec())
|
|
|
|
{
|
|
|
|
if let Some(module_path) =
|
|
|
|
find_in_dirs(&module_filename, working_set, &cwd, LIB_DIRS_ENV)
|
|
|
|
{
|
|
|
|
let overlay_name = if let Some(stem) = module_path.file_stem() {
|
|
|
|
stem.to_string_lossy().to_string()
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
pipeline,
|
2023-03-10 21:20:31 +00:00
|
|
|
Some(ParseError::ModuleOrOverlayNotFound(overlay_name_span)),
|
2022-05-07 19:39:22 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-07-27 15:36:56 +00:00
|
|
|
if let Ok(contents) = std::fs::read(&module_path) {
|
2022-05-07 19:39:22 +00:00
|
|
|
let span_start = working_set.next_span_start();
|
|
|
|
working_set.add_file(module_filename, &contents);
|
|
|
|
let span_end = working_set.next_span_start();
|
|
|
|
|
2022-07-27 15:36:56 +00:00
|
|
|
// Change currently parsed directory
|
|
|
|
let prev_currently_parsed_cwd = if let Some(parent) = module_path.parent() {
|
|
|
|
let prev = working_set.currently_parsed_cwd.clone();
|
|
|
|
|
|
|
|
working_set.currently_parsed_cwd = Some(parent.into());
|
|
|
|
|
|
|
|
prev
|
|
|
|
} else {
|
|
|
|
working_set.currently_parsed_cwd.clone()
|
|
|
|
};
|
|
|
|
|
2022-12-30 15:44:37 +00:00
|
|
|
let (block, module, module_comments, err) = parse_module_block(
|
2022-05-07 19:39:22 +00:00
|
|
|
working_set,
|
|
|
|
Span::new(span_start, span_end),
|
2023-01-22 19:34:15 +00:00
|
|
|
overlay_name.as_bytes(),
|
2022-05-07 19:39:22 +00:00
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
|
|
|
|
2022-07-27 15:36:56 +00:00
|
|
|
// Restore the currently parsed directory back
|
|
|
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
|
|
|
|
2022-05-07 19:39:22 +00:00
|
|
|
let _ = working_set.add_block(block);
|
2022-12-30 15:44:37 +00:00
|
|
|
let module_id =
|
|
|
|
working_set.add_module(&overlay_name, module.clone(), module_comments);
|
2022-05-07 19:39:22 +00:00
|
|
|
|
2022-09-04 15:36:42 +00:00
|
|
|
(
|
2022-08-13 14:28:18 +00:00
|
|
|
new_name.map(|spanned| spanned.item).unwrap_or(overlay_name),
|
|
|
|
module,
|
|
|
|
module_id,
|
2022-09-04 15:36:42 +00:00
|
|
|
true,
|
|
|
|
)
|
2022-05-07 19:39:22 +00:00
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
pipeline,
|
2023-03-10 21:20:31 +00:00
|
|
|
Some(ParseError::ModuleOrOverlayNotFound(overlay_name_span)),
|
2022-05-07 19:39:22 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
2022-09-04 15:36:42 +00:00
|
|
|
return (
|
|
|
|
pipeline,
|
|
|
|
Some(ParseError::ModuleOrOverlayNotFound(overlay_name_span)),
|
|
|
|
);
|
2022-05-07 19:39:22 +00:00
|
|
|
}
|
|
|
|
} else {
|
2023-03-10 21:20:31 +00:00
|
|
|
return (
|
|
|
|
garbage_pipeline(&[call_span]),
|
|
|
|
Some(ParseError::NonUtf8(overlay_name_span)),
|
|
|
|
);
|
2022-05-07 19:39:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
Reorder export-env eval and allow reloading an overlay (#7231)
# Description
This PR is a response to the issues raised in
https://github.com/nushell/nushell/pull/7087. It consists of two
changes:
* `export-env`, when evaluated in `overlay use`, will see the original
environment. Previously, it would see the environment from previous
overlay activation.
* Added a new `--reload` flag that reloads the overlay. Custom
definitions will be kept but the original definitions and environment
will be reloaded.
This enables a pattern when an overlay is supposed to shadow an existing
environment variable, such as `PROMPT_COMMAND`, but `overlay use` would
keep loading the value from the first activation. You can easily test it
by defining a module
```
module prompt {
export-env {
let-env PROMPT_COMMAND = (date now | into string)
}
}
```
Calling `overlay use prompt` for the first time changes the prompt to
the current time, however, subsequent calls of `overlay use` won't
change the time. That's because overlays, once activated, store their
state so they can be hidden and restored at later time. To force-reload
the environment, use the new flag: Calling `overlay use --reload prompt`
repeatedly now updates the prompt with the current time each time.
# User-Facing Changes
* When calling `overlay use`, if the module has an `export-env` block,
the block will see the environment as it is _before_ the overlay is
activated. Previously, it was _after_.
* A new `overlay use --reload` flag.
# 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 22:45:24 +00:00
|
|
|
let (decls_to_lay, aliases_to_lay) = if is_module_updated {
|
|
|
|
if has_prefix {
|
|
|
|
(
|
|
|
|
origin_module.decls_with_head(final_overlay_name.as_bytes()),
|
|
|
|
origin_module.aliases_with_head(final_overlay_name.as_bytes()),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(origin_module.decls(), origin_module.aliases())
|
|
|
|
}
|
2022-09-04 15:36:42 +00:00
|
|
|
} else {
|
Reorder export-env eval and allow reloading an overlay (#7231)
# Description
This PR is a response to the issues raised in
https://github.com/nushell/nushell/pull/7087. It consists of two
changes:
* `export-env`, when evaluated in `overlay use`, will see the original
environment. Previously, it would see the environment from previous
overlay activation.
* Added a new `--reload` flag that reloads the overlay. Custom
definitions will be kept but the original definitions and environment
will be reloaded.
This enables a pattern when an overlay is supposed to shadow an existing
environment variable, such as `PROMPT_COMMAND`, but `overlay use` would
keep loading the value from the first activation. You can easily test it
by defining a module
```
module prompt {
export-env {
let-env PROMPT_COMMAND = (date now | into string)
}
}
```
Calling `overlay use prompt` for the first time changes the prompt to
the current time, however, subsequent calls of `overlay use` won't
change the time. That's because overlays, once activated, store their
state so they can be hidden and restored at later time. To force-reload
the environment, use the new flag: Calling `overlay use --reload prompt`
repeatedly now updates the prompt with the current time each time.
# User-Facing Changes
* When calling `overlay use`, if the module has an `export-env` block,
the block will see the environment as it is _before_ the overlay is
activated. Previously, it was _after_.
* A new `overlay use --reload` flag.
# 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 22:45:24 +00:00
|
|
|
(vec![], vec![])
|
2022-09-04 15:36:42 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
working_set.add_overlay(
|
|
|
|
final_overlay_name.as_bytes().to_vec(),
|
|
|
|
origin_module_id,
|
|
|
|
decls_to_lay,
|
|
|
|
aliases_to_lay,
|
|
|
|
has_prefix,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Change the call argument to include the Overlay expression with the module ID
|
|
|
|
let mut call = call;
|
2022-12-22 14:36:13 +00:00
|
|
|
call.add_parser_info(Expression {
|
|
|
|
expr: Expr::Overlay(if is_module_updated {
|
2022-09-04 15:36:42 +00:00
|
|
|
Some(origin_module_id)
|
2022-05-07 19:39:22 +00:00
|
|
|
} else {
|
2022-09-04 15:36:42 +00:00
|
|
|
None
|
2022-12-22 14:36:13 +00:00
|
|
|
}),
|
|
|
|
span: overlay_name_span,
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
});
|
2022-05-07 19:39:22 +00:00
|
|
|
|
2022-09-04 15:36:42 +00:00
|
|
|
let pipeline = Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
2023-03-10 21:20:31 +00:00
|
|
|
span: call_span,
|
2022-09-04 15:36:42 +00:00
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]);
|
2022-05-07 19:39:22 +00:00
|
|
|
|
|
|
|
(pipeline, error)
|
|
|
|
}
|
|
|
|
|
2022-08-21 14:27:56 +00:00
|
|
|
pub fn parse_overlay_hide(
|
2022-05-07 19:39:22 +00:00
|
|
|
working_set: &mut StateWorkingSet,
|
2023-03-10 21:20:31 +00:00
|
|
|
call: Box<Call>,
|
2022-05-07 19:39:22 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2023-03-10 21:20:31 +00:00
|
|
|
let call_span = call.span();
|
2022-05-07 19:39:22 +00:00
|
|
|
|
|
|
|
let (overlay_name, overlay_name_span) = if let Some(expr) = call.positional_nth(0) {
|
2023-01-11 22:18:06 +00:00
|
|
|
match eval_constant(working_set, expr) {
|
|
|
|
Ok(val) => match value_as_string(val, expr.span) {
|
|
|
|
Ok(s) => (s, expr.span),
|
|
|
|
Err(err) => {
|
2023-03-10 21:20:31 +00:00
|
|
|
return (garbage_pipeline(&[call_span]), Some(err));
|
2023-01-11 22:18:06 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
Err(err) => {
|
2023-03-10 21:20:31 +00:00
|
|
|
return (garbage_pipeline(&[call_span]), Some(err));
|
2023-01-11 22:18:06 +00:00
|
|
|
}
|
2022-05-07 19:39:22 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
String::from_utf8_lossy(working_set.last_overlay_name()).to_string(),
|
2023-03-10 21:20:31 +00:00
|
|
|
call_span,
|
2022-05-07 19:39:22 +00:00
|
|
|
)
|
|
|
|
};
|
|
|
|
|
2022-05-24 21:22:17 +00:00
|
|
|
let keep_custom = call.has_flag("keep-custom");
|
|
|
|
|
2022-05-07 19:39:22 +00:00
|
|
|
let pipeline = Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
2023-03-10 21:20:31 +00:00
|
|
|
span: call_span,
|
2022-05-07 19:39:22 +00:00
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]);
|
|
|
|
|
|
|
|
if overlay_name == DEFAULT_OVERLAY_NAME {
|
|
|
|
return (
|
|
|
|
pipeline,
|
2022-08-21 14:27:56 +00:00
|
|
|
Some(ParseError::CantHideDefaultOverlay(
|
2022-05-07 19:39:22 +00:00
|
|
|
overlay_name,
|
|
|
|
overlay_name_span,
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if !working_set
|
|
|
|
.unique_overlay_names()
|
|
|
|
.contains(&overlay_name.as_bytes().to_vec())
|
|
|
|
{
|
|
|
|
return (
|
|
|
|
pipeline,
|
|
|
|
Some(ParseError::ActiveOverlayNotFound(overlay_name_span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if working_set.num_overlays() < 2 {
|
|
|
|
return (
|
|
|
|
pipeline,
|
|
|
|
Some(ParseError::CantRemoveLastOverlay(overlay_name_span)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-05-24 21:22:17 +00:00
|
|
|
working_set.remove_overlay(overlay_name.as_bytes(), keep_custom);
|
2022-05-07 19:39:22 +00:00
|
|
|
|
|
|
|
(pipeline, None)
|
|
|
|
}
|
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
pub fn parse_let_or_const(
|
2021-09-26 18:39:19 +00:00
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2021-09-26 18:39:19 +00:00
|
|
|
let name = working_set.get_span_contents(spans[0]);
|
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
if name == b"let" || name == b"const" {
|
|
|
|
let is_const = &name == b"const";
|
|
|
|
|
2021-09-26 18:39:19 +00:00
|
|
|
if let Some((span, err)) = check_name(working_set, spans) {
|
2022-02-15 19:31:14 +00:00
|
|
|
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
if let Some(decl_id) =
|
|
|
|
working_set.find_decl(if is_const { b"const" } else { b"let" }, &Type::Any)
|
|
|
|
{
|
2022-01-03 23:14:33 +00:00
|
|
|
let cmd = working_set.get_decl(decl_id);
|
|
|
|
let call_signature = cmd.signature().call_signature();
|
|
|
|
|
2021-12-15 22:56:12 +00:00
|
|
|
if spans.len() >= 4 {
|
|
|
|
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
|
|
|
|
// so that the var-id created by the variable isn't visible in the expression that init it
|
|
|
|
for span in spans.iter().enumerate() {
|
|
|
|
let item = working_set.get_span_contents(*span.1);
|
|
|
|
if item == b"=" && spans.len() > (span.0 + 1) {
|
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
let mut idx = span.0;
|
|
|
|
let (rvalue, err) = parse_multispan_value(
|
|
|
|
working_set,
|
|
|
|
spans,
|
|
|
|
&mut idx,
|
|
|
|
&SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist,
|
2021-12-15 22:56:12 +00:00
|
|
|
);
|
|
|
|
error = error.or(err);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2021-12-27 03:04:22 +00:00
|
|
|
if idx < (spans.len() - 1) {
|
2022-01-03 23:14:33 +00:00
|
|
|
error = error.or(Some(ParseError::ExtraPositional(
|
|
|
|
call_signature,
|
|
|
|
spans[idx + 1],
|
|
|
|
)));
|
2021-12-27 03:04:22 +00:00
|
|
|
}
|
|
|
|
|
2021-12-15 22:56:12 +00:00
|
|
|
let mut idx = 0;
|
2022-11-11 06:51:08 +00:00
|
|
|
let (lvalue, err) = parse_var_with_opt_type(
|
|
|
|
working_set,
|
|
|
|
&spans[1..(span.0)],
|
|
|
|
&mut idx,
|
|
|
|
false,
|
|
|
|
);
|
2021-12-15 22:56:12 +00:00
|
|
|
error = error.or(err);
|
|
|
|
|
2022-06-24 21:55:25 +00:00
|
|
|
let var_name =
|
|
|
|
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
if ["in", "nu", "env", "nothing"].contains(&var_name.as_str()) {
|
2023-01-19 23:11:48 +00:00
|
|
|
error = if is_const {
|
|
|
|
error.or(Some(ParseError::ConstBuiltinVar(var_name, lvalue.span)))
|
|
|
|
} else {
|
|
|
|
error.or(Some(ParseError::LetBuiltinVar(var_name, lvalue.span)))
|
|
|
|
};
|
2022-06-24 21:55:25 +00:00
|
|
|
}
|
|
|
|
|
2021-12-15 22:56:12 +00:00
|
|
|
let var_id = lvalue.as_var();
|
|
|
|
let rhs_type = rvalue.ty.clone();
|
|
|
|
|
|
|
|
if let Some(var_id) = var_id {
|
2022-04-18 22:28:01 +00:00
|
|
|
working_set.set_variable_type(var_id, rhs_type);
|
2022-12-21 22:21:03 +00:00
|
|
|
|
|
|
|
if is_const {
|
|
|
|
match eval_constant(working_set, &rvalue) {
|
|
|
|
Ok(val) => {
|
|
|
|
working_set.add_constant(var_id, val);
|
|
|
|
}
|
|
|
|
Err(err) => error = error.or(Some(err)),
|
|
|
|
}
|
|
|
|
}
|
2021-12-15 22:56:12 +00:00
|
|
|
}
|
2021-09-26 18:39:19 +00:00
|
|
|
|
2021-12-15 22:56:12 +00:00
|
|
|
let call = Box::new(Call {
|
|
|
|
decl_id,
|
|
|
|
head: spans[0],
|
2022-04-09 02:55:02 +00:00
|
|
|
arguments: vec![
|
|
|
|
Argument::Positional(lvalue),
|
|
|
|
Argument::Positional(rvalue),
|
|
|
|
],
|
2022-02-21 22:22:21 +00:00
|
|
|
redirect_stdout: true,
|
|
|
|
redirect_stderr: false,
|
2022-12-22 14:36:13 +00:00
|
|
|
parser_info: vec![],
|
2021-12-15 22:56:12 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-12-15 22:56:12 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: nu_protocol::span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-12-15 22:56:12 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-12-15 22:56:12 +00:00
|
|
|
error,
|
|
|
|
);
|
|
|
|
}
|
2021-11-14 19:25:57 +00:00
|
|
|
}
|
2021-09-26 18:39:19 +00:00
|
|
|
}
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&spans[1..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-09-26 18:39:19 +00:00
|
|
|
|
|
|
|
return (
|
2022-11-18 21:46:48 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: nu_protocol::span(spans),
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
2021-09-26 18:39:19 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-09-26 18:39:19 +00:00
|
|
|
Some(ParseError::UnknownState(
|
Fix typos by codespell (#7600)
# Description
Found via `codespell -S target -L
crate,ser,numer,falsy,ro,te,nd,bu,ndoes,statics,ons,fo,rouge,pard`
# User-Facing Changes
None.
# Tests + Formatting
None and done.
# After Submitting
None.
2022-12-26 07:31:26 +00:00
|
|
|
"internal error: let or const statement unparsable".into(),
|
2021-09-26 18:39:19 +00:00
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
2021-10-02 02:24:43 +00:00
|
|
|
|
2022-11-11 06:51:08 +00:00
|
|
|
pub fn parse_mut(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
|
|
|
let name = working_set.get_span_contents(spans[0]);
|
|
|
|
|
|
|
|
if name == b"mut" {
|
|
|
|
if let Some((span, err)) = check_name(working_set, spans) {
|
|
|
|
return (Pipeline::from_vec(vec![garbage(*span)]), Some(err));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_decl(b"mut", &Type::Any) {
|
|
|
|
let cmd = working_set.get_decl(decl_id);
|
|
|
|
let call_signature = cmd.signature().call_signature();
|
|
|
|
|
|
|
|
if spans.len() >= 4 {
|
|
|
|
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
|
|
|
|
// so that the var-id created by the variable isn't visible in the expression that init it
|
|
|
|
for span in spans.iter().enumerate() {
|
|
|
|
let item = working_set.get_span_contents(*span.1);
|
|
|
|
if item == b"=" && spans.len() > (span.0 + 1) {
|
|
|
|
let mut error = None;
|
|
|
|
|
|
|
|
let mut idx = span.0;
|
|
|
|
let (rvalue, err) = parse_multispan_value(
|
|
|
|
working_set,
|
|
|
|
spans,
|
|
|
|
&mut idx,
|
|
|
|
&SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Expression)),
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
if idx < (spans.len() - 1) {
|
|
|
|
error = error.or(Some(ParseError::ExtraPositional(
|
|
|
|
call_signature,
|
|
|
|
spans[idx + 1],
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut idx = 0;
|
|
|
|
let (lvalue, err) = parse_var_with_opt_type(
|
|
|
|
working_set,
|
|
|
|
&spans[1..(span.0)],
|
|
|
|
&mut idx,
|
|
|
|
true,
|
|
|
|
);
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let var_name =
|
|
|
|
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
if ["in", "nu", "env", "nothing"].contains(&var_name.as_str()) {
|
|
|
|
error =
|
|
|
|
error.or(Some(ParseError::MutBuiltinVar(var_name, lvalue.span)));
|
|
|
|
}
|
|
|
|
|
|
|
|
let var_id = lvalue.as_var();
|
|
|
|
let rhs_type = rvalue.ty.clone();
|
|
|
|
|
|
|
|
if let Some(var_id) = var_id {
|
|
|
|
working_set.set_variable_type(var_id, rhs_type);
|
|
|
|
}
|
|
|
|
|
|
|
|
let call = Box::new(Call {
|
|
|
|
decl_id,
|
|
|
|
head: spans[0],
|
|
|
|
arguments: vec![
|
|
|
|
Argument::Positional(lvalue),
|
|
|
|
Argument::Positional(rvalue),
|
|
|
|
],
|
|
|
|
redirect_stdout: true,
|
|
|
|
redirect_stderr: false,
|
2022-12-22 14:36:13 +00:00
|
|
|
parser_info: vec![],
|
2022-11-11 06:51:08 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: nu_protocol::span(spans),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
error,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&spans[1..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
return (
|
2022-11-18 21:46:48 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: nu_protocol::span(spans),
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
2022-11-11 06:51:08 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(
|
|
|
|
garbage_pipeline(spans),
|
|
|
|
Some(ParseError::UnknownState(
|
Fix typos by codespell (#7600)
# Description
Found via `codespell -S target -L
crate,ser,numer,falsy,ro,te,nd,bu,ndoes,statics,ons,fo,rouge,pard`
# User-Facing Changes
None.
# Tests + Formatting
None and done.
# After Submitting
None.
2022-12-26 07:31:26 +00:00
|
|
|
"internal error: mut statement unparsable".into(),
|
2022-11-11 06:51:08 +00:00
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-10-02 02:24:43 +00:00
|
|
|
pub fn parse_source(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2021-12-03 19:49:11 +00:00
|
|
|
let mut error = None;
|
2021-10-02 02:24:43 +00:00
|
|
|
let name = working_set.get_span_contents(spans[0]);
|
|
|
|
|
2022-09-08 20:41:49 +00:00
|
|
|
if name == b"source" || name == b"source-env" {
|
|
|
|
let scoped = name == b"source-env";
|
|
|
|
|
|
|
|
if let Some(decl_id) = working_set.find_decl(name, &Type::Any) {
|
2022-01-05 00:26:01 +00:00
|
|
|
let cwd = working_set.get_cwd();
|
2022-07-27 15:36:56 +00:00
|
|
|
|
2021-10-06 02:03:18 +00:00
|
|
|
// Is this the right call to be using here?
|
|
|
|
// Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't.
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&spans[1..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-12-03 19:49:11 +00:00
|
|
|
error = error.or(err);
|
2021-10-02 02:24:43 +00:00
|
|
|
|
2022-02-24 15:32:10 +00:00
|
|
|
if error.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
2022-06-12 19:18:00 +00:00
|
|
|
ty: output,
|
2022-02-24 15:32:10 +00:00
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
error,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-10-02 02:24:43 +00:00
|
|
|
// Command and one file name
|
|
|
|
if spans.len() >= 2 {
|
2022-12-21 22:21:03 +00:00
|
|
|
let (expr, err) = parse_value(
|
|
|
|
working_set,
|
|
|
|
spans[1],
|
|
|
|
&SyntaxShape::Any,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
error = error.or(err);
|
|
|
|
|
|
|
|
let val = match eval_constant(working_set, &expr) {
|
|
|
|
Ok(val) => val,
|
|
|
|
Err(err) => {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(&spans[1..]),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
Some(err),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let filename = match value_as_string(val, spans[1]) {
|
|
|
|
Ok(s) => s,
|
|
|
|
Err(err) => {
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(&spans[1..]),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
Some(err),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_ENV) {
|
|
|
|
if let Ok(contents) = std::fs::read(&path) {
|
|
|
|
// Change currently parsed directory
|
|
|
|
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
|
|
|
|
let prev = working_set.currently_parsed_cwd.clone();
|
|
|
|
|
|
|
|
working_set.currently_parsed_cwd = Some(parent.into());
|
|
|
|
|
|
|
|
prev
|
|
|
|
} else {
|
|
|
|
working_set.currently_parsed_cwd.clone()
|
|
|
|
};
|
|
|
|
|
|
|
|
// This will load the defs from the file into the
|
|
|
|
// working set, if it was a successful parse.
|
|
|
|
let (block, err) = parse(
|
|
|
|
working_set,
|
|
|
|
path.file_name().and_then(|x| x.to_str()),
|
|
|
|
&contents,
|
|
|
|
scoped,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Restore the currently parsed directory back
|
|
|
|
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
|
|
|
|
|
|
|
|
if err.is_some() {
|
|
|
|
// Unsuccessful parse of file
|
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(&spans[1..]),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
}]),
|
|
|
|
// Return the file parse error
|
|
|
|
err,
|
2021-10-06 01:59:16 +00:00
|
|
|
);
|
2022-12-21 22:21:03 +00:00
|
|
|
} else {
|
|
|
|
// Save the block into the working set
|
|
|
|
let block_id = working_set.add_block(block);
|
2021-10-06 01:59:16 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
let mut call_with_block = call;
|
2021-12-03 19:49:11 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
// FIXME: Adding this expression to the positional creates a syntax highlighting error
|
|
|
|
// after writing `source example.nu`
|
2022-12-22 14:36:13 +00:00
|
|
|
call_with_block.add_parser_info(Expression {
|
2022-12-21 22:21:03 +00:00
|
|
|
expr: Expr::Int(block_id as i64),
|
|
|
|
span: spans[1],
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
});
|
2021-12-03 19:49:11 +00:00
|
|
|
|
2022-12-21 22:21:03 +00:00
|
|
|
return (
|
|
|
|
Pipeline::from_vec(vec![Expression {
|
|
|
|
expr: Expr::Call(call_with_block),
|
|
|
|
span: span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-10-06 01:59:16 +00:00
|
|
|
custom_completion: None,
|
2022-12-21 22:21:03 +00:00
|
|
|
}]),
|
|
|
|
None,
|
|
|
|
);
|
2021-10-02 02:24:43 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-31 15:53:53 +00:00
|
|
|
} else {
|
2022-12-21 22:21:03 +00:00
|
|
|
error = error.or(Some(ParseError::SourcedFileNotFound(filename, spans[1])));
|
2021-10-02 02:24:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-10-02 02:24:43 +00:00
|
|
|
expr: Expr::Call(call),
|
2021-12-18 20:10:40 +00:00
|
|
|
span: span(spans),
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-10-02 02:24:43 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-12-03 19:49:11 +00:00
|
|
|
error,
|
2021-10-02 02:24:43 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-10-02 02:24:43 +00:00
|
|
|
Some(ParseError::UnknownState(
|
Fix typos by codespell (#7600)
# Description
Found via `codespell -S target -L
crate,ser,numer,falsy,ro,te,nd,bu,ndoes,statics,ons,fo,rouge,pard`
# User-Facing Changes
None.
# Tests + Formatting
None and done.
# After Submitting
None.
2022-12-26 07:31:26 +00:00
|
|
|
"internal error: source statement unparsable".into(),
|
2021-10-02 02:24:43 +00:00
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
2021-10-31 08:17:01 +00:00
|
|
|
|
2022-12-10 17:23:24 +00:00
|
|
|
pub fn parse_where_expr(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> (Expression, Option<ParseError>) {
|
|
|
|
trace!("parsing: where");
|
|
|
|
|
|
|
|
if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"where" {
|
|
|
|
return (
|
|
|
|
garbage(span(spans)),
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for 'where' command".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if spans.len() < 2 {
|
|
|
|
return (
|
|
|
|
garbage(span(spans)),
|
|
|
|
Some(ParseError::MissingPositional(
|
|
|
|
"row condition".into(),
|
|
|
|
span(spans),
|
|
|
|
"where <row_condition>".into(),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let call = match working_set.find_decl(b"where", &Type::Any) {
|
|
|
|
Some(decl_id) => {
|
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&spans[1..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
|
|
|
let decl = working_set.get_decl(decl_id);
|
|
|
|
|
|
|
|
let call_span = span(spans);
|
|
|
|
|
|
|
|
err = check_call(call_span, &decl.signature(), &call).or(err);
|
|
|
|
if err.is_some() || call.has_flag("help") {
|
|
|
|
return (
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: output,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
call
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
return (
|
|
|
|
garbage(span(spans)),
|
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: 'where' declaration not found".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
(
|
|
|
|
Expression {
|
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: span(spans),
|
|
|
|
ty: Type::Any,
|
|
|
|
custom_completion: None,
|
|
|
|
},
|
|
|
|
None,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn parse_where(
|
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
|
|
|
expand_aliases_denylist: &[usize],
|
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
|
|
|
let (expression, err) = parse_where_expr(working_set, spans, expand_aliases_denylist);
|
|
|
|
(Pipeline::from_vec(vec![expression]), err)
|
|
|
|
}
|
|
|
|
|
2021-11-02 20:56:00 +00:00
|
|
|
#[cfg(feature = "plugin")]
|
2021-12-03 14:29:55 +00:00
|
|
|
pub fn parse_register(
|
2021-10-31 08:17:01 +00:00
|
|
|
working_set: &mut StateWorkingSet,
|
|
|
|
spans: &[Span],
|
2022-03-18 19:03:57 +00:00
|
|
|
expand_aliases_denylist: &[usize],
|
2022-02-15 19:31:14 +00:00
|
|
|
) -> (Pipeline, Option<ParseError>) {
|
2022-09-07 14:07:42 +00:00
|
|
|
use nu_plugin::{get_signature, PluginDeclaration};
|
Make plugin commands support examples. (#7984)
# Description
As title, we can't provide examples for plugin commands, this pr would
make it possible
# User-Facing Changes
Take plugin `nu-example-1` as example:
```
❯ nu-example-1 -h
PluginSignature test 1 for plugin. Returns Value::Nothing
Usage:
> nu-example-1 {flags} <a> <b> (opt) ...(rest)
Flags:
-h, --help - Display the help message for this command
-f, --flag - a flag for the signature
-n, --named <String> - named string
Parameters:
a <int>: required integer value
b <string>: required string value
(optional) opt <int>: Optional number
...rest <string>: rest value string
Examples:
running example with an int value and string value
> nu-example-1 3 bb
```
The examples session is newly added.
## Basic idea behind these changes
when nushell query plugin signatures, plugin just returns it's signature
without any examples, so nushell have no idea about the examples of
plugin commands.
To adding the feature, we just making plugin returns it's signature with
examples.
Before:
```
1. get signature
---------------->
Nushell ------------------ Plugin
<-----------------
2. returns Vec<Signature>
```
After:
```
1. get signature
---------------->
Nushell ------------------ Plugin
<-----------------
2. returns Vec<PluginSignature>
```
When writing plugin signature to $nu.plugin-path:
Serialize `<PluginSignature>` rather than `<Signature>`, which would
enable us to serialize examples to `$nu.plugin-path`
## Shortcoming
It's a breaking changes because `Plugin::signature` is changed, and it
requires plugin authors to change their code for new signatures.
Fortunally it should be easy to change, for rust based plugin, we just
need to make a global replace from word `Signature` to word
`PluginSignature` in their plugin project.
Our content of plugin-path is really large, if one plugin have many
examples, it'd results to larger body of $nu.plugin-path, which is not
really scale. A solution would be save register information in other
binary formats rather than `json`. But I think it'd be another story.
# 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-02-08 22:14:18 +00:00
|
|
|
use nu_protocol::{engine::Stack, PluginSignature};
|
2022-08-08 12:26:49 +00:00
|
|
|
|
2022-01-05 00:26:01 +00:00
|
|
|
let cwd = working_set.get_cwd();
|
2021-11-19 02:51:42 +00:00
|
|
|
|
2021-12-12 11:50:35 +00:00
|
|
|
// Checking that the function is used with the correct name
|
|
|
|
// Maybe this is not necessary but it is a sanity check
|
|
|
|
if working_set.get_span_contents(spans[0]) != b"register" {
|
2021-10-31 08:17:01 +00:00
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-10-31 08:17:01 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Wrong call name for parse plugin function".into(),
|
|
|
|
span(spans),
|
|
|
|
)),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-12-12 11:50:35 +00:00
|
|
|
// Parsing the spans and checking that they match the register signature
|
|
|
|
// Using a parsed call makes more sense than checking for how many spans are in the call
|
|
|
|
// Also, by creating a call, it can be checked if it matches the declaration signature
|
2022-06-10 15:59:35 +00:00
|
|
|
let (call, call_span) = match working_set.find_decl(b"register", &Type::Any) {
|
2021-12-12 11:50:35 +00:00
|
|
|
None => {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
garbage_pipeline(spans),
|
2021-12-12 11:50:35 +00:00
|
|
|
Some(ParseError::UnknownState(
|
|
|
|
"internal error: Register declaration not found".into(),
|
|
|
|
span(spans),
|
2021-10-31 08:17:01 +00:00
|
|
|
)),
|
2021-12-12 11:50:35 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
Some(decl_id) => {
|
2022-06-12 19:18:00 +00:00
|
|
|
let ParsedInternalCall {
|
|
|
|
call,
|
|
|
|
error: mut err,
|
|
|
|
output,
|
|
|
|
} = parse_internal_call(
|
2022-03-18 19:03:57 +00:00
|
|
|
working_set,
|
|
|
|
spans[0],
|
|
|
|
&spans[1..],
|
|
|
|
decl_id,
|
|
|
|
expand_aliases_denylist,
|
|
|
|
);
|
2021-12-12 11:50:35 +00:00
|
|
|
let decl = working_set.get_decl(decl_id);
|
2021-12-03 14:29:55 +00:00
|
|
|
|
2021-12-18 20:10:40 +00:00
|
|
|
let call_span = span(spans);
|
|
|
|
|
2021-12-12 11:50:35 +00:00
|
|
|
err = check_call(call_span, &decl.signature(), &call).or(err);
|
2021-12-12 14:00:07 +00:00
|
|
|
if err.is_some() || call.has_flag("help") {
|
2021-12-12 11:50:35 +00:00
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-12-12 11:50:35 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-06-12 19:18:00 +00:00
|
|
|
ty: output,
|
2021-12-12 11:50:35 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-12-12 11:50:35 +00:00
|
|
|
err,
|
|
|
|
);
|
|
|
|
}
|
2021-12-03 14:29:55 +00:00
|
|
|
|
2021-12-12 11:50:35 +00:00
|
|
|
(call, call_span)
|
|
|
|
}
|
|
|
|
};
|
2021-12-03 14:29:55 +00:00
|
|
|
|
2021-12-12 11:50:35 +00:00
|
|
|
// Extracting the required arguments from the call and keeping them together in a tuple
|
|
|
|
// The ? operator is not used because the error has to be kept to be printed in the shell
|
|
|
|
// For that reason the values are kept in a result that will be passed at the end of this call
|
|
|
|
let arguments = call
|
2022-04-09 02:55:02 +00:00
|
|
|
.positional_nth(0)
|
2021-12-12 11:50:35 +00:00
|
|
|
.map(|expr| {
|
|
|
|
let name_expr = working_set.get_span_contents(expr.span);
|
2022-03-09 12:06:44 +00:00
|
|
|
|
2022-05-01 18:37:20 +00:00
|
|
|
let (name, err) = unescape_unquote_string(name_expr, expr.span);
|
|
|
|
|
|
|
|
if let Some(err) = err {
|
|
|
|
Err(err)
|
|
|
|
} else {
|
|
|
|
let path = if let Some(p) = find_in_dirs(&name, working_set, &cwd, PLUGIN_DIRS_ENV)
|
|
|
|
{
|
|
|
|
p
|
|
|
|
} else {
|
|
|
|
return Err(ParseError::RegisteredFileNotFound(name, expr.span));
|
|
|
|
};
|
|
|
|
|
|
|
|
if path.exists() & path.is_file() {
|
|
|
|
Ok(path)
|
|
|
|
} else {
|
|
|
|
Err(ParseError::RegisteredFileNotFound(
|
2023-01-30 01:37:54 +00:00
|
|
|
format!("{path:?}"),
|
2022-05-01 18:37:20 +00:00
|
|
|
expr.span,
|
|
|
|
))
|
|
|
|
}
|
|
|
|
}
|
2021-12-12 11:50:35 +00:00
|
|
|
})
|
2022-09-07 14:07:42 +00:00
|
|
|
.expect("required positional has being checked");
|
2021-10-31 08:17:01 +00:00
|
|
|
|
2021-12-18 18:13:56 +00:00
|
|
|
// Signature is an optional value from the call and will be used to decide if
|
2021-12-12 11:50:35 +00:00
|
|
|
// the plugin is called to get the signatures or to use the given signature
|
2022-04-09 02:55:02 +00:00
|
|
|
let signature = call.positional_nth(1).map(|expr| {
|
2021-12-12 11:50:35 +00:00
|
|
|
let signature = working_set.get_span_contents(expr.span);
|
Make plugin commands support examples. (#7984)
# Description
As title, we can't provide examples for plugin commands, this pr would
make it possible
# User-Facing Changes
Take plugin `nu-example-1` as example:
```
❯ nu-example-1 -h
PluginSignature test 1 for plugin. Returns Value::Nothing
Usage:
> nu-example-1 {flags} <a> <b> (opt) ...(rest)
Flags:
-h, --help - Display the help message for this command
-f, --flag - a flag for the signature
-n, --named <String> - named string
Parameters:
a <int>: required integer value
b <string>: required string value
(optional) opt <int>: Optional number
...rest <string>: rest value string
Examples:
running example with an int value and string value
> nu-example-1 3 bb
```
The examples session is newly added.
## Basic idea behind these changes
when nushell query plugin signatures, plugin just returns it's signature
without any examples, so nushell have no idea about the examples of
plugin commands.
To adding the feature, we just making plugin returns it's signature with
examples.
Before:
```
1. get signature
---------------->
Nushell ------------------ Plugin
<-----------------
2. returns Vec<Signature>
```
After:
```
1. get signature
---------------->
Nushell ------------------ Plugin
<-----------------
2. returns Vec<PluginSignature>
```
When writing plugin signature to $nu.plugin-path:
Serialize `<PluginSignature>` rather than `<Signature>`, which would
enable us to serialize examples to `$nu.plugin-path`
## Shortcoming
It's a breaking changes because `Plugin::signature` is changed, and it
requires plugin authors to change their code for new signatures.
Fortunally it should be easy to change, for rust based plugin, we just
need to make a global replace from word `Signature` to word
`PluginSignature` in their plugin project.
Our content of plugin-path is really large, if one plugin have many
examples, it'd results to larger body of $nu.plugin-path, which is not
really scale. A solution would be save register information in other
binary formats rather than `json`. But I think it'd be another story.
# 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-02-08 22:14:18 +00:00
|
|
|
serde_json::from_slice::<PluginSignature>(signature).map_err(|e| {
|
2021-12-12 11:50:35 +00:00
|
|
|
ParseError::LabeledError(
|
|
|
|
"Signature deserialization error".into(),
|
2023-01-30 01:37:54 +00:00
|
|
|
format!("unable to deserialize signature: {e}"),
|
2021-12-12 11:50:35 +00:00
|
|
|
spans[0],
|
|
|
|
)
|
|
|
|
})
|
|
|
|
});
|
2021-10-31 08:17:01 +00:00
|
|
|
|
2021-12-18 18:13:56 +00:00
|
|
|
// Shell is another optional value used as base to call shell to plugins
|
|
|
|
let shell = call.get_flag_expr("shell").map(|expr| {
|
|
|
|
let shell_expr = working_set.get_span_contents(expr.span);
|
|
|
|
|
|
|
|
String::from_utf8(shell_expr.to_vec())
|
|
|
|
.map_err(|_| ParseError::NonUtf8(expr.span))
|
|
|
|
.and_then(|name| {
|
2022-03-25 21:43:46 +00:00
|
|
|
canonicalize_with(&name, cwd)
|
|
|
|
.map_err(|_| ParseError::RegisteredFileNotFound(name, expr.span))
|
2021-12-18 18:13:56 +00:00
|
|
|
})
|
|
|
|
.and_then(|path| {
|
|
|
|
if path.exists() & path.is_file() {
|
|
|
|
Ok(path)
|
|
|
|
} else {
|
2022-03-25 21:43:46 +00:00
|
|
|
Err(ParseError::RegisteredFileNotFound(
|
2023-01-30 01:37:54 +00:00
|
|
|
format!("{path:?}"),
|
2022-03-25 21:43:46 +00:00
|
|
|
expr.span,
|
|
|
|
))
|
2021-12-18 18:13:56 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
});
|
|
|
|
|
|
|
|
let shell = match shell {
|
|
|
|
None => None,
|
|
|
|
Some(path) => match path {
|
|
|
|
Ok(path) => Some(path),
|
|
|
|
Err(err) => {
|
|
|
|
return (
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-12-18 18:13:56 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
2022-04-07 04:34:09 +00:00
|
|
|
ty: Type::Any,
|
2021-12-18 18:13:56 +00:00
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-12-18 18:13:56 +00:00
|
|
|
Some(err),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2022-08-08 12:26:49 +00:00
|
|
|
// We need the current environment variables for `python` based plugins
|
|
|
|
// Or we'll likely have a problem when a plugin is implemented in a virtual Python environment.
|
|
|
|
let stack = Stack::new();
|
|
|
|
let current_envs =
|
|
|
|
nu_engine::env::env_to_strings(working_set.permanent_state, &stack).unwrap_or_default();
|
2021-12-12 11:50:35 +00:00
|
|
|
let error = match signature {
|
2022-09-07 14:07:42 +00:00
|
|
|
Some(signature) => arguments.and_then(|path| {
|
2022-09-04 23:00:20 +00:00
|
|
|
// restrict plugin file name starts with `nu_plugin_`
|
|
|
|
let f_name = path
|
|
|
|
.file_name()
|
|
|
|
.map(|s| s.to_string_lossy().starts_with("nu_plugin_"));
|
|
|
|
|
|
|
|
if let Some(true) = f_name {
|
|
|
|
signature.map(|signature| {
|
2022-09-07 14:07:42 +00:00
|
|
|
let plugin_decl = PluginDeclaration::new(path, signature, shell);
|
2022-09-04 23:00:20 +00:00
|
|
|
working_set.add_decl(Box::new(plugin_decl));
|
|
|
|
working_set.mark_plugins_file_dirty();
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-12 11:50:35 +00:00
|
|
|
}),
|
2022-09-07 14:07:42 +00:00
|
|
|
None => arguments.and_then(|path| {
|
2022-09-04 23:00:20 +00:00
|
|
|
// restrict plugin file name starts with `nu_plugin_`
|
|
|
|
let f_name = path
|
|
|
|
.file_name()
|
|
|
|
.map(|s| s.to_string_lossy().starts_with("nu_plugin_"));
|
|
|
|
|
|
|
|
if let Some(true) = f_name {
|
2022-09-07 14:07:42 +00:00
|
|
|
get_signature(path.as_path(), &shell, ¤t_envs)
|
2022-09-04 23:00:20 +00:00
|
|
|
.map_err(|err| {
|
|
|
|
ParseError::LabeledError(
|
|
|
|
"Error getting signatures".into(),
|
|
|
|
err.to_string(),
|
|
|
|
spans[0],
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.map(|signatures| {
|
|
|
|
for signature in signatures {
|
|
|
|
// create plugin command declaration (need struct impl Command)
|
|
|
|
// store declaration in working set
|
2022-09-07 14:07:42 +00:00
|
|
|
let plugin_decl =
|
|
|
|
PluginDeclaration::new(path.clone(), signature, shell.clone());
|
2021-12-12 11:50:35 +00:00
|
|
|
|
2022-09-04 23:00:20 +00:00
|
|
|
working_set.add_decl(Box::new(plugin_decl));
|
|
|
|
}
|
2021-10-31 08:17:01 +00:00
|
|
|
|
2022-09-04 23:00:20 +00:00
|
|
|
working_set.mark_plugins_file_dirty();
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-12-12 11:50:35 +00:00
|
|
|
}),
|
2021-10-31 08:17:01 +00:00
|
|
|
}
|
2021-12-12 11:50:35 +00:00
|
|
|
.err();
|
|
|
|
|
|
|
|
(
|
2022-02-15 19:31:14 +00:00
|
|
|
Pipeline::from_vec(vec![Expression {
|
2021-12-12 11:50:35 +00:00
|
|
|
expr: Expr::Call(call),
|
|
|
|
span: call_span,
|
|
|
|
ty: Type::Nothing,
|
|
|
|
custom_completion: None,
|
2022-02-15 19:31:14 +00:00
|
|
|
}]),
|
2021-12-12 11:50:35 +00:00
|
|
|
error,
|
|
|
|
)
|
2021-10-31 08:17:01 +00:00
|
|
|
}
|
2022-03-12 20:12:15 +00:00
|
|
|
|
2022-07-27 15:36:56 +00:00
|
|
|
/// This helper function is used to find files during parsing
|
|
|
|
///
|
2022-07-29 20:42:00 +00:00
|
|
|
/// First, the actual current working directory is selected as
|
|
|
|
/// a) the directory of a file currently being parsed
|
|
|
|
/// b) current working directory (PWD)
|
2022-07-27 15:36:56 +00:00
|
|
|
///
|
2022-07-29 20:42:00 +00:00
|
|
|
/// Then, if the file is not found in the actual cwd, NU_LIB_DIRS is checked.
|
|
|
|
/// If there is a relative path in NU_LIB_DIRS, it is assumed to be relative to the actual cwd
|
|
|
|
/// determined in the first step.
|
|
|
|
///
|
|
|
|
/// Always returns an absolute path
|
2022-08-31 20:32:56 +00:00
|
|
|
pub fn find_in_dirs(
|
2022-03-12 20:12:15 +00:00
|
|
|
filename: &str,
|
|
|
|
working_set: &StateWorkingSet,
|
|
|
|
cwd: &str,
|
|
|
|
dirs_env: &str,
|
|
|
|
) -> Option<PathBuf> {
|
2022-07-29 20:42:00 +00:00
|
|
|
// Choose whether to use file-relative or PWD-relative path
|
|
|
|
let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd {
|
|
|
|
currently_parsed_cwd.as_path()
|
|
|
|
} else {
|
|
|
|
Path::new(cwd)
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(p) = canonicalize_with(filename, actual_cwd) {
|
2022-03-12 20:12:15 +00:00
|
|
|
Some(p)
|
|
|
|
} else {
|
|
|
|
let path = Path::new(filename);
|
|
|
|
|
|
|
|
if path.is_relative() {
|
2022-05-07 19:39:22 +00:00
|
|
|
if let Some(lib_dirs) = working_set.get_env_var(dirs_env) {
|
2022-03-12 20:12:15 +00:00
|
|
|
if let Ok(dirs) = lib_dirs.as_list() {
|
|
|
|
for lib_dir in dirs {
|
|
|
|
if let Ok(dir) = lib_dir.as_path() {
|
2022-07-29 20:42:00 +00:00
|
|
|
// make sure the dir is absolute path
|
2022-11-04 20:11:17 +00:00
|
|
|
if let Ok(dir_abs) = canonicalize_with(dir, actual_cwd) {
|
2022-03-12 20:12:15 +00:00
|
|
|
if let Ok(path) = canonicalize_with(filename, dir_abs) {
|
|
|
|
return Some(path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|