Add with-env command (#1717)

This commit is contained in:
Jonathan Turner 2020-05-06 15:56:31 +12:00 committed by GitHub
parent 22e70478a4
commit b37e420c7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 153 additions and 12 deletions

View File

@ -261,6 +261,7 @@ pub fn create_default_context(
whole_stream_command(Which), whole_stream_command(Which),
whole_stream_command(Debug), whole_stream_command(Debug),
whole_stream_command(Alias), whole_stream_command(Alias),
whole_stream_command(WithEnv),
// Statistics // Statistics
whole_stream_command(Size), whole_stream_command(Size),
whole_stream_command(Count), whole_stream_command(Count),
@ -856,8 +857,8 @@ async fn process_line(
classified_block.block.expand_it_usage(); classified_block.block.expand_it_usage();
trace!("{:#?}", classified_block); trace!("{:#?}", classified_block);
let env = ctx.get_env();
match run_block(&classified_block.block, ctx, input_stream, &Scope::empty()).await { match run_block(&classified_block.block, ctx, input_stream, &Scope::env(env)).await {
Ok(input) => { Ok(input) => {
// Running a pipeline gives us back a stream that we can then // Running a pipeline gives us back a stream that we can then
// work through. At the top level, we just want to pull on the // work through. At the top level, we just want to pull on the

View File

@ -119,6 +119,7 @@ pub(crate) mod version;
pub(crate) mod what; pub(crate) mod what;
pub(crate) mod where_; pub(crate) mod where_;
pub(crate) mod which_; pub(crate) mod which_;
pub(crate) mod with_env;
pub(crate) mod wrap; pub(crate) mod wrap;
pub(crate) use autoview::Autoview; pub(crate) use autoview::Autoview;
@ -241,4 +242,5 @@ pub(crate) use version::Version;
pub(crate) use what::What; pub(crate) use what::What;
pub(crate) use where_::Where; pub(crate) use where_::Where;
pub(crate) use which_::Which; pub(crate) use which_::Which;
pub(crate) use with_env::WithEnv;
pub(crate) use wrap::Wrap; pub(crate) use wrap::Wrap;

View File

@ -159,7 +159,7 @@ fn run_with_stdin(
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>();
spawn(&command, &path, &process_args[..], input, is_last) spawn(&command, &path, &process_args[..], input, is_last, scope)
} }
fn spawn( fn spawn(
@ -168,6 +168,7 @@ fn spawn(
args: &[String], args: &[String],
input: InputStream, input: InputStream,
is_last: bool, is_last: bool,
scope: &Scope,
) -> Result<InputStream, ShellError> { ) -> Result<InputStream, ShellError> {
let command = command.clone(); let command = command.clone();
@ -197,6 +198,9 @@ fn spawn(
process.current_dir(path); process.current_dir(path);
trace!(target: "nu::run::external", "cwd = {:?}", &path); trace!(target: "nu::run::external", "cwd = {:?}", &path);
process.env_clear();
process.envs(scope.env.iter());
// We want stdout regardless of what // We want stdout regardless of what
// we are doing ($it case or pipe stdin) // we are doing ($it case or pipe stdin)
if !is_last { if !is_last {

View File

@ -33,6 +33,7 @@ pub(crate) fn run_internal_command(
let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result); let mut result = trace_out_stream!(target: "nu::trace_stream::internal", "output" = result);
let mut context = context.clone(); let mut context = context.clone();
let scope = scope.clone();
let stream = async_stream! { let stream = async_stream! {
let mut soft_errs: Vec<ShellError> = vec![]; let mut soft_errs: Vec<ShellError> = vec![];
@ -67,7 +68,7 @@ pub(crate) fn run_internal_command(
is_last: false, is_last: false,
}, },
name_tag: Tag::unknown_anchor(command.name_span), name_tag: Tag::unknown_anchor(command.name_span),
scope: Scope::empty(), scope: scope.clone(),
} }
}; };
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry); let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry);

View File

@ -6,7 +6,7 @@ use crate::prelude::*;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{hir::Block, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
pub struct Merge; pub struct Merge;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -48,6 +48,7 @@ fn merge(
let block = merge_args.block; let block = merge_args.block;
let registry = context.registry.clone(); let registry = context.registry.clone();
let mut input = context.input; let mut input = context.input;
let scope = raw_args.call_info.scope.clone();
let mut context = Context::from_raw(&raw_args, &registry); let mut context = Context::from_raw(&raw_args, &registry);
@ -55,7 +56,7 @@ fn merge(
let table: Option<Vec<Value>> = match run_block(&block, let table: Option<Vec<Value>> = match run_block(&block,
&mut context, &mut context,
InputStream::empty(), InputStream::empty(),
&Scope::empty()).await { &scope).await {
Ok(mut stream) => Some(stream.drain_vec().await), Ok(mut stream) => Some(stream.drain_vec().await),
Err(err) => { Err(err) => {
yield Err(err); yield Err(err);

View File

@ -1,7 +1,7 @@
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand}; use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
use crate::prelude::*; use crate::prelude::*;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged; use nu_source::Tagged;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -175,6 +175,7 @@ fn save(
) -> Result<OutputStream, ShellError> { ) -> Result<OutputStream, ShellError> {
let mut full_path = PathBuf::from(shell_manager.path()); let mut full_path = PathBuf::from(shell_manager.path());
let name_tag = name.clone(); let name_tag = name.clone();
let scope = raw_args.call_info.scope.clone();
let stream = async_stream! { let stream = async_stream! {
let input: Vec<Value> = input.collect().await; let input: Vec<Value> = input.collect().await;
@ -236,7 +237,7 @@ fn save(
is_last: false, is_last: false,
}, },
name_tag: raw_args.call_info.name_tag, name_tag: raw_args.call_info.name_tag,
scope: Scope::empty(), // FIXME? scope,
} }
}; };
let mut result = converter.run(new_args.with_input(input), &registry); let mut result = converter.run(new_args.with_input(input), &registry);

View File

@ -0,0 +1,87 @@
use crate::commands::classified::block::run_block;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape};
use nu_source::Tagged;
pub struct WithEnv;
#[derive(Deserialize, Debug)]
struct WithEnvArgs {
variable: (Tagged<String>, Tagged<String>),
block: Block,
}
impl WholeStreamCommand for WithEnv {
fn name(&self) -> &str {
"with-env"
}
fn signature(&self) -> Signature {
Signature::build("with-env")
.required(
"variable",
SyntaxShape::Any,
"the environment variable to temporarily set",
)
.required(
"block",
SyntaxShape::Block,
"the block to run once the variable is set",
)
}
fn usage(&self) -> &str {
"Runs a block with an environment set. Eg) with-env [NAME 'foo'] { echo $nu.env.NAME }"
}
fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
Ok(args.process_raw(registry, with_env)?.run())
}
}
fn with_env(
WithEnvArgs { variable, block }: WithEnvArgs,
context: RunnableContext,
raw_args: RawCommandArgs,
) -> Result<OutputStream, ShellError> {
let scope = raw_args
.call_info
.scope
.clone()
.set_env_var(variable.0.item, variable.1.item);
let registry = context.registry.clone();
let input = context.input;
let mut context = Context::from_raw(&raw_args, &registry);
let stream = async_stream! {
let result = run_block(
&block,
&mut context,
input,
&scope.clone(),
).await;
match result {
Ok(mut stream) => {
while let Some(result) = stream.next().await {
yield Ok(ReturnSuccess::Value(result));
}
let errors = context.get_errors();
if let Some(error) = errors.first() {
yield Err(error.clone());
}
}
Err(e) => {
yield Err(e);
}
}
};
Ok(stream.to_output_stream())
}

View File

@ -258,4 +258,12 @@ impl Context {
input, input,
} }
} }
pub fn get_env(&self) -> IndexMap<String, String> {
let mut output = IndexMap::new();
for (var, value) in self.host.lock().vars() {
output.insert(var, value);
}
output
}
} }

View File

@ -151,7 +151,7 @@ fn evaluate_reference(name: &hir::Variable, scope: &Scope, tag: Tag) -> Result<V
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(name, _) => match name { hir::Variable::Other(name, _) => match name {
x if x == "$nu" => crate::evaluate::variables::nu(tag), x if x == "$nu" => crate::evaluate::variables::nu(scope, tag),
x if x == "$true" => Ok(Value { x if x == "$true" => Ok(Value {
value: UntaggedValue::boolean(true), value: UntaggedValue::boolean(true),
tag, tag,

View File

@ -1,15 +1,15 @@
use crate::cli::History; use crate::cli::History;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Scope, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tag; use nu_source::Tag;
pub fn nu(tag: impl Into<Tag>) -> Result<Value, ShellError> { pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let tag = tag.into(); let tag = tag.into();
let mut nu_dict = TaggedDictBuilder::new(&tag); let mut nu_dict = TaggedDictBuilder::new(&tag);
let mut dict = TaggedDictBuilder::new(&tag); let mut dict = TaggedDictBuilder::new(&tag);
for v in std::env::vars() { for v in scope.env.iter() {
if v.0 != "PATH" && v.0 != "Path" { if v.0 != "PATH" && v.0 != "Path" {
dict.insert_untagged(v.0, UntaggedValue::string(v.1)); dict.insert_untagged(v.0, UntaggedValue::string(v.1));
} }

View File

@ -46,4 +46,5 @@ mod touch;
mod trim; mod trim;
mod uniq; mod uniq;
mod where_; mod where_;
mod with_env;
mod wrap; mod wrap;

View File

@ -0,0 +1,11 @@
use nu_test_support::nu;
#[test]
fn with_env_extends_environment() {
let actual = nu!(
cwd: "tests/fixtures/formats",
"with-env [FOO BARRRR] {echo $nu.env} | get FOO"
);
assert_eq!(actual, "BARRRR");
}

View File

@ -10,6 +10,7 @@ use std::fmt::Debug;
pub struct Scope { pub struct Scope {
pub it: Value, pub it: Value,
pub vars: IndexMap<String, Value>, pub vars: IndexMap<String, Value>,
pub env: IndexMap<String, String>,
} }
impl Scope { impl Scope {
@ -18,6 +19,7 @@ impl Scope {
Scope { Scope {
it, it,
vars: IndexMap::new(), vars: IndexMap::new(),
env: IndexMap::new(),
} }
} }
} }
@ -28,6 +30,7 @@ impl Scope {
Scope { Scope {
it: UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(), it: UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(),
vars: IndexMap::new(), vars: IndexMap::new(),
env: IndexMap::new(),
} }
} }
@ -36,6 +39,15 @@ impl Scope {
Scope { Scope {
it: value, it: value,
vars: IndexMap::new(), vars: IndexMap::new(),
env: IndexMap::new(),
}
}
pub fn env(env: IndexMap<String, String>) -> Scope {
Scope {
it: UntaggedValue::Primitive(Primitive::Nothing).into_untagged_value(),
vars: IndexMap::new(),
env,
} }
} }
@ -43,6 +55,7 @@ impl Scope {
Scope { Scope {
it: value, it: value,
vars: self.vars, vars: self.vars,
env: self.env,
} }
} }
@ -52,6 +65,17 @@ impl Scope {
Scope { Scope {
it: self.it, it: self.it,
vars: new_vars, vars: new_vars,
env: self.env,
}
}
pub fn set_env_var(self, variable: String, value: String) -> Scope {
let mut new_env_vars = self.env.clone();
new_env_vars.insert(variable, value);
Scope {
it: self.it,
vars: self.vars,
env: new_env_vars,
} }
} }
} }