forked from extern/nushell
External it and nu variable column path fetch support. (#1379)
This commit is contained in:
parent
24094acee9
commit
2ab8d035e6
@ -14,6 +14,18 @@ impl ExternalArg {
|
|||||||
pub fn is_it(&self) -> bool {
|
pub fn is_it(&self) -> bool {
|
||||||
self.has("$it")
|
self.has("$it")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_nu(&self) -> bool {
|
||||||
|
self.has("$nu")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn looks_like_it(&self) -> bool {
|
||||||
|
self.arg.starts_with("$it") && (self.arg.starts_with("$it.") || self.is_it())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn looks_like_nu(&self) -> bool {
|
||||||
|
self.arg.starts_with("$nu") && (self.arg.starts_with("$nu.") || self.is_nu())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::ops::Deref for ExternalArg {
|
impl std::ops::Deref for ExternalArg {
|
||||||
@ -54,7 +66,11 @@ pub struct ExternalCommand {
|
|||||||
|
|
||||||
impl ExternalCommand {
|
impl ExternalCommand {
|
||||||
pub fn has_it_argument(&self) -> bool {
|
pub fn has_it_argument(&self) -> bool {
|
||||||
self.args.iter().any(|arg| arg.has("$it"))
|
self.args.iter().any(|arg| arg.looks_like_it())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_nu_argument(&self) -> bool {
|
||||||
|
self.args.iter().any(|arg| arg.looks_like_nu())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,11 @@ use futures::stream::StreamExt;
|
|||||||
use futures_codec::{FramedRead, LinesCodec};
|
use futures_codec::{FramedRead, LinesCodec};
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
|
use nu_parser::commands::classified::external::ExternalArg;
|
||||||
use nu_parser::ExternalCommand;
|
use nu_parser::ExternalCommand;
|
||||||
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value};
|
||||||
|
use nu_source::{Tag, Tagged};
|
||||||
|
use nu_value_ext::as_column_path;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
@ -16,7 +19,7 @@ pub fn nu_value_to_string(command: &ExternalCommand, from: &Value) -> Result<Str
|
|||||||
| UntaggedValue::Primitive(Primitive::Line(s)) => Ok(s.clone()),
|
| UntaggedValue::Primitive(Primitive::Line(s)) => Ok(s.clone()),
|
||||||
UntaggedValue::Primitive(Primitive::Path(p)) => Ok(p.to_string_lossy().to_string()),
|
UntaggedValue::Primitive(Primitive::Path(p)) => Ok(p.to_string_lossy().to_string()),
|
||||||
unsupported => Err(ShellError::labeled_error(
|
unsupported => Err(ShellError::labeled_error(
|
||||||
format!("$it needs string data (given: {})", unsupported.type_name()),
|
format!("needs string data (given: {})", unsupported.type_name()),
|
||||||
"expected a string",
|
"expected a string",
|
||||||
&command.name_tag,
|
&command.name_tag,
|
||||||
)),
|
)),
|
||||||
@ -58,13 +61,59 @@ pub(crate) async fn run_external_command(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if command.has_it_argument() {
|
if command.has_it_argument() || command.has_nu_argument() {
|
||||||
run_with_iterator_arg(command, context, input, is_last).await
|
run_with_iterator_arg(command, context, input, is_last).await
|
||||||
} else {
|
} else {
|
||||||
run_with_stdin(command, context, input, is_last).await
|
run_with_stdin(command, context, input, is_last).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_column_path_for_fetching_it_variable(
|
||||||
|
argument: &ExternalArg,
|
||||||
|
) -> Result<Tagged<ColumnPath>, ShellError> {
|
||||||
|
// We have "$it.[contents of interest]"
|
||||||
|
// and start slicing from "$it.[member+]"
|
||||||
|
// ^ here.
|
||||||
|
let key = nu_source::Text::from(argument.deref()).slice(4..argument.len());
|
||||||
|
|
||||||
|
to_column_path(&key, &argument.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_column_path_for_fetching_nu_variable(
|
||||||
|
argument: &ExternalArg,
|
||||||
|
) -> Result<Tagged<ColumnPath>, ShellError> {
|
||||||
|
// We have "$nu.[contents of interest]"
|
||||||
|
// and start slicing from "$nu.[member+]"
|
||||||
|
// ^ here.
|
||||||
|
let key = nu_source::Text::from(argument.deref()).slice(4..argument.len());
|
||||||
|
|
||||||
|
to_column_path(&key, &argument.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_column_path(
|
||||||
|
path_members: &str,
|
||||||
|
tag: impl Into<Tag>,
|
||||||
|
) -> Result<Tagged<ColumnPath>, ShellError> {
|
||||||
|
let tag = tag.into();
|
||||||
|
|
||||||
|
as_column_path(
|
||||||
|
&UntaggedValue::Table(
|
||||||
|
path_members
|
||||||
|
.split('.')
|
||||||
|
.map(|x| {
|
||||||
|
let member = match x.parse::<u64>() {
|
||||||
|
Ok(v) => UntaggedValue::int(v),
|
||||||
|
Err(_) => UntaggedValue::string(x),
|
||||||
|
};
|
||||||
|
|
||||||
|
member.into_value(&tag)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.into_value(&tag),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async fn run_with_iterator_arg(
|
async fn run_with_iterator_arg(
|
||||||
command: ExternalCommand,
|
command: ExternalCommand,
|
||||||
context: &mut Context,
|
context: &mut Context,
|
||||||
@ -87,8 +136,20 @@ async fn run_with_iterator_arg(
|
|||||||
let path = &path;
|
let path = &path;
|
||||||
let args = command.args.clone();
|
let args = command.args.clone();
|
||||||
|
|
||||||
let it_replacement = match nu_value_to_string(&command, &value) {
|
let it_replacement = {
|
||||||
Ok(value) => value,
|
if command.has_it_argument() {
|
||||||
|
let empty_arg = ExternalArg {
|
||||||
|
arg: "".to_string(),
|
||||||
|
tag: name_tag.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = args.iter()
|
||||||
|
.find(|arg| arg.looks_like_it())
|
||||||
|
.unwrap_or_else(|| &empty_arg);
|
||||||
|
|
||||||
|
if args.iter().all(|arg| !arg.is_it()) {
|
||||||
|
let key = match prepare_column_path_for_fetching_it_variable(&key) {
|
||||||
|
Ok(keypath) => keypath,
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
yield Ok(Value {
|
yield Ok(Value {
|
||||||
value: UntaggedValue::Error(reason),
|
value: UntaggedValue::Error(reason),
|
||||||
@ -98,12 +159,122 @@ async fn run_with_iterator_arg(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
match crate::commands::get::get_column_path(&key, &value) {
|
||||||
|
Ok(field) => {
|
||||||
|
match nu_value_to_string(&command, &field) {
|
||||||
|
Ok(val) => Some(val),
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match nu_value_to_string(&command, &value) {
|
||||||
|
Ok(val) => Some(val),
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let nu_replacement = {
|
||||||
|
if command.has_nu_argument() {
|
||||||
|
let empty_arg = ExternalArg {
|
||||||
|
arg: "".to_string(),
|
||||||
|
tag: name_tag.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let key = args.iter()
|
||||||
|
.find(|arg| arg.looks_like_nu())
|
||||||
|
.unwrap_or_else(|| &empty_arg);
|
||||||
|
|
||||||
|
let nu_var = match crate::evaluate::variables::nu(&name_tag) {
|
||||||
|
Ok(variables) => variables,
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if args.iter().all(|arg| !arg.is_nu()) {
|
||||||
|
let key = match prepare_column_path_for_fetching_nu_variable(&key) {
|
||||||
|
Ok(keypath) => keypath,
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match crate::commands::get::get_column_path(&key, &nu_var) {
|
||||||
|
Ok(field) => {
|
||||||
|
match nu_value_to_string(&command, &field) {
|
||||||
|
Ok(val) => Some(val),
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match nu_value_to_string(&command, &nu_var) {
|
||||||
|
Ok(val) => Some(val),
|
||||||
|
Err(reason) => {
|
||||||
|
yield Ok(Value {
|
||||||
|
value: UntaggedValue::Error(reason),
|
||||||
|
tag: name_tag
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let process_args = args.iter().filter_map(|arg| {
|
let process_args = args.iter().filter_map(|arg| {
|
||||||
if arg.chars().all(|c| c.is_whitespace()) {
|
if arg.chars().all(|c| c.is_whitespace()) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let arg = if arg.is_it() {
|
let arg = if arg.looks_like_it() {
|
||||||
let value = it_replacement.to_owned();
|
if let Some(mut value) = it_replacement.to_owned() {
|
||||||
let mut value = expand_tilde(&value, || home_dir.as_ref()).as_ref().to_string();
|
let mut value = expand_tilde(&value, || home_dir.as_ref()).as_ref().to_string();
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
{
|
{
|
||||||
@ -115,12 +286,31 @@ async fn run_with_iterator_arg(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
value
|
Some(value)
|
||||||
} else {
|
} else {
|
||||||
arg.to_string()
|
None
|
||||||
|
}
|
||||||
|
} else if arg.looks_like_nu() {
|
||||||
|
if let Some(mut value) = nu_replacement.to_owned() {
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
{
|
||||||
|
value = {
|
||||||
|
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
|
||||||
|
add_quotes(&value)
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(arg.to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(arg)
|
arg
|
||||||
}
|
}
|
||||||
}).collect::<Vec<String>>();
|
}).collect::<Vec<String>>();
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ use log::trace;
|
|||||||
use nu_errors::{ArgumentError, ShellError};
|
use nu_errors::{ArgumentError, ShellError};
|
||||||
use nu_parser::hir::{self, Expression, SpannedExpression};
|
use nu_parser::hir::{self, Expression, SpannedExpression};
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ColumnPath, Evaluate, Primitive, RangeInclusion, Scope, TaggedDictBuilder, UnspannedPathMember,
|
ColumnPath, Evaluate, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue,
|
||||||
UntaggedValue, Value,
|
Value,
|
||||||
};
|
};
|
||||||
use nu_source::Text;
|
use nu_source::Text;
|
||||||
|
|
||||||
@ -158,31 +158,7 @@ fn evaluate_reference(
|
|||||||
match name {
|
match name {
|
||||||
hir::Variable::It(_) => Ok(scope.it.value.clone().into_value(tag)),
|
hir::Variable::It(_) => Ok(scope.it.value.clone().into_value(tag)),
|
||||||
hir::Variable::Other(inner) => match inner.slice(source) {
|
hir::Variable::Other(inner) => match inner.slice(source) {
|
||||||
x if x == "nu" => {
|
x if x == "nu" => crate::evaluate::variables::nu(tag),
|
||||||
let mut nu_dict = TaggedDictBuilder::new(&tag);
|
|
||||||
|
|
||||||
let mut dict = TaggedDictBuilder::new(&tag);
|
|
||||||
for v in std::env::vars() {
|
|
||||||
if v.0 != "PATH" && v.0 != "Path" {
|
|
||||||
dict.insert_untagged(v.0, UntaggedValue::string(v.1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nu_dict.insert_value("env", dict.into_value());
|
|
||||||
|
|
||||||
let config = crate::data::config::read(&tag, &None)?;
|
|
||||||
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
|
|
||||||
|
|
||||||
let mut table = vec![];
|
|
||||||
let path = std::env::var_os("PATH");
|
|
||||||
if let Some(paths) = path {
|
|
||||||
for path in std::env::split_paths(&paths) {
|
|
||||||
table.push(UntaggedValue::path(path).into_value(&tag));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nu_dict.insert_value("path", UntaggedValue::table(&table).into_value(&tag));
|
|
||||||
|
|
||||||
Ok(nu_dict.into_value())
|
|
||||||
}
|
|
||||||
x => Ok(scope
|
x => Ok(scope
|
||||||
.vars
|
.vars
|
||||||
.get(x)
|
.get(x)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub(crate) mod evaluate_args;
|
pub(crate) mod evaluate_args;
|
||||||
pub(crate) mod evaluator;
|
pub(crate) mod evaluator;
|
||||||
pub(crate) mod operator;
|
pub(crate) mod operator;
|
||||||
|
pub(crate) mod variables;
|
||||||
|
|
||||||
pub(crate) use evaluator::evaluate_baseline_expr;
|
pub(crate) use evaluator::evaluate_baseline_expr;
|
||||||
|
31
src/evaluate/variables.rs
Normal file
31
src/evaluate/variables.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
|
||||||
|
use nu_source::Tag;
|
||||||
|
|
||||||
|
pub fn nu(tag: impl Into<Tag>) -> Result<Value, ShellError> {
|
||||||
|
let tag = tag.into();
|
||||||
|
|
||||||
|
let mut nu_dict = TaggedDictBuilder::new(&tag);
|
||||||
|
|
||||||
|
let mut dict = TaggedDictBuilder::new(&tag);
|
||||||
|
for v in std::env::vars() {
|
||||||
|
if v.0 != "PATH" && v.0 != "Path" {
|
||||||
|
dict.insert_untagged(v.0, UntaggedValue::string(v.1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nu_dict.insert_value("env", dict.into_value());
|
||||||
|
|
||||||
|
let config = crate::data::config::read(&tag, &None)?;
|
||||||
|
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
|
||||||
|
|
||||||
|
let mut table = vec![];
|
||||||
|
let path = std::env::var_os("PATH");
|
||||||
|
if let Some(paths) = path {
|
||||||
|
for path in std::env::split_paths(&paths) {
|
||||||
|
table.push(UntaggedValue::path(path).into_value(&tag));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nu_dict.insert_value("path", UntaggedValue::table(&table).into_value(&tag));
|
||||||
|
|
||||||
|
Ok(nu_dict.into_value())
|
||||||
|
}
|
@ -22,7 +22,7 @@ fn shows_error_for_command_not_found() {
|
|||||||
|
|
||||||
mod it_evaluation {
|
mod it_evaluation {
|
||||||
use super::nu;
|
use super::nu;
|
||||||
use nu_test_support::fs::Stub::{EmptyFile, FileWithContentToBeTrimmed};
|
use nu_test_support::fs::Stub::{EmptyFile, FileWithContent, FileWithContentToBeTrimmed};
|
||||||
use nu_test_support::{pipeline, playground::Playground};
|
use nu_test_support::{pipeline, playground::Playground};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -76,6 +76,29 @@ mod it_evaluation {
|
|||||||
assert_eq!(actual, "AndrásWithKitKat");
|
assert_eq!(actual, "AndrásWithKitKat");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn supports_fetching_given_a_column_path_to_it() {
|
||||||
|
Playground::setup("it_argument_test_3", |dirs, sandbox| {
|
||||||
|
sandbox.with_files(vec![FileWithContent(
|
||||||
|
"sample.toml",
|
||||||
|
r#"
|
||||||
|
nu_party_venue = "zion"
|
||||||
|
"#,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.toml
|
||||||
|
| cococo $it.nu_party_venue
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "zion");
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod stdin_evaluation {
|
mod stdin_evaluation {
|
||||||
|
Loading…
Reference in New Issue
Block a user