External it and nu variable column path fetch support. (#1379)

This commit is contained in:
Andrés N. Robalino 2020-02-11 18:25:56 -05:00 committed by GitHub
parent 24094acee9
commit 2ab8d035e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 292 additions and 55 deletions

View File

@ -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())
} }
} }

View File

@ -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>>();

View File

@ -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)

View File

@ -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
View 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())
}

View File

@ -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 {