* WIP getting scopes right

* finish adding initial support

* Finish with alias and add startup commands
This commit is contained in:
Jonathan Turner 2020-04-15 17:43:23 +12:00 committed by GitHub
parent e3da037b80
commit bd5836e25d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 332 additions and 38 deletions

View File

@ -11,7 +11,7 @@ use futures_codec::FramedRead;
use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, ExternalCommand};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use log::{debug, trace};
use rustyline::error::ReadlineError;
@ -258,6 +258,7 @@ pub fn create_default_context(
whole_stream_command(What),
whole_stream_command(Which),
whole_stream_command(Debug),
per_item_command(Alias),
// Statistics
whole_stream_command(Size),
whole_stream_command(Count),
@ -442,6 +443,29 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
})
.expect("Error setting Ctrl-C handler");
let mut ctrlcbreak = false;
// before we start up, let's run our startup commands
if let Ok(config) = crate::data::config::config(Tag::unknown()) {
if let Some(commands) = config.get("startup") {
match commands {
Value {
value: UntaggedValue::Table(pipelines),
..
} => {
for pipeline in pipelines {
if let Ok(pipeline_string) = pipeline.as_string() {
let _ =
run_pipeline_standalone(pipeline_string, false, &mut context).await;
}
}
}
_ => {
println!("warning: expected a table of pipeline strings as startup commands");
}
}
}
}
loop {
if context.ctrl_c.load(Ordering::SeqCst) {
context.ctrl_c.store(false, Ordering::SeqCst);
@ -712,7 +736,7 @@ async fn process_line(
None
};
match run_pipeline(pipeline, ctx, input_stream).await {
match run_pipeline(pipeline, ctx, input_stream, &Scope::empty()).await {
Ok(Some(input)) => {
// 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

View File

@ -4,6 +4,7 @@ pub(crate) mod macros;
mod from_delimited_data;
mod to_delimited_data;
pub(crate) mod alias;
pub(crate) mod append;
pub(crate) mod args;
pub(crate) mod autoview;
@ -75,6 +76,7 @@ pub(crate) mod reject;
pub(crate) mod rename;
pub(crate) mod reverse;
pub(crate) mod rm;
pub(crate) mod run_alias;
pub(crate) mod save;
pub(crate) mod shells;
pub(crate) mod shuffle;
@ -115,6 +117,7 @@ pub(crate) use command::{
WholeStreamCommand,
};
pub(crate) use alias::Alias;
pub(crate) use append::Append;
pub(crate) use calc::Calc;
pub(crate) use compact::Compact;

View File

@ -0,0 +1,65 @@
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
CallInfo, CommandAction, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct Alias;
impl PerItemCommand for Alias {
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")
.required("block", SyntaxShape::Block, "the block to run on each row")
}
fn usage(&self) -> &str {
"Run a block on each row of the table."
}
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
_input: Value,
) -> Result<OutputStream, ShellError> {
let call_info = call_info.clone();
let stream = async_stream! {
match (call_info.args.expect_nth(0)?, call_info.args.expect_nth(1)?, call_info.args.expect_nth(2)?) {
(Value {value: UntaggedValue::Primitive(Primitive::String(name)), .. },
Value { value: UntaggedValue::Table(list), .. },
Value {
value: UntaggedValue::Block(block),
tag
}) => {
let mut args: Vec<String> = vec![];
for item in list.iter() {
if let Ok(string) = item.as_string() {
args.push(format!("${}", string));
} else {
yield Err(ShellError::labeled_error("Expected a string", "expected a string", item.tag()));
}
}
yield ReturnSuccess::action(CommandAction::AddAlias(name.to_string(), args, block.clone()))
}
_ => {
yield Err(ShellError::labeled_error(
"Expected `name [args] {block}",
"needs a name, args, and a block",
call_info.name_tag,
))
}
};
};
Ok(stream.to_output_stream())
}
}

View File

@ -3,7 +3,7 @@ use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, UntaggedValue, Value};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
@ -287,6 +287,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
span,
},
name_tag: context.name.clone(),
scope: Scope::empty(),
},
}
}

