Pipeline blocks (#1579)

* Making Commands match what UntaggedValue needs

* WIP

* WIP

* WIP

* Moved to expressions for conditions

* Add 'each' command to use command blocks

* More cleanup

* Add test for 'each'

* Instead use an expression block
This commit is contained in:
Jonathan Turner 2020-04-13 19:59:57 +12:00 committed by GitHub
parent 85d6b24be3
commit 08a09e2273
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 544 additions and 444 deletions

1
Cargo.lock generated
View File

@ -2192,6 +2192,7 @@ dependencies = [
"getset",
"indexmap",
"language-reporting",
"log",
"natural",
"nu-build",
"nu-errors",

View File

@ -10,7 +10,7 @@ use crate::prelude::*;
use futures_codec::FramedRead;
use nu_errors::ShellError;
use nu_parser::{ClassifiedCommand, ExternalCommand};
use nu_protocol::hir::{ClassifiedCommand, ExternalCommand};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use log::{debug, trace};
@ -304,6 +304,7 @@ pub fn create_default_context(
whole_stream_command(Range),
whole_stream_command(Rename),
whole_stream_command(Uniq),
per_item_command(Each),
// Table manipulation
whole_stream_command(Shuffle),
whole_stream_command(Wrap),

View File

@ -20,6 +20,7 @@ pub(crate) mod date;
pub(crate) mod debug;
pub(crate) mod default;
pub(crate) mod du;
pub(crate) mod each;
pub(crate) mod echo;
pub(crate) mod edit;
pub(crate) mod enter;
@ -124,6 +125,7 @@ pub(crate) use date::Date;
pub(crate) use debug::Debug;
pub(crate) use default::Default;
pub(crate) use du::Du;
pub(crate) use each::Each;
pub(crate) use echo::Echo;
pub(crate) use edit::Edit;
pub(crate) mod kill;

View File

@ -2,7 +2,7 @@ use crate::commands::UnevaluatedCallInfo;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_parser::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{hir, hir::Expression, hir::Literal, hir::SpannedExpression};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;

View File

@ -1,5 +1,5 @@
use derive_new::new;
use nu_parser::hir;
use nu_protocol::hir;
#[derive(new, Debug)]
pub(crate) struct Command {

View File

@ -6,8 +6,7 @@ use futures::stream::StreamExt;
use futures_codec::FramedRead;
use log::trace;
use nu_errors::ShellError;
use nu_parser::ExternalArg;
use nu_parser::ExternalCommand;
use nu_protocol::hir::{ExternalArg, ExternalCommand};
use nu_protocol::{ColumnPath, Primitive, ShellTypeName, UntaggedValue, Value};
use nu_source::{Tag, Tagged};
use nu_value_ext::as_column_path;

View File

@ -2,7 +2,7 @@ use crate::commands::UnevaluatedCallInfo;
use crate::prelude::*;
use log::{log_enabled, trace};
use nu_errors::ShellError;
use nu_parser::InternalCommand;
use nu_protocol::hir::InternalCommand;
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value};
pub(crate) fn run_internal_command(
@ -61,7 +61,7 @@ pub(crate) fn run_internal_command(
ctrl_c: context.ctrl_c.clone(),
shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call {
args: nu_protocol::hir::Call {
head: command.args.head,
positional: None,
named: None,

View File

@ -3,7 +3,7 @@ use crate::commands::classified::internal::run_internal_command;
use crate::context::Context;
use crate::stream::InputStream;
use nu_errors::ShellError;
use nu_parser::{ClassifiedCommand, ClassifiedPipeline};
use nu_protocol::hir::{ClassifiedCommand, ClassifiedPipeline};
pub(crate) async fn run_pipeline(
pipeline: ClassifiedPipeline,

View File

@ -6,7 +6,7 @@ use crate::prelude::*;
use derive_new::new;
use getset::Getters;
use nu_errors::ShellError;
use nu_parser::hir;
use nu_protocol::hir;
use nu_protocol::{CallInfo, EvaluatedArgs, ReturnValue, Scope, Signature, Value};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
@ -364,6 +364,14 @@ impl EvaluatedCommandArgs {
self.call_info.args.nth(pos)
}
/// Get the nth positional argument, error if not possible
pub fn expect_nth(&self, pos: usize) -> Result<&Value, ShellError> {
match self.call_info.args.nth(pos) {
None => Err(ShellError::unimplemented("Better error: expect_nth")),
Some(item) => Ok(item),
}
}
pub fn get(&self, name: &str) -> Option<&Value> {
self.call_info.args.get(name)
}

View File

@ -0,0 +1,89 @@
use crate::commands::classified::pipeline::run_pipeline;
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{
hir::ClassifiedPipeline, CallInfo, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct Each;
impl PerItemCommand for Each {
fn name(&self) -> &str {
"each"
}
fn signature(&self) -> Signature {
Signature::build("each").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 registry = registry.clone();
let raw_args = raw_args.clone();
let stream = async_stream! {
match call_info.args.expect_nth(0)? {
Value {
value: UntaggedValue::Block(block),
tag
} => {
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),
).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);
}
}
}
Value { tag, .. } => {
yield Err(ShellError::labeled_error(
"Expected a block",
"each needs a block",
tag,
))
}
};
};
Ok(stream.to_output_stream())
}
}

View File

@ -97,7 +97,7 @@ impl PerItemCommand for Enter {
ctrl_c: raw_args.ctrl_c,
shell_manager: raw_args.shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call {
args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None,

View File

@ -228,7 +228,7 @@ fn save(
ctrl_c,
shell_manager,
call_info: UnevaluatedCallInfo {
args: nu_parser::hir::Call {
args: nu_protocol::hir::Call {
head: raw_args.call_info.args.head,
positional: None,
named: None,

View File

@ -1,16 +1,12 @@
use crate::commands::WholeStreamCommand;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use log::trace;
use nu_errors::ShellError;
use nu_protocol::{Evaluate, Scope, Signature, SyntaxShape};
use nu_protocol::{hir::ClassifiedCommand, Scope, Signature, SyntaxShape, UntaggedValue, Value};
pub struct SkipWhile;
#[derive(Deserialize)]
pub struct SkipWhileArgs {
condition: Evaluate,
}
impl WholeStreamCommand for SkipWhile {
fn name(&self) -> &str {
"skip-while"
@ -20,7 +16,7 @@ impl WholeStreamCommand for SkipWhile {
Signature::build("skip-while")
.required(
"condition",
SyntaxShape::Block,
SyntaxShape::Condition,
"the condition that must be met to continue skipping",
)
.filter()
@ -35,26 +31,57 @@ impl WholeStreamCommand for SkipWhile {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
args.process(registry, skip_while)?.run()
}
}
let registry = registry.clone();
let call_info = args.evaluate_once(&registry)?;
pub fn skip_while(
SkipWhileArgs { condition }: SkipWhileArgs,
RunnableContext { input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let objects = input.values.skip_while(move |item| {
trace!("ITEM = {:?}", item);
let result = condition.invoke(&Scope::new(item.clone()));
trace!("RESULT = {:?}", result);
let block = call_info.args.expect_nth(0)?.clone();
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
let condition = match block {
Value {
value: UntaggedValue::Block(block),
tag,
} => match block.list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
))
}
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
},
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
};
futures::future::ready(return_value)
});
let objects = call_info.input.values.skip_while(move |item| {
let condition = condition.clone();
trace!("ITEM = {:?}", item);
let result = evaluate_baseline_expr(&*condition, &registry, &Scope::new(item.clone()));
trace!("RESULT = {:?}", result);
Ok(objects.from_input_stream())
let return_value = match result {
Ok(ref v) if v.is_true() => true,
_ => false,
};
futures::future::ready(return_value)
});
Ok(objects.from_input_stream())
}
}

View File

@ -1,8 +1,12 @@
use crate::commands::PerItemCommand;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{
hir::ClassifiedCommand, CallInfo, ReturnSuccess, Scope, Signature, SyntaxShape, UntaggedValue,
Value,
};
pub struct Where;
@ -14,7 +18,7 @@ impl PerItemCommand for Where {
fn signature(&self) -> Signature {
Signature::build("where").required(
"condition",
SyntaxShape::Block,
SyntaxShape::Condition,
"the condition that must match",
)
}
@ -26,37 +30,57 @@ impl PerItemCommand for Where {
fn run(
&self,
call_info: &CallInfo,
_registry: &CommandRegistry,
registry: &CommandRegistry,
_raw_args: &RawCommandArgs,
input: Value,
) -> Result<OutputStream, ShellError> {
let condition = call_info.args.expect_nth(0)?;
let stream = match condition {
let block = call_info.args.expect_nth(0)?.clone();
let condition = match block {
Value {
value: UntaggedValue::Block(block),
..
} => {
let result = block.invoke(&Scope::new(input.clone()));
match result {
Ok(v) => {
if v.is_true() {
VecDeque::from(vec![Ok(ReturnSuccess::Value(input))])
} else {
VecDeque::new()
}
tag,
} => match block.list.get(0) {
Some(item) => match item {
ClassifiedCommand::Expr(expr) => expr.clone(),
_ => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
))
}
Err(e) => return Err(e),
},
None => {
return Err(ShellError::labeled_error(
"Expected a condition",
"expected a condition",
tag,
));
}
}
},
Value { tag, .. } => {
return Err(ShellError::labeled_error(
"Expected a condition",
"where needs a condition",
"expected a condition",
tag,
))
));
}
};
let condition = evaluate_baseline_expr(&condition, registry, &Scope::new(input.clone()))?;
let stream = match condition.as_bool() {
Ok(b) => {
if b {
VecDeque::from(vec![Ok(ReturnSuccess::Value(input))])
} else {
VecDeque::new()
}
}
Err(e) => return Err(e),
};
Ok(stream.into())
}
}

View File

@ -1,11 +1,13 @@
use crate::commands::{command::CommandArgs, Command, UnevaluatedCallInfo};
use crate::commands::{
command::CommandArgs, command::RawCommandArgs, Command, UnevaluatedCallInfo,
};
use crate::env::host::Host;
use crate::shell::shell_manager::ShellManager;
use crate::stream::{InputStream, OutputStream};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::{hir, SignatureRegistry};
use nu_protocol::Signature;
use nu_parser::SignatureRegistry;
use nu_protocol::{hir, Signature};
use nu_source::{Tag, Text};
use parking_lot::Mutex;
use std::error::Error;
@ -40,12 +42,6 @@ impl CommandRegistry {
}
impl CommandRegistry {
pub(crate) fn empty() -> CommandRegistry {
CommandRegistry {
registry: Arc::new(Mutex::new(IndexMap::default())),
}
}
pub(crate) fn get_command(&self, name: &str) -> Option<Arc<Command>> {
let registry = self.registry.lock();
@ -92,6 +88,30 @@ impl Context {
&self.registry
}
pub(crate) fn from_raw(raw_args: &RawCommandArgs, registry: &CommandRegistry) -> Context {
#[cfg(windows)]
{
Context {
registry: registry.clone(),
host: raw_args.host.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: raw_args.ctrl_c.clone(),
shell_manager: raw_args.shell_manager.clone(),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
}
}
#[cfg(not(windows))]
{
Context {
registry: registry.clone(),
host: raw_args.host.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: raw_args.ctrl_c.clone(),
shell_manager: raw_args.shell_manager.clone(),
}
}
}
pub(crate) fn basic() -> Result<Context, Box<dyn Error>> {
let registry = CommandRegistry::new();

View File

@ -1,16 +1,11 @@
pub(crate) mod shape;
use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use bigdecimal::BigDecimal;
use chrono::{DateTime, Utc};
use derive_new::new;
use log::trace;
use nu_errors::ShellError;
use nu_parser::hir;
use nu_protocol::{
Evaluate, EvaluateTrait, Primitive, Scope, ShellTypeName, SpannedTypeName, TaggedDictBuilder,
UntaggedValue, Value,
hir, Primitive, ShellTypeName, SpannedTypeName, TaggedDictBuilder, UntaggedValue, Value,
};
use nu_source::Tag;
use nu_value_ext::ValueExt;
@ -29,42 +24,12 @@ pub struct Operation {
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Serialize, Deserialize, new)]
pub struct Block {
pub(crate) expressions: Vec<hir::SpannedExpression>,
pub(crate) commands: hir::Commands,
pub(crate) tag: Tag,
}
interfaces!(Block: dyn ObjectHash);
#[typetag::serde]
impl EvaluateTrait for Block {
fn invoke(&self, scope: &Scope) -> Result<Value, ShellError> {
if self.expressions.is_empty() {
return Ok(UntaggedValue::nothing().into_value(&self.tag));
}
let mut last = Ok(UntaggedValue::nothing().into_value(&self.tag));
trace!(
"EXPRS = {:?}",
self.expressions
.iter()
.map(|e| format!("{:?}", e))
.collect::<Vec<_>>()
);
for expr in self.expressions.iter() {
last = evaluate_baseline_expr(&expr, &CommandRegistry::empty(), &scope)
}
last
}
fn clone_box(&self) -> Evaluate {
let block = self.clone();
Evaluate::new(block)
}
}
#[derive(Serialize, Deserialize)]
pub enum Switch {
Present,

View File

@ -1,5 +1,4 @@
use nu_parser::hir::Number;
use nu_protocol::Primitive;
use nu_protocol::{hir::Number, Primitive};
pub fn number(number: impl Into<Number>) -> Primitive {
let number = number.into();

View File

@ -3,7 +3,7 @@ use crate::data::base::shape::{Column, InlineShape};
use crate::data::primitive::style_primitive;
use chrono::DateTime;
use nu_errors::ShellError;
use nu_parser::hir::CompareOperator;
use nu_protocol::hir::CompareOperator;
use nu_protocol::{Primitive, Type, UntaggedValue};
use nu_source::{DebugDocBuilder, PrettyDebug, Tagged};

View File

@ -1,7 +1,8 @@
use log::trace;
use nu_errors::{CoerceInto, ShellError};
use nu_protocol::{
CallInfo, ColumnPath, Evaluate, Primitive, RangeInclusion, ShellTypeName, UntaggedValue, Value,
hir::Commands, CallInfo, ColumnPath, Primitive, RangeInclusion, ShellTypeName, UntaggedValue,
Value,
};
use nu_source::{HasSpan, Spanned, SpannedItem, Tagged, TaggedItem};
use nu_value_ext::ValueExt;
@ -368,7 +369,7 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut ConfigDeserializer<'de> {
))
}
};
return visit::<Evaluate, _>(block, name, fields, visitor);
return visit::<Commands, _>(block, name, fields, visitor);
}
if name == "ColumnPath" {

View File

@ -3,8 +3,7 @@ use crate::context::CommandRegistry;
use crate::evaluate::evaluate_baseline_expr;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_parser::hir;
use nu_protocol::{EvaluatedArgs, Scope, UntaggedValue, Value};
use nu_protocol::{hir, EvaluatedArgs, Scope, UntaggedValue, Value};
pub(crate) fn evaluate_args(
call: &hir::Call,

View File

@ -1,13 +1,11 @@
use crate::context::CommandRegistry;
use crate::data::base::Block;
use crate::evaluate::operator::apply_operator;
use crate::prelude::*;
use log::trace;
use nu_errors::{ArgumentError, ShellError};
use nu_parser::hir::{self, Expression, SpannedExpression};
use nu_protocol::hir::{self, Expression, SpannedExpression};
use nu_protocol::{
ColumnPath, Evaluate, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue,
Value,
ColumnPath, Primitive, RangeInclusion, Scope, UnspannedPathMember, UntaggedValue, Value,
};
pub(crate) fn evaluate_baseline_expr(
@ -81,11 +79,7 @@ pub(crate) fn evaluate_baseline_expr(
Ok(UntaggedValue::Table(exprs).into_value(tag))
}
Expression::Block(block) => Ok(UntaggedValue::Block(Evaluate::new(Block::new(
block.clone(),
tag.clone(),
)))
.into_value(&tag)),
Expression::Block(block) => Ok(UntaggedValue::Block(block.clone()).into_value(&tag)),
Expression::Path(path) => {
let value = evaluate_baseline_expr(&path.head, registry, scope)?;
let mut item = value;
@ -138,8 +132,8 @@ fn evaluate_literal(literal: &hir::Literal, span: Span) -> Value {
.into_value(span)
}
hir::Literal::Number(int) => match int {
nu_parser::hir::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
nu_parser::hir::Number::Decimal(d) => {
nu_protocol::hir::Number::Int(i) => UntaggedValue::int(i.clone()).into_value(span),
nu_protocol::hir::Number::Decimal(d) => {
UntaggedValue::decimal(d.clone()).into_value(span)
}
},
@ -166,7 +160,10 @@ fn evaluate_reference(name: &hir::Variable, scope: &Scope, tag: Tag) -> Result<V
}
}
fn evaluate_external(external: &hir::ExternalCommand, _scope: &Scope) -> Result<Value, ShellError> {
fn evaluate_external(
external: &hir::ExternalStringCommand,
_scope: &Scope,
) -> Result<Value, ShellError> {
Err(ShellError::syntax_error(
"Unexpected external command".spanned(external.name.span),
))

View File

@ -1,5 +1,5 @@
use crate::data::value;
use nu_parser::hir::CompareOperator;
use nu_protocol::hir::CompareOperator;
use nu_protocol::{Primitive, ShellTypeName, UntaggedValue, Value};
use std::ops::Not;

View File

@ -91,7 +91,7 @@ pub(crate) use async_stream::stream as async_stream;
pub(crate) use bigdecimal::BigDecimal;
pub(crate) use futures::stream::BoxStream;
pub(crate) use futures::{FutureExt, Stream, StreamExt};
pub(crate) use nu_protocol::{EvaluateTrait, MaybeOwned};
pub(crate) use nu_protocol::MaybeOwned;
pub(crate) use nu_source::{
b, AnchorLocation, DebugDocBuilder, PrettyDebug, PrettyDebugWithSource, Span, SpannedItem, Tag,
TaggedItem, Text,

View File

@ -112,9 +112,9 @@ impl NuCompleter {
let result = nu_parser::classify_pipeline(&lite_parse, &self.commands);
for command in result.commands.list {
if let nu_parser::ClassifiedCommand::Internal(nu_parser::InternalCommand {
args, ..
}) = command
if let nu_protocol::hir::ClassifiedCommand::Internal(
nu_protocol::hir::InternalCommand { args, .. },
) = command
{
if replace_pos >= args.span.start() && replace_pos <= args.span.end() {
if let Some(named) = args.named {

View File

@ -1,7 +1,7 @@
use crate::context::Context;
use ansi_term::{Color, Style};
use nu_parser::hir::FlatShape;
use nu_parser::SignatureRegistry;
use nu_protocol::hir::FlatShape;
use nu_source::{Span, Spanned, Tag, Tagged};
use rustyline::completion::Completer;
use rustyline::error::ReadlineError;

View File

@ -2,7 +2,7 @@ use crate::data::value::compare_values;
use crate::data::TaggedListBuilder;
use chrono::{DateTime, NaiveDate, Utc};
use nu_errors::ShellError;
use nu_parser::hir::CompareOperator;
use nu_protocol::hir::CompareOperator;
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{SpannedItem, Tag, Tagged, TaggedItem};
use nu_value_ext::{get_data_by_key, ValueExt};

View File

@ -0,0 +1,13 @@
use nu_test_support::{nu, pipeline};
#[test]
fn each_works_separately() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [1 2 3] | each { echo $it 10 | sum } | to-json | echo $it
"#
));
assert_eq!(actual, "[11,12,13]");
}

View File

@ -4,6 +4,7 @@ mod cd;
mod compact;
mod cp;
mod default;
mod each;
mod edit;
mod enter;
mod first;

View File

@ -13,7 +13,7 @@ use std::ops::Range;
/// A structured reason for a ParseError. Note that parsing in nu is more like macro expansion in
/// other languages, so the kinds of errors that can occur during parsing are more contextual than
/// you might expect.
#[derive(Debug, Clone, Eq, PartialEq)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum ParseErrorReason {
/// The parser encountered an EOF rather than what it was expecting
Eof { expected: String, span: Span },
@ -38,7 +38,7 @@ pub enum ParseErrorReason {
}
/// A newtype for `ParseErrorReason`
#[derive(Debug, Clone, Eq, PartialEq, Getters)]
#[derive(Debug, Clone, Getters, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct ParseError {
#[get = "pub"]
reason: ParseErrorReason,

View File

@ -1,5 +1,4 @@
mod files;
pub mod hir;
mod lite_parse;
mod parse;
mod shapes;
@ -7,10 +6,6 @@ mod signature;
pub use crate::files::Files;
pub use crate::lite_parse::{lite_parse, LitePipeline};
pub use crate::parse::{
classify_pipeline, garbage, ClassifiedCommand, ClassifiedPipeline, Commands, InternalCommand,
};
pub use crate::parse::{classify_pipeline, garbage};
pub use crate::shapes::shapes;
pub use crate::signature::{
ExternalArg, ExternalArgs, ExternalCommand, Signature, SignatureRegistry,
};
pub use crate::signature::{Signature, SignatureRegistry};

View File

@ -1,83 +1,17 @@
use std::path::Path;
use nu_errors::{ArgumentError, ParseError};
//use crate::hir::*;
use crate::hir::{
Binary, CompareOperator, Expression, Flag, FlagKind, Member, NamedArguments, SpannedExpression,
Unit,
};
use crate::lite_parse::{lite_parse, LiteCommand, LitePipeline};
use crate::signature::SignatureRegistry;
use crate::{ExternalArg, ExternalArgs, ExternalCommand};
use nu_errors::{ArgumentError, ParseError};
use nu_protocol::hir::{
self, Binary, ClassifiedCommand, ClassifiedPipeline, Commands, CompareOperator, Expression,
ExternalArg, ExternalArgs, ExternalCommand, Flag, FlagKind, InternalCommand, Member,
NamedArguments, SpannedExpression, Unit,
};
use nu_protocol::{NamedType, PositionalType, Signature, SyntaxShape, UnspannedPathMember};
use nu_source::{Span, Spanned, SpannedItem, Tag};
use num_bigint::BigInt;
#[derive(Debug, Clone)]
pub struct InternalCommand {
pub name: String,
pub name_span: Span,
pub args: crate::hir::Call,
}
impl InternalCommand {
pub fn new(name: String, name_span: Span, full_span: Span) -> InternalCommand {
InternalCommand {
name: name.clone(),
name_span,
args: crate::hir::Call::new(
Box::new(SpannedExpression::new(Expression::string(name), name_span)),
full_span,
),
}
}
}
#[derive(Debug, Clone)]
pub enum ClassifiedCommand {
#[allow(unused)]
Comparison(
Box<SpannedExpression>,
Box<SpannedExpression>,
Box<SpannedExpression>,
),
#[allow(unused)]
Dynamic(crate::hir::Call),
Internal(InternalCommand),
External(crate::ExternalCommand),
Error(ParseError),
}
#[derive(Debug, Clone)]
pub struct Commands {
pub list: Vec<ClassifiedCommand>,
pub span: Span,
}
impl Commands {
pub fn new(span: Span) -> Commands {
Commands { list: vec![], span }
}
pub fn push(&mut self, command: ClassifiedCommand) {
self.list.push(command);
}
}
#[derive(Debug, Clone)]
pub struct ClassifiedPipeline {
pub commands: Commands,
// this is not a Result to make it crystal clear that these shapes
// aren't intended to be used directly with `?`
pub failed: Option<ParseError>,
}
impl ClassifiedPipeline {
pub fn new(commands: Commands, failed: Option<ParseError>) -> ClassifiedPipeline {
ClassifiedPipeline { commands, failed }
}
}
/// Parses a simple column path, one without a variable (implied or explicit) at the head
fn parse_simple_column_path(lite_arg: &Spanned<String>) -> (SpannedExpression, Option<ParseError>) {
let mut delimiter = '.';
@ -534,53 +468,23 @@ fn parse_arg(
(Some('{'), Some('}')) => {
// We have a literal block
let string: String = chars.collect();
let mut error = None;
// We haven't done much with the inner string, so let's go ahead and work with it
let lite_pipeline = match lite_parse(&string, lite_arg.span.start() + 1) {
Ok(lp) => lp,
Err(e) => return (garbage(lite_arg.span), Some(e)),
};
//let pipeline = parse(&lite_pipeline, definitions)?;
// For now, just take the first command
if let Some(lite_cmd) = lite_pipeline.commands.first() {
if lite_cmd.args.len() != 2 {
return (
garbage(lite_arg.span),
Some(ParseError::mismatch("block", lite_arg.clone())),
);
}
let (lhs, err) =
parse_arg(SyntaxShape::FullColumnPath, registry, &lite_cmd.name);
if error.is_none() {
error = err;
}
let (op, err) =
parse_arg(SyntaxShape::Operator, registry, &lite_cmd.args[0]);
if error.is_none() {
error = err;
}
let (rhs, err) = parse_arg(SyntaxShape::Any, registry, &lite_cmd.args[1]);
if error.is_none() {
error = err;
}
let classified_block = classify_pipeline(&lite_pipeline, registry);
let error = classified_block.failed;
let span = Span::new(lhs.span.start(), rhs.span.end());
let binary = SpannedExpression::new(
Expression::Binary(Box::new(Binary::new(lhs, op, rhs))),
span,
);
(
SpannedExpression::new(Expression::Block(vec![binary]), span),
error,
)
} else {
(
garbage(lite_arg.span),
Some(ParseError::mismatch("block", lite_arg.clone())),
)
}
(
SpannedExpression::new(
Expression::Block(classified_block.commands),
lite_arg.span,
),
error,
)
}
_ => {
// We have an implied block, but we can't parse this here
@ -592,6 +496,14 @@ fn parse_arg(
}
}
}
SyntaxShape::Condition => {
// We have an implied condition, but we can't parse this here
// it needed to have been parsed up higher where we have control over more than one arg
(
garbage(lite_arg.span),
Some(ParseError::mismatch("condition", lite_arg.clone())),
)
}
}
}
@ -664,13 +576,10 @@ fn classify_positional_arg(
let mut idx = idx;
let mut error = None;
let arg = match positional_type {
PositionalType::Mandatory(_, SyntaxShape::Block)
| PositionalType::Optional(_, SyntaxShape::Block) => {
// We may have an implied block, so let's try to parse it here
// The only implied block format we currently support is <shorthand path> <operator> <any>, though
// we may want to revisit this in the future
// TODO: only do this step if it's not a literal block
PositionalType::Mandatory(_, SyntaxShape::Condition)
| PositionalType::Optional(_, SyntaxShape::Condition) => {
// A condition can take up multiple arguments, as we build the operation as <arg> <operator> <arg>
// We need to do this here because in parse_arg, we have access to only one arg at a time
if (idx + 2) < lite_cmd.args.len() {
let (lhs, err) =
parse_arg(SyntaxShape::FullColumnPath, registry, &lite_cmd.args[idx]);
@ -691,9 +600,19 @@ fn classify_positional_arg(
Expression::Binary(Box::new(Binary::new(lhs, op, rhs))),
span,
);
SpannedExpression::new(Expression::Block(vec![binary]), span)
let mut commands = hir::Commands::new(span);
commands.push(ClassifiedCommand::Expr(Box::new(binary)));
SpannedExpression::new(Expression::Block(commands), span)
} else if idx < lite_cmd.args.len() {
let (arg, err) =
parse_arg(SyntaxShape::FullColumnPath, registry, &lite_cmd.args[idx]);
if error.is_none() {
error = err;
}
arg
} else {
let (arg, err) = parse_arg(SyntaxShape::Block, registry, &lite_cmd.args[idx]);
// TODO - this needs to get fixed and return an actual error
let (arg, err) = parse_arg(SyntaxShape::Condition, registry, &lite_cmd.args[idx]);
if error.is_none() {
error = err;
}

View File

@ -1,18 +1,11 @@
use crate::hir::*;
use crate::parse::{ClassifiedCommand, Commands};
use nu_protocol::hir::*;
use nu_protocol::UnspannedPathMember;
use nu_source::{Spanned, SpannedItem};
/// Converts a SpannedExpression into a spanned shape(s) ready for color-highlighting
pub fn expression_to_flat_shape(e: &SpannedExpression) -> Vec<Spanned<FlatShape>> {
match &e.expr {
Expression::Block(exprs) => {
let mut output = vec![];
for expr in exprs.iter() {
output.append(&mut expression_to_flat_shape(expr));
}
output
}
Expression::Block(exprs) => shapes(exprs),
Expression::FilePath(_) => vec![FlatShape::Path.spanned(e.span)],
Expression::Garbage => vec![FlatShape::Garbage.spanned(e.span)],
Expression::List(exprs) => {
@ -106,6 +99,7 @@ pub fn shapes(commands: &Commands) -> Vec<Spanned<FlatShape>> {
output.push(FlatShape::ExternalWord.spanned(arg.tag.span));
}
}
ClassifiedCommand::Expr(expr) => output.append(&mut expression_to_flat_shape(expr)),
_ => {}
}
}

View File

@ -1,6 +1,6 @@
use std::fmt::Debug;
use nu_source::{b, DebugDocBuilder, HasSpan, PrettyDebug, PrettyDebugWithSource, Span, Tag};
use nu_source::{DebugDocBuilder, HasSpan, PrettyDebugWithSource, Span};
pub trait SignatureRegistry: Debug {
fn has(&self, name: &str) -> bool;
@ -46,99 +46,3 @@ impl PrettyDebugWithSource for Signature {
self.unspanned.pretty_debug(source)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ExternalArg {
pub arg: String,
pub tag: Tag,
}
impl ExternalArg {
pub fn has(&self, name: &str) -> bool {
self.arg == name
}
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 {
type Target = str;
fn deref(&self) -> &str {
&self.arg
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ExternalArgs {
pub list: Vec<ExternalArg>,
pub span: Span,
}
impl ExternalArgs {
pub fn iter(&self) -> impl Iterator<Item = &ExternalArg> {
self.list.iter()
}
}
impl std::ops::Deref for ExternalArgs {
type Target = [ExternalArg];
fn deref(&self) -> &[ExternalArg] {
&self.list
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ExternalCommand {
pub name: String,
pub name_tag: Tag,
pub args: ExternalArgs,
}
impl ExternalCommand {
pub fn has_it_argument(&self) -> bool {
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())
}
}
impl PrettyDebug for ExternalCommand {
fn pretty(&self) -> DebugDocBuilder {
b::typed(
"external command",
b::description(&self.name)
+ b::preceded(
b::space(),
b::intersperse(
self.args.iter().map(|a| b::primitive(a.arg.to_string())),
b::space(),
),
),
)
}
}
impl HasSpan for ExternalCommand {
fn span(&self) -> Span {
self.name_tag.span.until(self.args.span)
}
}

View File

@ -28,6 +28,7 @@ typetag = "0.1.4"
query_interface = "0.3.5"
byte-unit = "3.0.3"
natural = "0.5.0"
log = "0.4.8"
# implement conversions
serde_yaml = "0.8"

View File

@ -1,16 +1,20 @@
use std::cmp::{Ord, Ordering, PartialOrd};
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::{hir, Primitive, UntaggedValue};
use crate::{PathMember, ShellTypeName};
use derive_new::new;
use nu_protocol::{PathMember, ShellTypeName};
use nu_protocol::{Primitive, UntaggedValue};
use num_traits::ToPrimitive;
use nu_errors::ParseError;
use nu_source::{
b, DebugDocBuilder, HasSpan, PrettyDebug, PrettyDebugRefineKind, PrettyDebugWithSource,
};
use nu_source::{IntoSpanned, Span, Spanned, SpannedItem};
use nu_source::{IntoSpanned, Span, Spanned, SpannedItem, Tag};
use bigdecimal::BigDecimal;
use indexmap::IndexMap;
@ -19,12 +23,168 @@ use num_bigint::BigInt;
use num_traits::identities::Zero;
use num_traits::FromPrimitive;
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct InternalCommand {
pub name: String,
pub name_span: Span,
pub args: crate::hir::Call,
}
impl InternalCommand {
pub fn new(name: String, name_span: Span, full_span: Span) -> InternalCommand {
InternalCommand {
name: name.clone(),
name_span,
args: crate::hir::Call::new(
Box::new(SpannedExpression::new(Expression::string(name), name_span)),
full_span,
),
}
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct ClassifiedPipeline {
pub commands: Commands,
// this is not a Result to make it crystal clear that these shapes
// aren't intended to be used directly with `?`
pub failed: Option<ParseError>,
}
impl ClassifiedPipeline {
pub fn new(commands: Commands, failed: Option<ParseError>) -> ClassifiedPipeline {
ClassifiedPipeline { commands, failed }
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum ClassifiedCommand {
Expr(Box<SpannedExpression>),
#[allow(unused)]
Dynamic(crate::hir::Call),
Internal(InternalCommand),
External(crate::hir::ExternalCommand),
Error(ParseError),
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct Commands {
pub list: Vec<ClassifiedCommand>,
pub span: Span,
}
impl Commands {
pub fn new(span: Span) -> Commands {
Commands { list: vec![], span }
}
pub fn push(&mut self, command: ClassifiedCommand) {
self.list.push(command);
}
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Deserialize, Serialize)]
pub struct ExternalCommand {
pub struct ExternalStringCommand {
pub name: Spanned<String>,
pub args: Vec<Spanned<String>>,
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct ExternalArg {
pub arg: String,
pub tag: Tag,
}
impl ExternalArg {
pub fn has(&self, name: &str) -> bool {
self.arg == name
}
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 {
type Target = str;
fn deref(&self) -> &str {
&self.arg
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct ExternalArgs {
pub list: Vec<ExternalArg>,
pub span: Span,
}
impl ExternalArgs {
pub fn iter(&self) -> impl Iterator<Item = &ExternalArg> {
self.list.iter()
}
}
impl std::ops::Deref for ExternalArgs {
type Target = [ExternalArg];
fn deref(&self) -> &[ExternalArg] {
&self.list
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct ExternalCommand {
pub name: String,
pub name_tag: Tag,
pub args: ExternalArgs,
}
impl ExternalCommand {
pub fn has_it_argument(&self) -> bool {
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())
}
}
impl PrettyDebug for ExternalCommand {
fn pretty(&self) -> DebugDocBuilder {
b::typed(
"external command",
b::description(&self.name)
+ b::preceded(
b::space(),
b::intersperse(
self.args.iter().map(|a| b::primitive(a.arg.to_string())),
b::space(),
),
),
)
}
}
impl HasSpan for ExternalCommand {
fn span(&self) -> Span {
self.name_tag.span.until(self.args.span)
}
}
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone, Hash, Copy, Deserialize, Serialize)]
pub enum Unit {
// Filesize units
@ -558,12 +718,12 @@ pub enum Expression {
Variable(Variable),
Binary(Box<Binary>),
Range(Box<Range>),
Block(Vec<SpannedExpression>),
Block(hir::Commands),
List(Vec<SpannedExpression>),
Path(Box<Path>),
FilePath(PathBuf),
ExternalCommand(ExternalCommand),
ExternalCommand(ExternalStringCommand),
Command(Span),
Boolean(bool),
@ -664,7 +824,7 @@ impl Expression {
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub enum NamedValue {
AbsentSwitch,
PresentSwitch(Span),
@ -695,7 +855,7 @@ impl PrettyDebugWithSource for NamedValue {
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
pub struct Call {
pub head: Box<SpannedExpression>,
pub positional: Option<Vec<SpannedExpression>>,
@ -711,7 +871,7 @@ impl Call {
.unwrap_or(false)
}
pub fn set_initial_flags(&mut self, signature: &nu_protocol::Signature) {
pub fn set_initial_flags(&mut self, signature: &crate::Signature) {
for (named, value) in signature.named.iter() {
if self.named.is_none() {
self.named = Some(NamedArguments::new());
@ -719,7 +879,7 @@ impl Call {
if let Some(ref mut args) = self.named {
match value.0 {
nu_protocol::NamedType::Switch(_) => args.insert_switch(named, None),
crate::NamedType::Switch(_) => args.insert_switch(named, None),
_ => args.insert_optional(named, Span::new(0, 0), None),
}
}
@ -817,11 +977,56 @@ pub enum FlatShape {
Size { number: Span, unit: Span },
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct NamedArguments {
pub named: IndexMap<String, NamedValue>,
}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for NamedArguments {
/// Create the hash function to allow the Hash trait for dictionaries
fn hash<H: Hasher>(&self, state: &mut H) {
let mut entries = self.named.clone();
entries.sort_keys();
entries.keys().collect::<Vec<&String>>().hash(state);
entries.values().collect::<Vec<&NamedValue>>().hash(state);
}
}
impl PartialOrd for NamedArguments {
/// Compare two dictionaries for sort ordering
fn partial_cmp(&self, other: &NamedArguments) -> Option<Ordering> {
let this: Vec<&String> = self.named.keys().collect();
let that: Vec<&String> = other.named.keys().collect();
if this != that {
return this.partial_cmp(&that);
}
let this: Vec<&NamedValue> = self.named.values().collect();
let that: Vec<&NamedValue> = self.named.values().collect();
this.partial_cmp(&that)
}
}
impl Ord for NamedArguments {
/// Compare two dictionaries for ordering
fn cmp(&self, other: &NamedArguments) -> Ordering {
let this: Vec<&String> = self.named.keys().collect();
let that: Vec<&String> = other.named.keys().collect();
if this != that {
return this.cmp(&that);
}
let this: Vec<&NamedValue> = self.named.values().collect();
let that: Vec<&NamedValue> = self.named.values().collect();
this.cmp(&that)
}
}
impl NamedArguments {
pub fn new() -> NamedArguments {
Default::default()

View File

@ -2,6 +2,7 @@
mod macros;
mod call_info;
pub mod hir;
mod maybe_owned;
mod return_value;
mod signature;
@ -19,7 +20,7 @@ pub use crate::type_name::{PrettyType, ShellTypeName, SpannedTypeName};
pub use crate::type_shape::{Row as RowType, Type};
pub use crate::value::column_path::{did_you_mean, ColumnPath, PathMember, UnspannedPathMember};
pub use crate::value::dict::{Dictionary, TaggedDictBuilder};
pub use crate::value::evaluate::{Evaluate, EvaluateTrait, Scope};
pub use crate::value::evaluate::Scope;
pub use crate::value::primitive::Primitive;
pub use crate::value::primitive::{format_date, format_duration, format_primitive};
pub use crate::value::range::{Range, RangeInclusion};

View File

@ -30,6 +30,8 @@ pub enum SyntaxShape {
Unit,
/// An operator
Operator,
/// A condition, eg `foo > 1`
Condition,
}
impl PrettyDebug for SyntaxShape {
@ -49,6 +51,7 @@ impl PrettyDebug for SyntaxShape {
SyntaxShape::Table => "table",
SyntaxShape::Unit => "unit",
SyntaxShape::Operator => "operator",
SyntaxShape::Condition => "condition",
})
}
}

View File

@ -8,9 +8,9 @@ pub mod range;
mod serde_bigdecimal;
mod serde_bigint;
use crate::hir;
use crate::type_name::{ShellTypeName, SpannedTypeName};
use crate::value::dict::Dictionary;
use crate::value::evaluate::Evaluate;
use crate::value::primitive::Primitive;
use crate::value::range::{Range, RangeInclusion};
use crate::{ColumnPath, PathMember};
@ -38,7 +38,7 @@ pub enum UntaggedValue {
Error(ShellError),
/// A block of Nu code, eg `{ ls | get name }`
Block(Evaluate),
Block(hir::Commands),
}
impl UntaggedValue {

View File

@ -1,9 +1,5 @@
use crate::value::{Primitive, UntaggedValue, Value};
use indexmap::IndexMap;
use nu_errors::ShellError;
use query_interface::{interfaces, vtable_for, Object, ObjectHash};
use serde::{Deserialize, Serialize};
use std::cmp::{Ord, Ordering, PartialOrd};
use std::fmt::Debug;
/// An evaluation scope. Scopes map variable names to Values and aid in evaluating blocks and expressions.
@ -42,67 +38,3 @@ impl Scope {
}
}
}
#[typetag::serde(tag = "type")]
pub trait EvaluateTrait: Debug + Send + Sync + Object + ObjectHash + 'static {
fn invoke(&self, scope: &Scope) -> Result<Value, ShellError>;
fn clone_box(&self) -> Evaluate;
}
interfaces!(Evaluate: dyn ObjectHash);
#[typetag::serde]
impl EvaluateTrait for Evaluate {
fn invoke(&self, scope: &Scope) -> Result<Value, ShellError> {
self.expr.invoke(scope)
}
fn clone_box(&self) -> Evaluate {
self.expr.clone_box()
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Evaluate {
expr: Box<dyn EvaluateTrait>,
}
impl Evaluate {
pub fn new(evaluate: impl EvaluateTrait) -> Evaluate {
Evaluate {
expr: Box::new(evaluate),
}
}
}
impl std::hash::Hash for Evaluate {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.expr.obj_hash(state)
}
}
impl Clone for Evaluate {
fn clone(&self) -> Evaluate {
self.expr.clone_box()
}
}
impl Ord for Evaluate {
fn cmp(&self, _: &Self) -> Ordering {
Ordering::Equal
}
}
impl PartialOrd for Evaluate {
fn partial_cmp(&self, _: &Evaluate) -> Option<Ordering> {
Some(Ordering::Equal)
}
}
impl PartialEq for Evaluate {
fn eq(&self, _: &Evaluate) -> bool {
true
}
}
impl Eq for Evaluate {}

View File

@ -1,4 +1,4 @@
use nu_parser::{ExternalArg, ExternalArgs, ExternalCommand};
use nu_protocol::hir::{ExternalArg, ExternalArgs, ExternalCommand};
use nu_source::{Span, SpannedItem, Tag, TaggedItem};
pub struct ExternalBuilder {