mirror of
https://github.com/nushell/nushell
synced 2024-12-26 04:53:09 +00:00
External it and nu variable column path fetch support. (#1379)
This commit is contained in:
parent
24094acee9
commit
2ab8d035e6
6 changed files with 292 additions and 55 deletions
|
@ -14,6 +14,18 @@ impl ExternalArg {
|
|||
pub fn is_it(&self) -> bool {
|
||||
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 {
|
||||
|
@ -54,7 +66,11 @@ pub struct ExternalCommand {
|
|||
|
||||
impl ExternalCommand {
|
||||
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 log::trace;
|
||||
use nu_errors::ShellError;
|
||||
use nu_parser::commands::classified::external::ExternalArg;
|
||||
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::ops::Deref;
|
||||
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::Path(p)) => Ok(p.to_string_lossy().to_string()),
|
||||
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",
|
||||
&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
|
||||
} else {
|
||||
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(
|
||||
command: ExternalCommand,
|
||||
context: &mut Context,
|
||||
|
@ -87,14 +136,136 @@ async fn run_with_iterator_arg(
|
|||
let path = &path;
|
||||
let args = command.args.clone();
|
||||
|
||||
let it_replacement = match nu_value_to_string(&command, &value) {
|
||||
Ok(value) => value,
|
||||
Err(reason) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
let it_replacement = {
|
||||
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) => {
|
||||
yield Ok(Value {
|
||||
value: UntaggedValue::Error(reason),
|
||||
tag: name_tag
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -102,25 +273,44 @@ async fn run_with_iterator_arg(
|
|||
if arg.chars().all(|c| c.is_whitespace()) {
|
||||
None
|
||||
} else {
|
||||
let arg = if arg.is_it() {
|
||||
let value = it_replacement.to_owned();
|
||||
let mut value = expand_tilde(&value, || home_dir.as_ref()).as_ref().to_string();
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
value = {
|
||||
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
|
||||
add_quotes(&value)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
};
|
||||
let arg = if arg.looks_like_it() {
|
||||
if let Some(mut value) = it_replacement.to_owned() {
|
||||
let mut value = expand_tilde(&value, || home_dir.as_ref()).as_ref().to_string();
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
value = {
|
||||
if argument_contains_whitespace(&value) && !argument_is_quoted(&value) {
|
||||
add_quotes(&value)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(value)
|
||||
} else {
|
||||
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
|
||||
}
|
||||
value
|
||||
} else {
|
||||
arg.to_string()
|
||||
Some(arg.to_string())
|
||||
};
|
||||
|
||||
Some(arg)
|
||||
arg
|
||||
}
|
||||
}).collect::<Vec<String>>();
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ use log::trace;
|
|||
use nu_errors::{ArgumentError, ShellError};
|
||||
use nu_parser::hir::{self, Expression, SpannedExpression};
|
||||
use nu_protocol::{
|
||||
ColumnPath, Evaluate, Primitive, RangeInclusion, Scope, TaggedDictBuilder, UnspannedPathMember,
|
||||
UntaggedValue, Value,
|
||||
ColumnPath, Evaluate, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue,
|
||||
Value,
|
||||
};
|
||||
use nu_source::Text;
|
||||
|
||||
|
@ -158,31 +158,7 @@ fn evaluate_reference(
|
|||
match name {
|
||||
hir::Variable::It(_) => Ok(scope.it.value.clone().into_value(tag)),
|
||||
hir::Variable::Other(inner) => match inner.slice(source) {
|
||||
x if x == "nu" => {
|
||||
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 if x == "nu" => crate::evaluate::variables::nu(tag),
|
||||
x => Ok(scope
|
||||
.vars
|
||||
.get(x)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
pub(crate) mod evaluate_args;
|
||||
pub(crate) mod evaluator;
|
||||
pub(crate) mod operator;
|
||||
pub(crate) mod variables;
|
||||
|
||||
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 {
|
||||
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};
|
||||
|
||||
#[test]
|
||||
|
@ -76,6 +76,29 @@ mod it_evaluation {
|
|||
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 {
|
||||
|
|
Loading…
Reference in a new issue