View File

@ -11,7 +11,9 @@ pub(crate) fn run_expression_block(
expr: SpannedExpression,
context: &mut Context,
input: Option<InputStream>,
scope: &Scope,
) -> Result<Option<InputStream>, ShellError> {
let scope = scope.clone();
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::expr", "->");
trace!(target: "nu::run::expr", "{:?}", expr);
@ -25,10 +27,11 @@ pub(crate) fn run_expression_block(
pin_mut!(values);
while let Some(row) = values.next().await {
yield evaluate_baseline_expr(&expr, &registry, &Scope::new(row));
let scope = scope.clone().set_it(row);
yield evaluate_baseline_expr(&expr, &registry, &scope);
}
} else {
yield evaluate_baseline_expr(&expr, &registry, &Scope::empty());
yield evaluate_baseline_expr(&expr, &registry, &scope);
}
};

View File

@ -7,7 +7,7 @@ use futures_codec::FramedRead;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::hir::{ExternalArg, ExternalCommand};
use nu_protocol::{ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value};
use nu_protocol::{ColumnPath, Primitive, Scope, ShellTypeName, UntaggedValue, Value};
use nu_source::{Tag, Tagged};
use nu_value_ext::as_column_path;
use std::io::Write;
@ -97,6 +97,7 @@ pub(crate) async fn run_external_command(
command: ExternalCommand,
context: &mut Context,
input: Option<InputStream>,
_scope: &Scope,
is_last: bool,
) -> Result<Option<InputStream>, ShellError> {
trace!(target: "nu::run::external", "-> {}", command.name);
@ -718,6 +719,7 @@ mod tests {
};
use futures::executor::block_on;
use nu_errors::ShellError;
use nu_protocol::Scope;
use nu_test_support::commands::ExternalBuilder;
// async fn read(mut stream: OutputStream) -> Option<Value> {
@ -738,9 +740,11 @@ mod tests {
let mut ctx = Context::basic().expect("There was a problem creating a basic context.");
assert!(run_external_command(cmd, &mut ctx, None, false)
.await
.is_err());
assert!(
run_external_command(cmd, &mut ctx, None, &Scope::empty(), false)
.await
.is_err()
);
Ok(())
}

View File

@ -1,14 +1,17 @@
use crate::commands::command::per_item_command;
use crate::commands::run_alias::AliasCommand;
use crate::commands::UnevaluatedCallInfo;
use crate::prelude::*;
use log::{log_enabled, trace};
use nu_errors::ShellError;
use nu_protocol::hir::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, Scope, UntaggedValue, Value};
pub(crate) fn run_internal_command(
command: InternalCommand,
context: &mut Context,
input: Option<InputStream>,
scope: &Scope,
) -> Result<Option<InputStream>, ShellError> {
if log_enabled!(log::Level::Trace) {
trace!(target: "nu::run::internal", "->");
@ -28,6 +31,7 @@ pub(crate) fn run_internal_command(
internal_command?,
Tag::unknown_anchor(command.name_span),
command.args.clone(),
scope,
objects,
)
};
@ -68,6 +72,7 @@ pub(crate) fn run_internal_command(
span: Span::unknown()
},
name_tag: Tag::unknown_anchor(command.name_span),
scope: Scope::empty(),
}
};
let mut result = converter.run(new_args.with_input(vec![tagged_contents]), &context.registry);
@ -120,6 +125,15 @@ pub(crate) fn run_internal_command(
FilesystemShell::with_location(location, context.registry().clone()),
));
}
CommandAction::AddAlias(name, args, commands) => {
context.add_commands(vec![
per_item_command(AliasCommand::new(
name,
args,
commands,
))
]);
}
CommandAction::PreviousShell => {
context.shell_manager.prev();
}

View File

