2020-09-19 23:29:51 +02:00
|
|
|
use crate::command_registry::CommandRegistry;
|
2020-05-12 03:00:55 +02:00
|
|
|
use crate::commands::WholeStreamCommand;
|
2020-04-15 07:43:23 +02:00
|
|
|
use crate::prelude::*;
|
2020-08-18 09:00:02 +02:00
|
|
|
use nu_data::config;
|
2020-04-15 07:43:23 +02:00
|
|
|
use nu_errors::ShellError;
|
2020-08-20 05:18:55 +02:00
|
|
|
use nu_parser::SignatureRegistry;
|
|
|
|
use nu_protocol::hir::{ClassifiedCommand, Expression, NamedValue, SpannedExpression, Variable};
|
2020-05-20 19:31:04 +02:00
|
|
|
use nu_protocol::{
|
2020-08-20 05:18:55 +02:00
|
|
|
hir::Block, CommandAction, NamedType, PositionalType, ReturnSuccess, Signature, SyntaxShape,
|
|
|
|
UntaggedValue, Value,
|
2020-05-20 19:31:04 +02:00
|
|
|
};
|
2020-04-27 04:04:54 +02:00
|
|
|
use nu_source::Tagged;
|
2020-08-20 05:18:55 +02:00
|
|
|
use std::collections::HashMap;
|
2020-04-15 07:43:23 +02:00
|
|
|
|
|
|
|
pub struct Alias;
|
|
|
|
|
2020-04-27 04:04:54 +02:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct AliasArgs {
|
|
|
|
pub name: Tagged<String>,
|
|
|
|
pub args: Vec<Value>,
|
|
|
|
pub block: Block,
|
2020-08-27 07:48:13 +02:00
|
|
|
pub infer: Option<bool>,
|
2020-05-20 19:31:04 +02:00
|
|
|
pub save: Option<bool>,
|
2020-04-27 04:04:54 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 10:22:52 +02:00
|
|
|
#[async_trait]
|
2020-04-27 04:04:54 +02:00
|
|
|
impl WholeStreamCommand for Alias {
|
2020-04-15 07:43:23 +02:00
|
|
|
fn name(&self) -> &str {
|
|
|
|
"alias"
|
|
|
|
}
|
|
|
|
|
|
|
|
fn signature(&self) -> Signature {
|
|
|
|
Signature::build("alias")
|
|
|
|
.required("name", SyntaxShape::String, "the name of the alias")
|
|
|
|
.required("args", SyntaxShape::Table, "the arguments to the alias")
|
2020-05-09 19:16:14 +02:00
|
|
|
.required(
|
|
|
|
"block",
|
|
|
|
SyntaxShape::Block,
|
|
|
|
"the block to run as the body of the alias",
|
|
|
|
)
|
2020-08-27 07:48:13 +02:00
|
|
|
.switch("infer", "infer argument types (experimental)", Some('i'))
|
2020-05-20 19:31:04 +02:00
|
|
|
.switch("save", "save the alias to your config", Some('s'))
|
2020-04-15 07:43:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn usage(&self) -> &str {
|
2020-05-09 19:16:14 +02:00
|
|
|
"Define a shortcut for another command."
|
2020-04-15 07:43:23 +02:00
|
|
|
}
|
|
|
|
|
2020-05-29 10:22:52 +02:00
|
|
|
async fn run(
|
2020-04-15 07:43:23 +02:00
|
|
|
&self,
|
2020-04-27 04:04:54 +02:00
|
|
|
args: CommandArgs,
|
|
|
|
registry: &CommandRegistry,
|
2020-04-15 07:43:23 +02:00
|
|
|
) -> Result<OutputStream, ShellError> {
|
2020-06-12 10:34:41 +02:00
|
|
|
alias(args, registry).await
|
2020-04-15 07:43:23 +02:00
|
|
|
}
|
2020-05-11 22:05:44 +02:00
|
|
|
|
2020-05-18 14:56:01 +02:00
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
|
|
vec![
|
2020-05-12 01:06:40 +02:00
|
|
|
Example {
|
|
|
|
description: "An alias without parameters",
|
|
|
|
example: "alias say-hi [] { echo 'Hello!' }",
|
2020-05-18 14:56:01 +02:00
|
|
|
result: None,
|
2020-05-12 01:06:40 +02:00
|
|
|
},
|
|
|
|
Example {
|
|
|
|
description: "An alias with a single parameter",
|
|
|
|
example: "alias l [x] { ls $x }",
|
2020-05-18 14:56:01 +02:00
|
|
|
result: None,
|
2020-05-12 01:06:40 +02:00
|
|
|
},
|
|
|
|
]
|
2020-05-11 22:05:44 +02:00
|
|
|
}
|
2020-04-15 07:43:23 +02:00
|
|
|
}
|
2020-04-27 04:04:54 +02:00
|
|
|
|
2020-06-12 10:34:41 +02:00
|
|
|
pub async fn alias(
|
|
|
|
args: CommandArgs,
|
|
|
|
registry: &CommandRegistry,
|
|
|
|
) -> Result<OutputStream, ShellError> {
|
2020-05-16 05:18:24 +02:00
|
|
|
let registry = registry.clone();
|
2020-06-12 10:34:41 +02:00
|
|
|
let mut raw_input = args.raw_input.clone();
|
|
|
|
let (
|
|
|
|
AliasArgs {
|
|
|
|
name,
|
|
|
|
args: list,
|
|
|
|
block,
|
2020-08-27 07:48:13 +02:00
|
|
|
infer,
|
2020-06-12 10:34:41 +02:00
|
|
|
save,
|
|
|
|
},
|
|
|
|
_ctx,
|
|
|
|
) = args.process(®istry).await?;
|
|
|
|
let mut processed_args: Vec<String> = vec![];
|
|
|
|
|
|
|
|
if let Some(true) = save {
|
2020-08-18 09:00:02 +02:00
|
|
|
let mut result = nu_data::config::read(name.clone().tag, &None)?;
|
2020-06-12 10:34:41 +02:00
|
|
|
|
|
|
|
// process the alias to remove the --save flag
|
|
|
|
let left_brace = raw_input.find('{').unwrap_or(0);
|
|
|
|
let right_brace = raw_input.rfind('}').unwrap_or_else(|| raw_input.len());
|
|
|
|
let left = raw_input[..left_brace]
|
2020-08-27 07:48:13 +02:00
|
|
|
.replace("--save", "") // TODO using regex (or reconstruct string from AST?)
|
|
|
|
.replace("-si", "-i")
|
|
|
|
.replace("-s ", "")
|
|
|
|
.replace("-is", "-i");
|
2020-06-12 10:34:41 +02:00
|
|
|
let right = raw_input[right_brace..]
|
|
|
|
.replace("--save", "")
|
2020-08-27 07:48:13 +02:00
|
|
|
.replace("-si", "-i")
|
|
|
|
.replace("-s ", "")
|
|
|
|
.replace("-is", "-i");
|
2020-06-12 10:34:41 +02:00
|
|
|
raw_input = format!("{}{}{}", left, &raw_input[left_brace..right_brace], right);
|
|
|
|
|
|
|
|
// create a value from raw_input alias
|
|
|
|
let alias: Value = raw_input.trim().to_string().into();
|
|
|
|
let alias_start = raw_input.find('[').unwrap_or(0); // used to check if the same alias already exists
|
|
|
|
|
|
|
|
// add to startup if alias doesn't exist and replce if it does
|
|
|
|
match result.get_mut("startup") {
|
|
|
|
Some(startup) => {
|
|
|
|
if let UntaggedValue::Table(ref mut commands) = startup.value {
|
|
|
|
if let Some(command) = commands.iter_mut().find(|command| {
|
|
|
|
let cmd_str = command.as_string().unwrap_or_default();
|
|
|
|
cmd_str.starts_with(&raw_input[..alias_start])
|
|
|
|
}) {
|
|
|
|
*command = alias;
|
|
|
|
} else {
|
|
|
|
commands.push(alias);
|
2020-05-20 19:31:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-12 10:34:41 +02:00
|
|
|
None => {
|
|
|
|
let table = UntaggedValue::table(&[alias]);
|
|
|
|
result.insert("startup".to_string(), table.into_value(Tag::default()));
|
|
|
|
}
|
2020-05-20 19:31:04 +02:00
|
|
|
}
|
2020-06-12 10:34:41 +02:00
|
|
|
config::write(&result, &None)?;
|
|
|
|
}
|
2020-05-20 19:31:04 +02:00
|
|
|
|
2020-06-12 10:34:41 +02:00
|
|
|
for item in list.iter() {
|
|
|
|
if let Ok(string) = item.as_string() {
|
|
|
|
processed_args.push(format!("${}", string));
|
|
|
|
} else {
|
|
|
|
return Err(ShellError::labeled_error(
|
|
|
|
"Expected a string",
|
|
|
|
"expected a string",
|
|
|
|
item.tag(),
|
|
|
|
));
|
2020-04-27 04:04:54 +02:00
|
|
|
}
|
2020-06-12 10:34:41 +02:00
|
|
|
}
|
2020-04-27 04:04:54 +02:00
|
|
|
|
2020-08-27 07:48:13 +02:00
|
|
|
if let Some(true) = infer {
|
|
|
|
Ok(OutputStream::one(ReturnSuccess::action(
|
|
|
|
CommandAction::AddAlias(
|
|
|
|
name.to_string(),
|
|
|
|
to_arg_shapes(processed_args, &block, ®istry)?,
|
|
|
|
block,
|
|
|
|
),
|
|
|
|
)))
|
|
|
|
} else {
|
|
|
|
Ok(OutputStream::one(ReturnSuccess::action(
|
|
|
|
CommandAction::AddAlias(
|
|
|
|
name.to_string(),
|
|
|
|
processed_args
|
|
|
|
.into_iter()
|
|
|
|
.map(|arg| (arg, SyntaxShape::Any))
|
|
|
|
.collect(),
|
|
|
|
block,
|
|
|
|
),
|
|
|
|
)))
|
|
|
|
}
|
2020-04-27 04:04:54 +02:00
|
|
|
}
|
2020-05-18 14:56:01 +02:00
|
|
|
|
2020-08-20 05:18:55 +02:00
|
|
|
fn to_arg_shapes(
|
|
|
|
args: Vec<String>,
|
|
|
|
block: &Block,
|
|
|
|
registry: &CommandRegistry,
|
|
|
|
) -> Result<Vec<(String, SyntaxShape)>, ShellError> {
|
|
|
|
match find_block_shapes(block, registry) {
|
|
|
|
Ok(found) => Ok(args
|
|
|
|
.iter()
|
|
|
|
.map(|arg| {
|
|
|
|
(
|
|
|
|
arg.clone(),
|
|
|
|
match found.get(arg) {
|
|
|
|
None | Some((_, None)) => SyntaxShape::Any,
|
|
|
|
Some((_, Some(shape))) => *shape,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.collect()),
|
|
|
|
Err(err) => Err(err),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type ShapeMap = HashMap<String, (Span, Option<SyntaxShape>)>;
|
|
|
|
|
|
|
|
fn check_insert(
|
|
|
|
existing: &mut ShapeMap,
|
|
|
|
to_add: (String, (Span, Option<SyntaxShape>)),
|
|
|
|
) -> Result<(), ShellError> {
|
|
|
|
match (to_add.1).1 {
|
|
|
|
None => match existing.get(&to_add.0) {
|
|
|
|
None => {
|
|
|
|
existing.insert(to_add.0, to_add.1);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Some(_) => Ok(()),
|
|
|
|
},
|
|
|
|
Some(new) => match existing.insert(to_add.0.clone(), ((to_add.1).0, Some(new))) {
|
|
|
|
None => Ok(()),
|
|
|
|
Some(exist) => match exist.1 {
|
|
|
|
None => Ok(()),
|
|
|
|
Some(shape) => match shape {
|
|
|
|
SyntaxShape::Any => Ok(()),
|
|
|
|
shape if shape == new => Ok(()),
|
2020-08-24 20:38:24 +02:00
|
|
|
_ => Err(ShellError::labeled_error_with_secondary(
|
2020-08-20 05:18:55 +02:00
|
|
|
"Type conflict in alias variable use",
|
2020-08-24 20:38:24 +02:00
|
|
|
format!("{:?}", new),
|
2020-08-20 05:18:55 +02:00
|
|
|
(to_add.1).0,
|
2020-08-24 20:38:24 +02:00
|
|
|
format!("{:?}", shape),
|
|
|
|
exist.0,
|
2020-08-20 05:18:55 +02:00
|
|
|
)),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_merge(existing: &mut ShapeMap, new: &ShapeMap) -> Result<(), ShellError> {
|
|
|
|
for (k, v) in new.iter() {
|
|
|
|
check_insert(existing, (k.clone(), *v))?;
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_expr_shapes(
|
|
|
|
spanned_expr: &SpannedExpression,
|
|
|
|
registry: &CommandRegistry,
|
|
|
|
) -> Result<ShapeMap, ShellError> {
|
|
|
|
match &spanned_expr.expr {
|
|
|
|
// TODO range will need similar if/when invocations can be parsed within range expression
|
|
|
|
Expression::Binary(bin) => find_expr_shapes(&bin.left, registry).and_then(|mut left| {
|
|
|
|
find_expr_shapes(&bin.right, registry)
|
|
|
|
.and_then(|right| check_merge(&mut left, &right).map(|()| left))
|
|
|
|
}),
|
|
|
|
Expression::Block(b) => find_block_shapes(&b, registry),
|
|
|
|
Expression::Path(path) => match &path.head.expr {
|
|
|
|
Expression::Invocation(b) => find_block_shapes(&b, registry),
|
|
|
|
Expression::Variable(Variable::Other(var, _)) => {
|
|
|
|
let mut result = HashMap::new();
|
|
|
|
result.insert(var.to_string(), (spanned_expr.span, None));
|
|
|
|
Ok(result)
|
|
|
|
}
|
|
|
|
_ => Ok(HashMap::new()),
|
|
|
|
},
|
|
|
|
_ => Ok(HashMap::new()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn find_block_shapes(block: &Block, registry: &CommandRegistry) -> Result<ShapeMap, ShellError> {
|
|
|
|
let apply_shape = |found: ShapeMap, sig_shape: SyntaxShape| -> ShapeMap {
|
|
|
|
found
|
|
|
|
.iter()
|
|
|
|
.map(|(v, sh)| match sh.1 {
|
|
|
|
None => (v.clone(), (sh.0, Some(sig_shape))),
|
|
|
|
Some(shape) => (v.clone(), (sh.0, Some(shape))),
|
|
|
|
})
|
|
|
|
.collect()
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut arg_shapes = HashMap::new();
|
|
|
|
for pipeline in &block.block {
|
|
|
|
for classified in &pipeline.list {
|
|
|
|
match classified {
|
|
|
|
ClassifiedCommand::Expr(spanned_expr) => {
|
|
|
|
let found = find_expr_shapes(&spanned_expr, registry)?;
|
|
|
|
check_merge(&mut arg_shapes, &found)?
|
|
|
|
}
|
|
|
|
ClassifiedCommand::Internal(internal) => {
|
|
|
|
if let Some(signature) = registry.get(&internal.name) {
|
|
|
|
if let Some(positional) = &internal.args.positional {
|
|
|
|
for (i, spanned_expr) in positional.iter().enumerate() {
|
|
|
|
let found = find_expr_shapes(&spanned_expr, registry)?;
|
|
|
|
if i >= signature.positional.len() {
|
|
|
|
if let Some((sig_shape, _)) = &signature.rest_positional {
|
|
|
|
check_merge(
|
|
|
|
&mut arg_shapes,
|
|
|
|
&apply_shape(found, *sig_shape),
|
|
|
|
)?;
|
|
|
|
} else {
|
|
|
|
unreachable!("should have error'd in parsing");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let (pos_type, _) = &signature.positional[i];
|
|
|
|
match pos_type {
|
|
|
|
// TODO pass on mandatory/optional?
|
|
|
|
PositionalType::Mandatory(_, sig_shape)
|
|
|
|
| PositionalType::Optional(_, sig_shape) => {
|
|
|
|
check_merge(
|
|
|
|
&mut arg_shapes,
|
|
|
|
&apply_shape(found, *sig_shape),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(named) = &internal.args.named {
|
|
|
|
for (name, val) in named.iter() {
|
|
|
|
if let NamedValue::Value(_, spanned_expr) = val {
|
|
|
|
let found = find_expr_shapes(&spanned_expr, registry)?;
|
|
|
|
match signature.named.get(name) {
|
|
|
|
None => {
|
|
|
|
unreachable!("should have error'd in parsing");
|
|
|
|
}
|
|
|
|
Some((named_type, _)) => {
|
|
|
|
if let NamedType::Mandatory(_, sig_shape)
|
|
|
|
| NamedType::Optional(_, sig_shape) = named_type
|
|
|
|
{
|
|
|
|
check_merge(
|
|
|
|
&mut arg_shapes,
|
|
|
|
&apply_shape(found, *sig_shape),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
unreachable!("registry has lost name it provided");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ClassifiedCommand::Dynamic(_) | ClassifiedCommand::Error(_) => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(arg_shapes)
|
|
|
|
}
|
|
|
|
|
2020-05-18 14:56:01 +02:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::Alias;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn examples_work_as_expected() {
|
|
|
|
use crate::examples::test as test_examples;
|
|
|
|
|
|
|
|
test_examples(Alias {})
|
|
|
|
}
|
|
|
|
}
|