@ -5,11 +5,13 @@ use crate::context::Context;
use crate::stream::InputStream;
use nu_errors::ShellError;
use nu_protocol::hir::{ClassifiedCommand, ClassifiedPipeline};
use nu_protocol::Scope;
pub(crate) async fn run_pipeline(
pipeline: ClassifiedPipeline,
ctx: &mut Context,
mut input: Option<InputStream>,
scope: &Scope,
) -> Result<Option<InputStream>, ShellError> {
let mut iter = pipeline.commands.list.into_iter().peekable();
@ -22,18 +24,22 @@ pub(crate) async fn run_pipeline(
return Err(ShellError::unimplemented("Dynamic commands"))
}
(Some(ClassifiedCommand::Expr(expr)), _) => run_expression_block(*expr, ctx, input)?,
(Some(ClassifiedCommand::Expr(expr)), _) => {
run_expression_block(*expr, ctx, input, scope)?
}
(Some(ClassifiedCommand::Error(err)), _) => return Err(err.into()),
(_, Some(ClassifiedCommand::Error(err))) => return Err(err.clone().into()),
(Some(ClassifiedCommand::Internal(left)), _) => run_internal_command(left, ctx, input)?,
(Some(ClassifiedCommand::Internal(left)), _) => {
run_internal_command(left, ctx, input, scope)?
}
(Some(ClassifiedCommand::External(left)), None) => {
run_external_command(left, ctx, input, true).await?
run_external_command(left, ctx, input, scope, true).await?
}
(Some(ClassifiedCommand::External(left)), _) => {
run_external_command(left, ctx, input, false).await?
run_external_command(left, ctx, input, scope, false).await?
}
(None, _) => break,

View File

@ -16,15 +16,27 @@ use std::sync::atomic::AtomicBool;
pub struct UnevaluatedCallInfo {
pub args: hir::Call,
pub name_tag: Tag,
pub scope: Scope,
}
impl UnevaluatedCallInfo {
pub fn evaluate(
pub fn evaluate(self, registry: &CommandRegistry) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, &self.scope)?;
Ok(CallInfo {
args,
name_tag: self.name_tag,
})
}
pub fn evaluate_with_new_it(
self,
registry: &CommandRegistry,
scope: &Scope,
it: &Value,
) -> Result<CallInfo, ShellError> {
let args = evaluate_args(&self.args, registry, scope)?;
let mut scope = self.scope.clone();
scope = scope.set_it(it.clone());
let args = evaluate_args(&self.args, registry, &scope)?;
Ok(CallInfo {
args,
@ -113,7 +125,7 @@ impl CommandArgs {
let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = self.call_info.evaluate(registry, &Scope::empty())?;
let call_info = self.call_info.evaluate(registry)?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
@ -133,7 +145,12 @@ impl CommandArgs {
let ctrl_c = self.ctrl_c.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = self.call_info.evaluate(registry, scope)?;
let call_info = UnevaluatedCallInfo {
name_tag: self.call_info.name_tag,
args: self.call_info.args,
scope: scope.clone(),
};
let call_info = call_info.evaluate(registry)?;
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
@ -515,10 +532,16 @@ impl Command {
.input
.values
.map(move |x| {
let call_info = raw_args
.clone()
.call_info
.evaluate(&registry, &Scope::it_value(x.clone()));
let call_info = UnevaluatedCallInfo {
args: raw_args.call_info.args.clone(),
name_tag: raw_args.call_info.name_tag.clone(),
scope: raw_args.call_info.scope.clone().set_it(x.clone()),
}
.evaluate(&registry);
// let call_info = raw_args
// .clone()
// .call_info
// .evaluate(&registry, &Scope::it_value(x.clone()));
match call_info {
Ok(call_info) => match command.run(&call_info, &registry, &raw_args, x) {
@ -576,7 +599,7 @@ impl WholeStreamCommand for FnFilterCommand {
let result = input.values.map(move |it| {
let registry = registry.clone();
let call_info = match call_info.clone().evaluate(&registry, &Scope::it_value(it)) {
let call_info = match call_info.clone().evaluate_with_new_it(&registry, &it) {
Err(err) => return OutputStream::from(vec![Err(err)]).values,
Ok(args) => args,
};

View File

@ -5,7 +5,8 @@ use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedPipeline, CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
hir::ClassifiedPipeline, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue,
Value,
};
pub struct Each;
@ -52,6 +53,7 @@ impl PerItemCommand for Each {
ClassifiedPipeline::new(block.clone(), None),
&mut context,
Some(input_stream),
&Scope::empty(),
).await;
match result {

View File

@ -104,6 +104,7 @@ impl PerItemCommand for Enter {
span: Span::unknown()
},
name_tag: raw_args.call_info.name_tag,
scope: raw_args.call_info.scope.clone()
},
};
let mut result = converter.run(

View File

@ -3,7 +3,7 @@ use crate::prelude::*;
use derive_new::new;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, Scope, Signature, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, ReturnValue, Signature, UntaggedValue, Value};
use serde::{self, Deserialize, Serialize};
use std::io::prelude::*;
use std::io::BufReader;
@ -71,10 +71,13 @@ pub fn filter_plugin(
) -> Result<OutputStream, ShellError> {
trace!("filter_plugin :: {}", path);
let args = args.evaluate_once_with_scope(
registry,
&Scope::it_value(UntaggedValue::string("$it").into_untagged_value()),
)?;
let scope = &args
.call_info
.scope
.clone()
.set_it(UntaggedValue::string("$it").into_untagged_value());
let args = args.evaluate_once_with_scope(registry, &scope)?;
let mut child = std::process::Command::new(path)
.stdin(std::process::Stdio::piped())

View File

@ -0,0 +1,93 @@
use crate::commands::classified::pipeline::run_pipeline;
use crate::prelude::*;
use derive_new::new;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedPipeline, hir::Commands, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape,
Value,
};
#[derive(new)]
pub struct AliasCommand {
name: String,
args: Vec<String>,
block: Commands,
}
impl PerItemCommand for AliasCommand {
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
let mut alias = Signature::build(&self.name);
for arg in &self.args {
alias = alias.required(arg, SyntaxShape::Any, "");
}
alias
}
fn usage(&self) -> &str {
""
}
fn run(
&self,
call_info: &CallInfo,
registry: &CommandRegistry,
raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> {
let tag = call_info.name_tag.clone();
let call_info = call_info.clone();
let registry = registry.clone();
let raw_args = raw_args.clone();
let block = self.block.clone();
let mut scope = Scope::empty();
if let Some(positional) = &call_info.args.positional {
for (pos, arg) in positional.iter().enumerate() {
scope = scope.set_var(self.args[pos].to_string(), arg.clone());
}
}
let stream = async_stream! {
let mut context = Context::from_raw(&raw_args, &registry);
let input_stream = async_stream! {
yield Ok(input.clone())
}.to_input_stream();
let result = run_pipeline(
ClassifiedPipeline::new(block.clone(), None),
&mut context,
Some(input_stream),
&scope
).await;
match result {
Ok(Some(v)) => {
let results: Vec<Value> = v.collect().await;
for result in results {
yield Ok(ReturnSuccess::Value(result));
}
}
Ok(None) => {
yield Err(ShellError::labeled_error(
"Expected a block",
"each needs a block",
tag,
));
}
Err(e) => {
yield Err(e);
}
}
};
Ok(stream.to_output_stream())
}
}

View File

@ -1,7 +1,7 @@
use crate::commands::{UnevaluatedCallInfo, WholeStreamCommand};
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{Primitive, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::{Path, PathBuf};
@ -235,6 +235,7 @@ fn save(
span: Span::unknown()
},
name_tag: raw_args.call_info.name_tag,
scope: Scope::empty(), // FIXME?
}
};
let mut result = converter.run(new_args.with_input(input), &registry);

View File

@ -68,6 +68,7 @@ impl PerItemCommand for Where {
}
};
//FIXME: should we use the scope that's brought in as well?
let condition = evaluate_baseline_expr(&condition, registry, &Scope::new(input.clone()))?;
let stream = match condition.as_bool() {

View File

@ -7,7 +7,7 @@ use crate::stream::{InputStream, OutputStream};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::SignatureRegistry;
use nu_protocol::{hir, Signature};
use nu_protocol::{hir, Scope, Signature};
use nu_source::{Tag, Text};
use parking_lot::Mutex;
use std::error::Error;
@ -196,22 +196,33 @@ impl Context {
command: Arc<Command>,
name_tag: Tag,
args: hir::Call,
scope: &Scope,
input: InputStream,
) -> OutputStream {
let command_args = self.command_args(args, input, name_tag);
let command_args = self.command_args(args, input, name_tag, scope);
command.run(command_args, self.registry())
}
fn call_info(&self, args: hir::Call, name_tag: Tag) -> UnevaluatedCallInfo {
UnevaluatedCallInfo { args, name_tag }
fn call_info(&self, args: hir::Call, name_tag: Tag, scope: &Scope) -> UnevaluatedCallInfo {
UnevaluatedCallInfo {
args,
name_tag,
scope: scope.clone(),
}
}
fn command_args(&self, args: hir::Call, input: InputStream, name_tag: Tag) -> CommandArgs {
fn command_args(
&self,
args: hir::Call,
input: InputStream,
name_tag: Tag,
scope: &Scope,
) -> CommandArgs {
CommandArgs {
host: self.host.clone(),
ctrl_c: self.ctrl_c.clone(),
shell_manager: self.shell_manager.clone(),
call_info: self.call_info(args, name_tag),
call_info: self.call_info(args, name_tag, scope),
input,
}
}

View File

@ -0,0 +1,17 @@
use nu_test_support::nu;
use nu_test_support::playground::Playground;
#[test]
fn alias_args_work() {
Playground::setup("append_test_1", |dirs, _| {
let actual = nu!(
cwd: dirs.root(),
r#"
alias double_echo [a b] {echo $a $b}
double_echo 1 2 | to-json
"#
);
assert_eq!(actual, "[1,2]");
})
}

View File

@ -1,3 +1,4 @@
mod alias;
mod append;
mod calc;
mod cd;

View File

@ -1,3 +1,4 @@
use crate::hir::Commands;
use crate::value::Value;
use nu_errors::ShellError;
use nu_source::{b, DebugDocBuilder, PrettyDebug};
@ -20,6 +21,8 @@ pub enum CommandAction {
EnterValueShell(Value),
/// Enter the help shell, which allows exploring the help system
EnterHelpShell(Value),
/// Enter the help shell, which allows exploring the help system
AddAlias(String, Vec<String>, Commands),
/// Go to the previous shell in the shell ring buffer
PreviousShell,
/// Go to the next shell in the shell ring buffer
@ -41,6 +44,7 @@ impl PrettyDebug for CommandAction {
CommandAction::EnterShell(s) => b::typed("enter shell", b::description(s)),
CommandAction::EnterValueShell(v) => b::typed("enter value shell", v.pretty()),
CommandAction::EnterHelpShell(v) => b::typed("enter help shell", v.pretty()),
CommandAction::AddAlias(..) => b::description("add alias"),
CommandAction::PreviousShell => b::description("previous shell"),
CommandAction::NextShell => b::description("next shell"),
CommandAction::LeaveShell => b::description("leave shell"),

View File

@ -1,11 +1,12 @@
use crate::value::{Primitive, UntaggedValue, Value};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
/// An evaluation scope. Scopes map variable names to Values and aid in evaluating blocks and expressions.
/// Additionally, holds the value for the special $it variable, a variable used to refer to the value passing
/// through the pipeline at that moment
#[derive(Debug)]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Scope {
pub it: Value,
pub vars: IndexMap<String, Value>,
@ -37,4 +38,20 @@ impl Scope {
vars: IndexMap::new(),
}
}
pub fn set_it(self, value: Value) -> Scope {
Scope {
it: value,
vars: self.vars,
}
}
pub fn set_var(self, name: String, value: Value) -> Scope {
let mut new_vars = self.vars.clone();
new_vars.insert(name, value);
Scope {
it: self.it,
vars: new_vars,
}
}
}