Change syntax for the whole language... :-/

This commit is contained in:
Axel Liljencrantz 2022-09-02 21:03:01 +02:00
parent ecba86aaac
commit 8d96142ac9
46 changed files with 428 additions and 294 deletions

View File

@ -39,14 +39,14 @@ Crush code in the interactive Crush prompt. Assign ANSI color codes
to the various token types of Crush to make your terminal more closely to the various token types of Crush to make your terminal more closely
resemble a Christmas tree: resemble a Christmas tree:
| Name | Description | | Name | Description |
| --- | --- | | --- |----------------------------------------------------------|
| `operator` | All the different Crush operators, such as `neg` and `+` | | `operator` | All the different Crush operators, such as `neg` and `+` |
| `numeric_literal` | Integer and floating point literals, such as `6` | | `numeric_literal` | Integer and floating point literals, such as `6` |
| `string_literal` | String literals, like `"Burrow"` | | `string_literal` | String literals, like `"Burrow"` |
| `file_literal` | File literals, like `'Cargo.toml'` | | `file_literal` | File literals, like `'Cargo.toml'` |
| `label` | Variables and members, like `global` | | `label` | Variables and members, like `$global` |
| `field` | Field definitions, such as `^name` | | `field` | Field definitions, such as `name` |
The `term` namespace contains useful constants containing ANSI color codes. The `term` namespace contains useful constants containing ANSI color codes.
A configuration example: A configuration example:

View File

@ -36,7 +36,7 @@ a Rush channel. It is not understood by the command as a series of bytes, but as
a table of rows, and Crush provides you with SQL-like commands to sort, filter, a table of rows, and Crush provides you with SQL-like commands to sort, filter,
aggregate and group rows of data. aggregate and group rows of data.
crush# ll | sort ^size crush# ll | sort size
user size modified type file user size modified type file
fox 31 2019-10-03 13:43:12 +0200 file .gitignore fox 31 2019-10-03 13:43:12 +0200 file .gitignore
fox 75 2020-03-07 17:09:15 +0100 file build.rs fox 75 2020-03-07 17:09:15 +0100 file build.rs
@ -44,7 +44,7 @@ aggregate and group rows of data.
fox 711 2019-10-03 14:19:46 +0200 file crush.iml fox 711 2019-10-03 14:19:46 +0200 file crush.iml
... ...
crush# ll | where {type == "directory"} crush# ll | where {$type == "directory"}
user size modified type file user size modified type file
fox 4_096 2019-11-22 21:56:30 +0100 directory target fox 4_096 2019-11-22 21:56:30 +0100 directory target
fox 4_096 2020-02-22 11:50:12 +0100 directory tests fox 4_096 2020-02-22 11:50:12 +0100 directory tests
@ -84,10 +84,10 @@ commands all work like you'd expect:
crush# ls | json:to ./listing.json crush# ls | json:to ./listing.json
# Read the file Cargo.toml as a toml file, and extract the dependencies-field # Read the file Cargo.toml as a toml file, and extract the dependencies-field
crush# toml:from Cargo.toml | member ^dependencies crush# toml:from Cargo.toml | member dependencies
# Fetch a web page and write it to a file # Fetch a web page and write it to a file
http "https://isitchristmas.com/" | member ^body | bin:to ./isitchristmas.html http "https://isitchristmas.com/" | member body | bin:to ./isitchristmas.html
``` ```
If you don't supply an input file to any of the deserializer commands, If you don't supply an input file to any of the deserializer commands,
@ -302,7 +302,7 @@ former displays a help messages, the latter lists the content of a value.
Example: Example:
ps | sort ^cpu ps | sort cpu
crush# dir list crush# dir list
[type, truncate, remove, clone, of, __call__, __setitem__, pop, push, empty, len, peek, new, clear] [type, truncate, remove, clone, of, __call__, __setitem__, pop, push, empty, len, peek, new, clear]
@ -369,10 +369,10 @@ re-executed until the stream is empty.
### More SQL-like data stream operations ### More SQL-like data stream operations
Crush features many commands to operate om arbitrary streams of data using a Crush features many commands to operate om arbitrary streams of data using a
SQL-like syntax. These commands use field-specifiers like `^foo` to specify SQL-like syntax. These commands use field-specifiers like `foo` to specify
columns in the data stream that they operate on: columns in the data stream that they operate on:
ps | where {user == "root"} | group ^status proc_per_status={count} | sort ^proc_per_status ps | where {$user == "root"} | group status proc_per_status={count} | sort proc_per_status
status proc_per_status status proc_per_status
Idle 108 Idle 108
Sleeping 170 Sleeping 170

View File

@ -1,23 +1,21 @@
ls := { $ls := {
|sort_by:field=^file @args| |@ $args |
"List names of files non-recursively" "List names of files non-recursively"
" Unlike find and ll, ls only shows you the names of files. " Unlike find and ll, ls only shows you the names of files.
sort_by can be one of ^user, ^size, ^modified, ^type or ^file.
Example: Example:
ls / sort_by=^size" ls /"
find recursive=false @args | sort sort_by| select ^file find recursive=$false @ $args | sort file| select file
} }
ll := { $ll := {
|sort_by:field=^file @args| |@ $args|
"List files non-recursively" "List files non-recursively"
" sort_by can be one of ^user, ^size, ^modified, ^type or ^file. "
Example: Example:
ll .. sort_by=^modified" ll .."
find recursive=false @args | sort sort_by find recursive=$false @ $args | sort file
} }

View File

@ -166,7 +166,7 @@ impl CommandNode {
error("Stray arguments") error("Stray arguments")
} }
} else { } else {
let cmd = self.expressions[0].generate_argument(env)?; let cmd = self.expressions[0].generate_command(env)?;
let arguments = self.expressions[1..] let arguments = self.expressions[1..]
.iter() .iter()
.map(|e| e.generate_argument(env)) .map(|e| e.generate_argument(env))
@ -259,7 +259,7 @@ pub enum Node {
Regex(TrackedString), Regex(TrackedString),
Field(TrackedString), Field(TrackedString),
String(TrackedString), String(TrackedString),
File(TrackedString, bool), File(TrackedString, bool), // true if filename is quoted
Integer(TrackedString), Integer(TrackedString),
Float(TrackedString), Float(TrackedString),
GetItem(Box<Node>, Box<Node>), GetItem(Box<Node>, Box<Node>),
@ -311,16 +311,44 @@ impl Node {
} }
} }
pub fn generate_command(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
self.generate(env, true)
}
pub fn generate_argument(&self, env: &Scope) -> CrushResult<ArgumentDefinition> { pub fn generate_argument(&self, env: &Scope) -> CrushResult<ArgumentDefinition> {
self.generate(env, false)
}
pub fn type_name(&self) -> &str {
match self {
Node::Assignment(_, _, _) => "assignment",
Node::Unary(_, _) => "unary operator",
Node::Glob(_) => "glob",
Node::Label(_) => "label",
Node::Regex(_) => "regular expression literal",
Node::Field(_) => "field",
Node::String(_) => "quoted string literal",
Node::File(_, _) => "file literal",
Node::Integer(_) => "integer literal",
Node::Float(_) => "floating point number literal",
Node::GetItem(_, _) => "subscript",
Node::GetAttr(_, _) => "member access",
Node::Path(_, _) => "path segment",
Node::Substitution(_) => "command substitution",
Node::Closure(_, _) => "closure",
}
}
pub fn generate(&self, env: &Scope, is_command: bool) -> CrushResult<ArgumentDefinition> {
Ok(ArgumentDefinition::unnamed(match self { Ok(ArgumentDefinition::unnamed(match self {
Node::Assignment(target, op, value) => match op.deref() { Node::Assignment(target, op, value) => match op.deref() {
"=" => { "=" => {
return match target.as_ref() { return match target.as_ref() {
Node::Label(t) => Ok(ArgumentDefinition::named( Node::Field(t) => Ok(ArgumentDefinition::named(
t.deref(), t.deref(),
propose_name(&t, value.generate_argument(env)?.unnamed_value()?), propose_name(&t, value.generate_argument(env)?.unnamed_value()?),
)), )),
_ => error("Invalid left side in named argument"), _ => error(format!("Invalid left side in named argument. Expected a field, got a {}", target.type_name())),
}; };
} }
_ => return error("Invalid assignment operator"), _ => return error("Invalid assignment operator"),
@ -367,7 +395,7 @@ impl Node {
)?), )?),
s.location), s.location),
Node::GetAttr(node, label) => { Node::GetAttr(node, label) => {
let parent = node.generate_argument(env)?; let parent = node.generate(env, is_command)?;
match parent.unnamed_value()? { match parent.unnamed_value()? {
ValueDefinition::Value(Value::Field(mut f), location) => { ValueDefinition::Value(Value::Field(mut f), location) => {
f.push(label.string.clone()); f.push(label.string.clone());
@ -380,7 +408,12 @@ impl Node {
Box::new(node.generate_argument(env)?.unnamed_value()?), Box::new(node.generate_argument(env)?.unnamed_value()?),
label.clone(), label.clone(),
), ),
Node::Field(f) => ValueDefinition::Value(Value::Field(vec![f.string[1..].to_string()]), f.location), Node::Field(f) =>
if is_command {
ValueDefinition::Label(f.clone())
} else {
ValueDefinition::Value(Value::Field(vec![f.string.to_string()]), f.location)
},
Node::Substitution(s) => ValueDefinition::JobDefinition(s.generate(env)?), Node::Substitution(s) => ValueDefinition::JobDefinition(s.generate(env)?),
Node::Closure(s, c) => { Node::Closure(s, c) => {
let param = s.as_ref().map(|v| { let param = s.as_ref().map(|v| {
@ -429,6 +462,7 @@ impl Node {
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?), ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
], ],
env, env,
true,
), ),
Node::GetAttr(container, attr) => container.method_invocation( Node::GetAttr(container, attr) => container.method_invocation(
@ -441,6 +475,7 @@ impl Node {
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?), ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
], ],
env, env,
true,
), ),
_ => error("Invalid left side in assignment"), _ => error("Invalid left side in assignment"),
@ -467,7 +502,7 @@ impl Node {
} }
Node::GetItem(val, key) => { Node::GetItem(val, key) => {
val.method_invocation(&TrackedString::from("__getitem__", key.location()), vec![key.generate_argument(env)?], env) val.method_invocation(&TrackedString::from("__getitem__", key.location()), vec![key.generate_argument(env)?], env, true)
} }
Node::Unary(op, _) => match op.string.as_ref() { Node::Unary(op, _) => match op.string.as_ref() {
@ -506,24 +541,31 @@ impl Node {
name: &TrackedString, name: &TrackedString,
arguments: Vec<ArgumentDefinition>, arguments: Vec<ArgumentDefinition>,
env: &Scope, env: &Scope,
as_command: bool,
) -> CrushResult<Option<CommandInvocation>> { ) -> CrushResult<Option<CommandInvocation>> {
Ok(Some(CommandInvocation::new( Ok(Some(CommandInvocation::new(
ValueDefinition::GetAttr( ValueDefinition::GetAttr(
Box::from(self.generate_argument(env)?.unnamed_value()?), Box::from(self.generate(env, as_command)?.unnamed_value()?),
name.clone(), name.clone(),
), ),
arguments, arguments,
))) )))
} }
pub fn parse_label_or_wildcard(s: &TrackedString) -> Box<Node> { pub fn parse_string_or_wildcard(s: &TrackedString) -> Box<Node> {
if s.string.contains('%') || s.string.contains('?') { if s.string.contains('%') || s.string.contains('?') {
Box::from(Node::Glob(s.clone())) Box::from(Node::Glob(s.clone()))
} else if s.string.contains('/') || s.string.contains('.') {
Box::from(Node::File(s.clone(), false))
} else { } else {
Box::from(Node::Label(s.clone())) Box::from(Node::Field(s.clone()))
} }
} }
pub fn parse_label(s: &TrackedString) -> Box<Node> {
Box::from(Node::Label(TrackedString::from(&s.string[1..], s.location)))
}
pub fn parse_file_or_wildcard(s: &TrackedString) -> Box<Node> { pub fn parse_file_or_wildcard(s: &TrackedString) -> Box<Node> {
if s.string.contains('%') || s.string.contains('?') { if s.string.contains('%') || s.string.contains('?') {
Box::from(Node::Glob(s.clone())) Box::from(Node::Glob(s.clone()))
@ -584,7 +626,7 @@ fn expand_user(s: &str, location: Location) -> Box<Node> {
simple_substitution( simple_substitution(
vec![ vec![
attr(&vec!["global", "user", "find"], location), attr(&vec!["global", "user", "find"], location),
Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location)) Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location)),
], ],
location, location,
), ),
@ -660,7 +702,8 @@ pub enum TokenType {
FactorOperator, FactorOperator,
TermOperator, TermOperator,
QuotedString, QuotedString,
LabelOrWildcard, StringOrWildcard,
Label,
Flag, Flag,
Field, Field,
QuotedFile, QuotedFile,

View File

@ -2,7 +2,7 @@ use crate::lang::argument::{Argument, ArgumentDefinition, ArgumentType};
use crate::lang::command::{BoundCommand, Command, CrushCommand, OutputType, Parameter, ArgumentDescription}; use crate::lang::command::{BoundCommand, Command, CrushCommand, OutputType, Parameter, ArgumentDescription};
use crate::lang::command_invocation::CommandInvocation; use crate::lang::command_invocation::CommandInvocation;
use crate::lang::data::dict::Dict; use crate::lang::data::dict::Dict;
use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult}; use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult, argument_error};
use crate::lang::execution_context::{CompileContext, CommandContext, JobContext}; use crate::lang::execution_context::{CompileContext, CommandContext, JobContext};
use crate::lang::help::Help; use crate::lang::help::Help;
use crate::lang::job::Job; use crate::lang::job::Job;
@ -595,7 +595,11 @@ impl Closure {
if named.contains_key(&name.string) { if named.contains_key(&name.string) {
let value = named.remove(&name.string).unwrap(); let value = named.remove(&name.string).unwrap();
if !value_type.is(&value) { if !value_type.is(&value) {
return argument_error_legacy("Wrong parameter type"); return argument_error(
format!(
"Wrong parameter type {}, expected {}",
value.value_type(), value_type),
name.location);
} }
context.env.redeclare(&name.string, value)?; context.env.redeclare(&name.string, value)?;
} else if !unnamed.is_empty() { } else if !unnamed.is_empty() {
@ -604,7 +608,12 @@ impl Closure {
let env = context.env.clone(); let env = context.env.clone();
env.redeclare(&name.string, default.eval_and_bind(context)?)?; env.redeclare(&name.string, default.eval_and_bind(context)?)?;
} else { } else {
return argument_error_legacy("Missing variable!!!"); return argument_error(
format!(
"Missing variable {}. Options are {}!!!",
name.string,
named.keys().map(|a|{a.to_string()}).collect::<Vec<String>>().join(", ")),
name.location);
} }
} else { } else {
return argument_error_legacy("Not a type"); return argument_error_legacy("Not a type");

View File

@ -1,11 +1,10 @@
use crate::lang::errors::{error, CrushResult, CrushErrorType}; use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::{CompileContext, JobContext}; use crate::lang::execution_context::{CompileContext, JobContext};
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::{argument::ArgumentDefinition, argument::ArgumentVecCompiler, value::Value}; use crate::lang::{argument::ArgumentDefinition, argument::ArgumentVecCompiler, value::Value};
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::value::{ValueDefinition, ValueType}; use crate::lang::value::{ValueDefinition, ValueType};
use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use std::thread::ThreadId; use std::thread::ThreadId;
@ -18,24 +17,6 @@ pub struct CommandInvocation {
arguments: Vec<ArgumentDefinition>, arguments: Vec<ArgumentDefinition>,
} }
fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> {
if let Some(Value::List(path)) = env.get("cmd_path")? {
let path_vec = path.dump();
for val in path_vec {
match val {
Value::File(el) => {
let full = el.join(name);
if full.exists() {
return Ok(Some(full));
}
}
_ => {}
}
}
}
Ok(None)
}
fn arg_can_block(local_arguments: &Vec<ArgumentDefinition>, context: &mut CompileContext) -> bool { fn arg_can_block(local_arguments: &Vec<ArgumentDefinition>, context: &mut CompileContext) -> bool {
for arg in local_arguments { for arg in local_arguments {
if arg.value.can_block(context) { if arg.value.can_block(context) {
@ -107,22 +88,12 @@ pub fn eval_non_blocking(command: &ValueDefinition, arguments: &Vec<ArgumentDefi
Ok((this, value)) => { Ok((this, value)) => {
eval_internal(this, value, arguments.clone(), context, command.location()) eval_internal(this, value, arguments.clone(), context, command.location())
} }
Err(err) => { Err(err) =>
let (cmd, sub) = match command { if let ValueDefinition::Label(str) = command {
ValueDefinition::Label(str) => (str, None), try_external_command(str, arguments.clone(), context)
ValueDefinition::GetAttr(parent, sub) => match parent.deref() { } else {
ValueDefinition::Label(str) => if context.scope.get(str.string.as_str())?.is_none() { return Err(err);
(str, Some(sub)) }
} else {
return Err(err);
},
_ => return Err(err),
},
_ => return Err(err),
};
try_external_command(cmd, sub, arguments.clone(), context)
}
} }
} }
@ -275,13 +246,29 @@ fn eval_command(
} }
} }
fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> {
if let Some(Value::List(path)) = env.get("cmd_path")? {
let path_vec = path.dump();
for val in path_vec {
match val {
Value::File(el) => {
let full = el.join(name);
if full.exists() {
return Ok(Some(full));
}
}
_ => {}
}
}
}
Ok(None)
}
fn try_external_command( fn try_external_command(
cmd: &TrackedString, cmd: &TrackedString,
sub: Option<&TrackedString>,
mut arguments: Vec<ArgumentDefinition>, mut arguments: Vec<ArgumentDefinition>,
context: JobContext, context: JobContext,
) -> CrushResult<Option<ThreadId>> { ) -> CrushResult<Option<ThreadId>> {
match resolve_external_command(&cmd.string, &context.scope)? { match resolve_external_command(&cmd.string, &context.scope)? {
None => error(format!("Unknown command name {}", cmd).as_str()), None => error(format!("Unknown command name {}", cmd).as_str()),
Some(path) => { Some(path) => {
@ -289,16 +276,6 @@ fn try_external_command(
0, 0,
ArgumentDefinition::unnamed(ValueDefinition::Value(Value::File(path), cmd.location)), ArgumentDefinition::unnamed(ValueDefinition::Value(Value::File(path), cmd.location)),
); );
if let Some(subcmd) = sub {
arguments.insert(
1,
ArgumentDefinition::unnamed(
ValueDefinition::Value(
Value::string(subcmd.string.clone()),
subcmd.location,
)),
);
}
let call = CommandInvocation { let call = CommandInvocation {
command: ValueDefinition::Value( command: ValueDefinition::Value(
Value::Command( Value::Command(

View File

@ -281,6 +281,9 @@ fn complete_partial_argument(
LastArgument::Label(label) => { LastArgument::Label(label) => {
complete_label(Value::Scope(scope.clone()), &label, &argument_type, cursor, res)?; complete_label(Value::Scope(scope.clone()), &label, &argument_type, cursor, res)?;
}
LastArgument::Field(label) => {
if parse_result.last_argument_name.is_none() { if parse_result.last_argument_name.is_none() {
if let CompletionCommand::Known(cmd) = parse_result.command { if let CompletionCommand::Known(cmd) = parse_result.command {
complete_argument_name(cmd.arguments(), &label, cursor, res, false)?; complete_argument_name(cmd.arguments(), &label, cursor, res, false)?;
@ -288,7 +291,7 @@ fn complete_partial_argument(
} }
} }
LastArgument::Field(parent, field) => { LastArgument::Member(parent, field) => {
complete_label(parent, &field, &argument_type, cursor, res)?; complete_label(parent, &field, &argument_type, cursor, res)?;
} }
@ -320,6 +323,10 @@ pub fn complete(
complete_label(Value::Scope(scope.clone()), &label, &ValueType::Any, cursor, &mut res)?; complete_label(Value::Scope(scope.clone()), &label, &ValueType::Any, cursor, &mut res)?;
} }
ParseResult::PartialField(label) => {
complete_label(Value::Scope(scope.clone()), &label, &ValueType::Any, cursor, &mut res)?;
}
ParseResult::PartialMember(parent, label) => { ParseResult::PartialMember(parent, label) => {
complete_label(parent, &label, &ValueType::Any, cursor, &mut res)?; complete_label(parent, &label, &ValueType::Any, cursor, &mut res)?;
} }
@ -642,8 +649,8 @@ mod tests {
#[test] #[test]
fn complete_namespaced_argument() { fn complete_namespaced_argument() {
let line = "xxx abcd:bc"; let line = "xxx $abcd:bc";
let cursor = 11; let cursor = line.len();
let s = Scope::create_root(); let s = Scope::create_root();
s.create_namespace("abcd", "bla", Box::new(|env| { s.create_namespace("abcd", "bla", Box::new(|env| {
@ -653,19 +660,19 @@ mod tests {
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "xxx abcd:bcde "); assert_eq!(&completions[0].complete(line), "xxx $abcd:bcde ");
} }
#[test] #[test]
fn complete_simple_argument() { fn complete_simple_argument() {
let line = "abcd ab"; let line = "abcd $ab";
let cursor = 7; let cursor = line.len();
let s = Scope::create_root(); let s = Scope::create_root();
s.declare("abcd", Value::Empty()).unwrap(); s.declare("abcd", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "abcd abcd "); assert_eq!(&completions[0].complete(line), "abcd $abcd ");
} }
#[test] #[test]
@ -682,26 +689,26 @@ mod tests {
#[test] #[test]
fn check_multiple_token() { fn check_multiple_token() {
let line = "ab cd ef"; let line = "ab $cd ef";
let cursor = 5; let cursor = 6;
let s = Scope::create_root(); let s = Scope::create_root();
s.declare("cdef", Value::Empty()).unwrap(); s.declare("cdef", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "ab cdef ef"); assert_eq!(&completions[0].complete(line), "ab $cdef ef");
} }
#[test] #[test]
fn check_named_argument() { fn check_named_argument() {
let line = "ab foo=cd"; let line = "ab foo=$cd";
let cursor = 9; let cursor = 10;
let s = Scope::create_root(); let s = Scope::create_root();
s.declare("cdef", Value::Empty()).unwrap(); s.declare("cdef", Value::Empty()).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "ab foo=cdef "); assert_eq!(&completions[0].complete(line), "ab foo=$cdef ");
} }
#[test] #[test]
@ -718,7 +725,7 @@ mod tests {
#[test] #[test]
fn check_completion_type_filtering() { fn check_completion_type_filtering() {
let line = "my_cmd super_fancy_argument=t"; let line = "my_cmd super_fancy_argument=$t";
let cursor = line.len(); let cursor = line.len();
let s = scope_with_function(); let s = scope_with_function();
@ -726,7 +733,7 @@ mod tests {
s.declare("type", Value::Type(ValueType::Empty)).unwrap(); s.declare("type", Value::Type(ValueType::Empty)).unwrap();
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap(); let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
assert_eq!(completions.len(), 1); assert_eq!(completions.len(), 1);
assert_eq!(&completions[0].complete(line), "my_cmd super_fancy_argument=type "); assert_eq!(&completions[0].complete(line), "my_cmd super_fancy_argument=$type ");
} }
#[test] #[test]

View File

@ -9,6 +9,7 @@ use crate::util::glob::Glob;
use crate::lang::parser::Parser; use crate::lang::parser::Parser;
use crate::util::escape::unescape; use crate::util::escape::unescape;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::{Display, Formatter};
pub enum CompletionCommand { pub enum CompletionCommand {
Unknown, Unknown,
@ -28,7 +29,8 @@ impl Clone for CompletionCommand {
pub enum LastArgument { pub enum LastArgument {
Unknown, Unknown,
Label(String), Label(String),
Field(Value, String), Field(String),
Member(Value, String),
File(String, bool), File(String, bool),
QuotedString(String), QuotedString(String),
Switch(String), Switch(String),
@ -68,9 +70,9 @@ impl PartialCommandResult {
if false && cmd.arguments().len() == 1 { if false && cmd.arguments().len() == 1 {
Some(&cmd.arguments()[0]) Some(&cmd.arguments()[0])
} else { } else {
let mut previous_named = HashSet::new(); let mut previous_named = HashSet::new();
let mut previous_unnamed = 0usize; let mut previous_unnamed = 0usize;
for arg in &self.previous_arguments { for arg in &self.previous_arguments {
match &arg.name { match &arg.name {
Some(name) => { Some(name) => {
@ -83,9 +85,9 @@ impl PartialCommandResult {
let mut unnamed_used = 0usize; let mut unnamed_used = 0usize;
for arg in cmd.arguments() { for arg in cmd.arguments() {
if arg.unnamed { if arg.unnamed {
return Some(arg) return Some(arg);
} }
if previous_named.contains(&arg.name ) { if previous_named.contains(&arg.name) {
continue; continue;
} else { } else {
unnamed_used += 1; unnamed_used += 1;
@ -115,12 +117,44 @@ impl PartialCommandResult {
pub enum ParseResult { pub enum ParseResult {
Nothing, Nothing,
PartialLabel(String), PartialLabel(String),
PartialField(String),
PartialMember(Value, String), PartialMember(Value, String),
PartialFile(String, bool), PartialFile(String, bool),
PartialQuotedString(String), PartialQuotedString(String),
PartialArgument(PartialCommandResult), PartialArgument(PartialCommandResult),
} }
impl Display for ParseResult {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ParseResult::Nothing => f.write_str("nothing"),
ParseResult::PartialLabel(l) => {
f.write_str("label ")?;
f.write_str(l)
}
ParseResult::PartialField(l) => {
f.write_str("field ")?;
f.write_str(l)
}
ParseResult::PartialMember(_p, m) => {
f.write_str("member ")?;
f.write_str(m)
}
ParseResult::PartialFile(p, _q) => {
f.write_str("file ")?;
f.write_str(p)
}
ParseResult::PartialQuotedString(s) => {
f.write_str("string ")?;
f.write_str(s)
}
ParseResult::PartialArgument(_a) => {
f.write_str("command")
}
}
}
}
fn simple_path(node: &Node, cursor: usize) -> CrushResult<String> { fn simple_path(node: &Node, cursor: usize) -> CrushResult<String> {
match node { match node {
Node::Label(label) => Ok(label.string.clone()), Node::Label(label) => Ok(label.string.clone()),
@ -192,12 +226,19 @@ fn find_command_in_job_list(ast: JobListNode, cursor: usize) -> CrushResult<Comm
.map(|c| c.clone())), "Nothing to complete") .map(|c| c.clone())), "Nothing to complete")
} }
fn fetch_value(node: &Node, scope: &Scope) -> CrushResult<Option<Value>> { fn fetch_value(node: &Node, scope: &Scope, is_command: bool) -> CrushResult<Option<Value>> {
match node { match node {
Node::Label(l) => scope.get(&l.string), Node::Label(l) => scope.get(&l.string),
Node::Field(l) =>
if is_command {
scope.get(&l.string)
} else {
Ok(None)
},
Node::GetAttr(n, l) => Node::GetAttr(n, l) =>
match fetch_value(n, scope)? { match fetch_value(n, scope, is_command)? {
Some(parent) => parent.field(&l.string), Some(parent) => parent.field(&l.string),
None => Ok(None), None => Ok(None),
}, },
@ -225,7 +266,7 @@ fn fetch_value(node: &Node, scope: &Scope) -> CrushResult<Option<Value>> {
} }
fn parse_command_node(node: &Node, scope: &Scope) -> CrushResult<CompletionCommand> { fn parse_command_node(node: &Node, scope: &Scope) -> CrushResult<CompletionCommand> {
match fetch_value(node, scope)? { match fetch_value(node, scope, true)? {
Some(Value::Command(command)) => Ok(CompletionCommand::Known(command)), Some(Value::Command(command)) => Ok(CompletionCommand::Known(command)),
_ => Ok(CompletionCommand::Unknown), _ => Ok(CompletionCommand::Unknown),
} }
@ -235,18 +276,18 @@ fn parse_previous_argument(arg: &Node) -> PreviousArgument {
match arg { match arg {
Node::Assignment(key, op, value) => { Node::Assignment(key, op, value) => {
match (key.as_ref(), op.as_str()) { match (key.as_ref(), op.as_str()) {
(Node::Label(name), "=") => { (Node::Field(name), "=") => {
let inner = parse_previous_argument(value.as_ref()); let inner = parse_previous_argument(value.as_ref());
return PreviousArgument { return PreviousArgument {
name: Some(name.string.clone()), name: Some(name.string.clone()),
value: inner.value value: inner.value,
} };
} }
_ => {} _ => {}
} }
} }
_ => {}, _ => {}
} }
PreviousArgument { PreviousArgument {
name: None, name: None,
@ -278,9 +319,13 @@ pub fn parse(
Ok(ParseResult::PartialLabel( Ok(ParseResult::PartialLabel(
label.prefix(cursor).string)), label.prefix(cursor).string)),
Node::Field(label) =>
Ok(ParseResult::PartialField(
label.prefix(cursor).string)),
Node::GetAttr(parent, field) => Node::GetAttr(parent, field) =>
Ok(ParseResult::PartialMember( Ok(ParseResult::PartialMember(
mandate(fetch_value(parent, scope)?, "Unknown value")?, mandate(fetch_value(parent, scope, true)?, "Unknown value")?,
field.prefix(cursor).string)), field.prefix(cursor).string)),
Node::Path(_, _) => Node::Path(_, _) =>
@ -296,7 +341,7 @@ pub fn parse(
Node::GetItem(_, _) => { panic!("AAA"); } Node::GetItem(_, _) => { panic!("AAA"); }
_ => error("Can't extract command to complete"), _ => error(format!("Can't extract command to complete. Unknown node type {}", cmd.type_name())),
} }
} else { } else {
Ok(ParseResult::PartialArgument( Ok(ParseResult::PartialArgument(
@ -358,13 +403,23 @@ pub fn parse(
} }
)), )),
Node::Field(l) =>
Ok(ParseResult::PartialArgument(
PartialCommandResult {
command: c,
previous_arguments,
last_argument: LastArgument::Field(l.string.clone()),
last_argument_name,
}
)),
Node::GetAttr(parent, field) => Node::GetAttr(parent, field) =>
Ok(ParseResult::PartialArgument( Ok(ParseResult::PartialArgument(
PartialCommandResult { PartialCommandResult {
command: c, command: c,
previous_arguments, previous_arguments,
last_argument: LastArgument::Field( last_argument: LastArgument::Member(
mandate(fetch_value(parent, scope)?, "unknown value")?, mandate(fetch_value(parent, scope, false)?, "unknown value")?,
field.prefix(cursor).string), field.prefix(cursor).string),
last_argument_name, last_argument_name,
})), })),
@ -451,8 +506,8 @@ mod tests {
#[test] #[test]
fn find_command_in_operator() { fn find_command_in_operator() {
let ast = ast("ps | where {^cpu == (max_)}").unwrap(); let ast = ast("ps | where {cpu == (max_)}").unwrap();
let cmd = find_command_in_job_list(ast, 25).unwrap(); let cmd = find_command_in_job_list(ast, 24).unwrap();
assert_eq!(cmd.location, Location::new(21, 25)) assert_eq!(cmd.location, Location::new(20, 24))
} }
} }

View File

@ -57,7 +57,7 @@ impl RustylineHelper {
QuotedString => highlight.get(&Value::string("string_literal")), QuotedString => highlight.get(&Value::string("string_literal")),
Regex => highlight.get(&Value::string("string_literal")), Regex => highlight.get(&Value::string("string_literal")),
QuotedFile => highlight.get(&Value::string("file_literal")), QuotedFile => highlight.get(&Value::string("file_literal")),
LabelOrWildcard => highlight.get(&Value::string("label")), StringOrWildcard => highlight.get(&Value::string("label")),
Integer => highlight.get(&Value::string("numeric_literal")), Integer => highlight.get(&Value::string("numeric_literal")),
Float => highlight.get(&Value::string("numeric_literal")), Float => highlight.get(&Value::string("numeric_literal")),
Field => highlight.get(&Value::string("field")), Field => highlight.get(&Value::string("field")),

View File

@ -102,15 +102,15 @@ ParameterList: Vec<ParameterNode> = {
} }
Parameter: ParameterNode = { Parameter: ParameterNode = {
<start: @L> <l: LabelOrWildcard> <end: @R> <d: Default> => <start: @L> <l: Label> <end: @R> <d: Default> =>
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), None, d), ParameterNode::Parameter(TrackedString::from(&l[1..], Location::new(start, end)), None, d),
<start: @L> <l: LabelOrWildcard> <end: @R> Colon <t: Item> <d: Default> => <start: @L> <l: Label> <end: @R> Colon <t: Item> <d: Default> =>
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), Some(t), d), ParameterNode::Parameter(TrackedString::from(&l[1..], Location::new(start, end)), Some(t), d),
"@" <start: @L> <l: LabelOrWildcard> <end: @R> => ParameterNode::Unnamed(TrackedString::from(l, Location::new(start, end))), "@" <start: @L> <l: Label> <end: @R> => ParameterNode::Unnamed(TrackedString::from(&l[1..], Location::new(start, end))),
"@@" <start: @L> <l: LabelOrWildcard> <end: @R> => ParameterNode::Named(TrackedString::from(l, Location::new(start, end))), "@@" <start: @L> <l: Label> <end: @R> => ParameterNode::Named(TrackedString::from(&l[1..], Location::new(start, end))),
} }
Default: Option<Node> = { Default: Option<Node> = {
@ -119,9 +119,11 @@ Default: Option<Node> = {
} }
Item: Box<Node> = { Item: Box<Node> = {
<start: @L> <l: LabelOrWildcard> <end: @R> => <start: @L> <l: StringOrWildcard> <end: @R> =>
Node::parse_label_or_wildcard(&TrackedString::from(l, Location::new(start, end))), Node::parse_string_or_wildcard(&TrackedString::from(l, Location::new(start, end))),
<start: @L> <l: Label> <end: @R> =>
Node::parse_label(&TrackedString::from(l, Location::new(start, end))),
<start: @L> <l: Regex> <end: @R> => <start: @L> <l: Regex> <end: @R> =>
Box::from(Node::Regex(TrackedString::from(&l[3..l.len()-1], Location::new(start, end)))), Box::from(Node::Regex(TrackedString::from(&l[3..l.len()-1], Location::new(start, end)))),
@ -145,12 +147,12 @@ Item: Box<Node> = {
<start: @L> <l:Flag> <end: @R> => <start: @L> <l:Flag> <end: @R> =>
Box::from( Box::from(
Node::Assignment(Box::from(Node::Label(TrackedString::from(&l[2..], Location::new(start+2, end)))), Node::Assignment(Box::from(Node::Field(TrackedString::from(&l[2..], Location::new(start+2, end)))),
"=".to_string(), "=".to_string(),
Box::from(Node::Label(TrackedString::from("true", Location::new(start, start+2)))))), Box::from(Node::Label(TrackedString::from("true", Location::new(start, start+2)))))),
<i: Item> "[" <e: Assignment> "]" => Box::from(Node::GetItem(i, e)), <i: Item> "[" <e: Assignment> "]" => Box::from(Node::GetItem(i, e)),
<i: Item> Colon <start: @L> <l: LabelOrWildcard> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(&l, Location::new(start, end)))), <i: Item> Colon <start: @L> <l: StringOrWildcard> <end: @R> => Box::from(Node::GetAttr(i, TrackedString::from(&l, Location::new(start, end)))),
"{" Separator? <s: Signature> <l: JobListWithoutSeparator> "}" => Box::from(Node::Closure(s, l)), "{" Separator? <s: Signature> <l: JobListWithoutSeparator> "}" => Box::from(Node::Closure(s, l)),
"(" <j:Job> ")" => Box::from(Node::Substitution(j)), "(" <j:Job> ")" => Box::from(Node::Substitution(j)),
} }
@ -169,7 +171,8 @@ Token: TokenNode = {
@L FactorOperator @R => TokenNode::new(TokenType::FactorOperator, <>), @L FactorOperator @R => TokenNode::new(TokenType::FactorOperator, <>),
@L TermOperator @R => TokenNode::new(TokenType::TermOperator, <>), @L TermOperator @R => TokenNode::new(TokenType::TermOperator, <>),
@L QuotedString @R => TokenNode::new(TokenType::QuotedString, <>), @L QuotedString @R => TokenNode::new(TokenType::QuotedString, <>),
@L LabelOrWildcard @R => TokenNode::new(TokenType::LabelOrWildcard, <>), @L Label @R => TokenNode::new(TokenType::Label, <>),
@L StringOrWildcard @R => TokenNode::new(TokenType::StringOrWildcard, <>),
@L QuotedFile @R => TokenNode::new(TokenType::QuotedFile, <>), @L QuotedFile @R => TokenNode::new(TokenType::QuotedFile, <>),
@L FileOrWildcard @R => TokenNode::new(TokenType::FileOrWildcard, <>), @L FileOrWildcard @R => TokenNode::new(TokenType::FileOrWildcard, <>),
@L Flag @R => TokenNode::new(TokenType::Flag, <>), @L Flag @R => TokenNode::new(TokenType::Flag, <>),
@ -202,7 +205,8 @@ match {
r"(\*|//)" => FactorOperator, r"(\*|//)" => FactorOperator,
r"(\+|-)" => TermOperator, r"(\+|-)" => TermOperator,
r#""([^\\"]|\\.)*""# => QuotedString, r#""([^\\"]|\\.)*""# => QuotedString,
r"[_a-zA-Z%\?][\._0-9a-zA-Z%\?/]*" => LabelOrWildcard, r"[_a-zA-Z%\?][\._0-9a-zA-Z%\?/]*" => StringOrWildcard,
r"\$[_0-9a-zA-Z][_0-9a-zA-Z]*" => Label,
r"(\.[\./_0-9a-zA-Z%\?]*|/([\._0-9a-zA-Z%\?][\./_0-9a-zA-Z%\?]*)?)" => FileOrWildcard, r"(\.[\./_0-9a-zA-Z%\?]*|/([\._0-9a-zA-Z%\?][\./_0-9a-zA-Z%\?]*)?)" => FileOrWildcard,
r"--[_0-9a-zA-Z]+" => Flag, r"--[_0-9a-zA-Z]+" => Flag,
r"\^[\._a-zA-Z][\._a-zA-Z0-9]*" => Field, r"\^[\._a-zA-Z][\._a-zA-Z0-9]*" => Field,

View File

@ -6,7 +6,7 @@ use crate::lang::data::binary::BinaryReader;
use crate::lang::errors::to_crush_error; use crate::lang::errors::to_crush_error;
use crate::lang::data::list::ListReader; use crate::lang::data::list::ListReader;
use crate::lang::printer::Printer; use crate::lang::printer::Printer;
use crate::lang::pipe::{CrushStream, InputStream, ValueSender, pipe, printer_pipe}; use crate::lang::pipe::{CrushStream, InputStream, ValueSender, printer_pipe};
use crate::lang::data::table::ColumnType; use crate::lang::data::table::ColumnType;
use crate::lang::data::table::Row; use crate::lang::data::table::Row;
use crate::lang::data::table::Table; use crate::lang::data::table::Table;

View File

@ -38,7 +38,7 @@ use crate::util::regex::RegexFileMatcher;
use ordered_map::OrderedMap; use ordered_map::OrderedMap;
pub use value_definition::ValueDefinition; pub use value_definition::ValueDefinition;
pub use value_type::ValueType; pub use value_type::ValueType;
use std::fmt::{Display, Formatter, Debug}; use std::fmt::{Display, Formatter};
use num_format::Grouping; use num_format::Grouping;
use crate::util::escape::escape; use crate::util::escape::escape;
@ -75,10 +75,7 @@ impl Display for Value {
Value::String(val) => std::fmt::Display::fmt(val, f), Value::String(val) => std::fmt::Display::fmt(val, f),
Value::Integer(val) => std::fmt::Display::fmt(val, f), Value::Integer(val) => std::fmt::Display::fmt(val, f),
Value::Time(val) => f.write_str(&val.format("%Y-%m-%d %H:%M:%S %z").to_string()), Value::Time(val) => f.write_str(&val.format("%Y-%m-%d %H:%M:%S %z").to_string()),
Value::Field(val) => { Value::Field(val) => f.write_str(&val.join(":")),
f.write_str("^")?;
f.write_str(&val.join(":"))
}
Value::Glob(val) => std::fmt::Display::fmt(val, f), Value::Glob(val) => std::fmt::Display::fmt(val, f),
Value::Regex(val, _) => { Value::Regex(val, _) => {
f.write_str("re\"")?; f.write_str("re\"")?;

View File

@ -1,7 +1,7 @@
use crate::lang::command::Parameter; use crate::lang::command::Parameter;
use crate::lang::errors::{block_error, mandate}; use crate::lang::errors::mandate;
use crate::lang::execution_context::CompileContext; use crate::lang::execution_context::CompileContext;
use crate::lang::{argument::ArgumentDefinition, command::CrushCommand, job::Job}; use crate::lang::{command::CrushCommand, job::Job};
use crate::{ use crate::{
lang::errors::CrushResult, lang::pipe::pipe, lang::pipe::empty_channel, lang::errors::CrushResult, lang::pipe::pipe, lang::pipe::empty_channel,
lang::value::Value, lang::value::Value,

View File

@ -87,7 +87,7 @@ fn echo(context: CommandContext) -> CrushResult<()> {
member, member,
can_block = false, can_block = false,
short = "Extracts one member from the input struct.", short = "Extracts one member from the input struct.",
example = "http \"example.com\" | member ^body | json:from" example = "http \"example.com\" | member body | json:from"
)] )]
struct Member { struct Member {
#[description("the member to extract.")] #[description("the member to extract.")]

View File

@ -1,10 +1,8 @@
use lazy_static::lazy_static;
use crate::lang::command::OutputType::Known; use crate::lang::command::OutputType::Known;
use crate::lang::errors::{to_crush_error, CrushResult}; use crate::lang::errors::{to_crush_error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::scope::Scope; use crate::lang::data::scope::Scope;
use crate::lang::data::table::ColumnType; use crate::{lang::value::Value, lang::value::ValueType};
use crate::{data::table::Row, lang::value::Value, lang::value::ValueType};
use nix::sys::signal; use nix::sys::signal;
use nix::unistd::Pid; use nix::unistd::Pid;
use signature::signature; use signature::signature;
@ -23,7 +21,7 @@ mod macos {
use signature::signature; use signature::signature;
lazy_static! { lazy_static! {
static ref PS_OUTPUT_TYPE: Vec<ColumnType> = vec![ static ref LIST_OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("pid", ValueType::Integer), ColumnType::new("pid", ValueType::Integer),
ColumnType::new("ppid", ValueType::Integer), ColumnType::new("ppid", ValueType::Integer),
ColumnType::new("user", ValueType::String), ColumnType::new("user", ValueType::String),
@ -58,12 +56,12 @@ mod macos {
} }
#[signature( #[signature(
ps, list,
can_block = true, can_block = true,
short = "Return a table stream containing information on all running processes on the system", short = "Return a table stream containing information on all running processes on the system",
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())), output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
long = "ps accepts no arguments.")] long = "proc:list accepts no arguments.")]
pub struct Ps {} pub struct List {}
use libproc::libproc::bsd_info::BSDInfo; use libproc::libproc::bsd_info::BSDInfo;
use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType}; use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType};
@ -124,11 +122,11 @@ mod macos {
TaskAllInfo { pbsd, ptinfo } TaskAllInfo { pbsd, ptinfo }
} }
fn ps(context: CommandContext) -> CrushResult<()> { fn list(context: CommandContext) -> CrushResult<()> {
let mut base_procs = Vec::new(); let mut base_procs = Vec::new();
let arg_max = 2048;//get_arg_max(); let arg_max = 2048;//get_arg_max();
let output = context.output.initialize(PS_OUTPUT_TYPE.clone())?; let output = context.output.initialize(LIST_OUTPUT_TYPE.clone())?;
let users = create_user_map()?; let users = create_user_map()?;
let mut info: mach_timebase_info = mach_timebase_info{numer: 0, denom: 0}; let mut info: mach_timebase_info = mach_timebase_info{numer: 0, denom: 0};
@ -243,7 +241,7 @@ mod linux {
use std::collections::HashMap; use std::collections::HashMap;
lazy_static! { lazy_static! {
static ref PS_OUTPUT_TYPE: Vec<ColumnType> = vec![ static ref LIST_OUTPUT_TYPE: Vec<ColumnType> = vec![
ColumnType::new("pid", ValueType::Integer), ColumnType::new("pid", ValueType::Integer),
ColumnType::new("ppid", ValueType::Integer), ColumnType::new("ppid", ValueType::Integer),
ColumnType::new("status", ValueType::String), ColumnType::new("status", ValueType::String),
@ -276,16 +274,16 @@ mod linux {
#[signature( #[signature(
ps, list,
can_block = true, can_block = true,
short = "Return a table stream containing information on all running processes on the system", short = "Return a table stream containing information on all running processes on the system",
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())), output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
long = "ps accepts no arguments.")] long = "proc:list accepts no arguments.")]
pub struct Ps {} pub struct List {}
fn ps(context: CommandContext) -> CrushResult<()> { fn list(context: CommandContext) -> CrushResult<()> {
Ps::parse(context.arguments.clone(), &context.global_state.printer())?; List::parse(context.arguments.clone(), &context.global_state.printer())?;
let output = context.output.initialize(PS_OUTPUT_TYPE.clone())?; let output = context.output.initialize(LIST_OUTPUT_TYPE.clone())?;
let users = create_user_map()?; let users = create_user_map()?;
match psutil::process::processes() { match psutil::process::processes() {
@ -356,12 +354,11 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
"Process related commands", "Process related commands",
Box::new(move |env| { Box::new(move |env| {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
linux::Ps::declare(env)?; linux::List::declare(env)?;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
macos::Ps::declare(env)?; macos::List::declare(env)?;
Kill::declare(env)?; Kill::declare(env)?;
Ok(()) Ok(())
}))?; }))?;
root.r#use(&e);
Ok(()) Ok(())
} }

View File

@ -11,7 +11,7 @@ drop,
can_block = true, can_block = true,
short = "Drop all fields mentioned from input, copy remainder of input", short = "Drop all fields mentioned from input, copy remainder of input",
long = "This command is does the opposite of the select command.\n It copies all column except the ones specified from input to output.", long = "This command is does the opposite of the select command.\n It copies all column except the ones specified from input to output.",
example= "ps | drop ^vms ^rss # Drop memory usage columns from output of ps", example= "ps | drop vms rss # Drop memory usage columns from output of ps",
output = Unknown, output = Unknown,
)] )]
pub struct Drop { pub struct Drop {

View File

@ -1,7 +1,6 @@
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::errors::{error, CrushResult}; use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::pipe::{black_hole, empty_channel};
use crate::lang::{argument::Argument, data::table::ColumnType}; use crate::lang::{argument::Argument, data::table::ColumnType};
use crate::lang::{data::table::Row, value::Value}; use crate::lang::{data::table::Row, value::Value};
use signature::signature; use signature::signature;
@ -15,7 +14,7 @@ can_block = true,
output = Known(Empty), output = Known(Empty),
short = "Runs a command one for each row of input", short = "Runs a command one for each row of input",
long = "The columns of the row are exported to the environment using the column names.", long = "The columns of the row are exported to the environment using the column names.",
example = "ps | where {status != \"Sleeping\"} | each {echo (\"{} is sleepy\":format name)}")] example = "ps | where {status != \"Sleeping\"} | each {echo (\"{} is sleepy\":format $name)}")]
pub struct Each { pub struct Each {
#[description("the command to run.")] #[description("the command to run.")]
body: Command, body: Command,

View File

@ -6,9 +6,16 @@ use signature::signature;
#[signature(enumerate, short = "Prepend a column containing the row number to each row of the input.")] #[signature(enumerate, short = "Prepend a column containing the row number to each row of the input.")]
pub struct Enumerate { pub struct Enumerate {
#[description("the index to use for the first row.")]
#[default(0)]
start_index: i128,
#[description("the step between rows.")]
#[default(1)]
step: i128,
} }
fn enumerate(context: CommandContext) -> CrushResult<()> { fn enumerate(context: CommandContext) -> CrushResult<()> {
let cfg: Enumerate = Enumerate::parse(context.arguments, &context.global_state.printer())?;
match context.input.recv()?.stream() { match context.input.recv()?.stream() {
Some(mut input) => { Some(mut input) => {
let mut output_type = vec![ let mut output_type = vec![
@ -16,12 +23,12 @@ fn enumerate(context: CommandContext) -> CrushResult<()> {
output_type.extend(input.types().to_vec()); output_type.extend(input.types().to_vec());
let output = context.output.initialize(output_type)?; let output = context.output.initialize(output_type)?;
let mut line: i128 = 0; let mut line: i128 = cfg.start_index;
while let Ok(row) = input.read() { while let Ok(row) = input.read() {
let mut out = vec![Value::Integer(line)]; let mut out = vec![Value::Integer(line)];
out.extend(Vec::from(row)); out.extend(Vec::from(row));
output.send(Row::new(out))?; output.send(Row::new(out))?;
line += 1; line += cfg.step;
} }
Ok(()) Ok(())
} }

View File

@ -23,7 +23,7 @@ use crate::lang::global_state::GlobalState;
group, group,
can_block = true, can_block = true,
short = "Group stream by the specified column(s)", short = "Group stream by the specified column(s)",
example = "find . | group ^user ^type file_count={count} size={sum ^size}" example = "find . | group user type file_count={count} size={sum size}"
)] )]
pub struct Group { pub struct Group {
#[unnamed()] #[unnamed()]

View File

@ -45,11 +45,12 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
sum_avg::Avg::declare(env)?; sum_avg::Avg::declare(env)?;
sum_avg::Min::declare(env)?; sum_avg::Min::declare(env)?;
sum_avg::Max::declare(env)?; sum_avg::Max::declare(env)?;
sum_avg::Mul::declare(env)?;
env.declare_command( env.declare_command(
"select", select::select, true, "select", select::select, true,
"select copy_fields:field... [%] new_field=definition:command", "select copy_fields:field... [%] new_field=definition:command",
"Pass on some old fields and calculate new ones for each line of input", "Pass on some old fields and calculate new ones for each line of input",
example!(r#"ls | select ^user path={"{}/{}":format (pwd) file}"#), Unknown, example!(r#"ls | select user path={"{}/{}":format (pwd) file}"#), Unknown,
vec![], vec![],
)?; )?;
seq::Seq::declare(env)?; seq::Seq::declare(env)?;

View File

@ -1,7 +1,7 @@
use crate::lang::command::Command; use crate::lang::command::Command;
use crate::lang::errors::error; use crate::lang::errors::error;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::pipe::{pipe, empty_channel, Stream}; use crate::lang::pipe::{pipe, Stream};
use crate::lang::data::table::ColumnVec; use crate::lang::data::table::ColumnVec;
use crate::{ use crate::{
lang::errors::argument_error_legacy, lang::errors::argument_error_legacy,

View File

@ -1,3 +1,4 @@
use std::mem;
use crate::lang::errors::CrushResult; use crate::lang::errors::CrushResult;
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::data::table::ColumnType; use crate::lang::data::table::ColumnType;
@ -22,9 +23,7 @@ pub fn seq(context: CommandContext) -> CrushResult<()> {
.initialize(vec![ColumnType::new("value", ValueType::Integer)])?; .initialize(vec![ColumnType::new("value", ValueType::Integer)])?;
if (cfg.to > cfg.from) != (cfg.step > 0) { if (cfg.to > cfg.from) != (cfg.step > 0) {
let tmp = cfg.to; mem::swap(&mut cfg.to, &mut cfg.from);
cfg.to = cfg.from;
cfg.from = tmp;
} }
let mut idx = cfg.from; let mut idx = cfg.from;

View File

@ -11,7 +11,7 @@ use std::cmp::Ordering;
#[signature( #[signature(
sort, sort,
short = "Sort input based on column", short = "Sort input based on column",
example = "ps | sort ^cpu", example = "ps | sort cpu",
output = Passthrough)] output = Passthrough)]
pub struct Sort { pub struct Sort {
#[unnamed()] #[unnamed()]

View File

@ -42,7 +42,7 @@ sum_function!(sum_duration, Duration, Duration::seconds(0), Duration);
#[signature( #[signature(
sum, sum,
short = "Calculate the sum for the specific column across all rows.", short = "Calculate the sum for the specific column across all rows.",
example = "ps | sum ^cpu")] example = "proc:list | sum cpu")]
pub struct Sum { pub struct Sum {
field: Option<Field>, field: Option<Field>,
} }
@ -94,7 +94,7 @@ avg_function!(avg_duration, Duration, Duration::seconds(0), Duration, i32);
#[signature( #[signature(
avg, avg,
short = "Calculate the average for the specific column across all rows.", short = "Calculate the average for the specific column across all rows.",
example = "ps | avg ^cpu")] example = "proc:list | avg cpu")]
pub struct Avg { pub struct Avg {
field: Option<Field>, field: Option<Field>,
} }
@ -159,7 +159,7 @@ aggr_function!(max_time, Time, |a, b| std::cmp::max(a, b));
#[signature( #[signature(
min, min,
short = "Calculate the minimum for the specific column across all rows.", short = "Calculate the minimum for the specific column across all rows.",
example = "ps | min ^cpu")] example = "proc:list | min cpu")]
pub struct Min { pub struct Min {
field: Option<Field>, field: Option<Field>,
} }
@ -186,7 +186,7 @@ fn min(context: CommandContext) -> CrushResult<()> {
#[signature( #[signature(
max, max,
short = "Calculate the maximum for the specific column across all rows.", short = "Calculate the maximum for the specific column across all rows.",
example = "ps | max ^cpu")] example = "proc:list | max cpu")]
pub struct Max { pub struct Max {
field: Option<Field>, field: Option<Field>,
} }
@ -209,3 +209,46 @@ fn max(context: CommandContext) -> CrushResult<()> {
_ => error("Expected a stream"), _ => error("Expected a stream"),
} }
} }
macro_rules! mul_function {
($name:ident, $var_type:ident, $var_initializer:expr, $value_type:ident) => {
fn $name(mut s: Stream, column: usize) -> CrushResult<Value> {
let mut res: $var_type = $var_initializer;
while let Ok(row) = s.read() {
match row.cells()[column] {
Value::$value_type(i) => res = res * i,
_ => return error("Invalid cell value"),
}
}
Ok(Value::$value_type(res))
}
};
}
mul_function!(mul_int, i128, 1, Integer);
mul_function!(mul_float, f64, 1.0, Float);
#[signature(
mul,
short = "Calculate the product for the specific column across all rows.",
example = "seq 5 10 | mul")]
pub struct Mul {
field: Option<Field>,
}
fn mul(context: CommandContext) -> CrushResult<()> {
match context.input.recv()?.stream() {
Some(input) => {
let cfg: Sum = Sum::parse(context.arguments, &context.global_state.printer())?;
let column = parse(input.types(), cfg.field)?;
match &input.types()[column].cell_type {
ValueType::Integer => context.output.send(mul_int(input, column)?),
ValueType::Float => context.output.send(mul_float(input, column)?),
t => argument_error_legacy(
&format!("Can't calculate product of elements of type {}", t),
),
}
}
_ => error("Expected a stream"),
}
}

View File

@ -12,7 +12,7 @@ use crate::lang::value::Field;
uniq, uniq,
output = Passthrough, output = Passthrough,
short = "Only output the first row if multiple rows has the same value for the specified column", short = "Only output the first row if multiple rows has the same value for the specified column",
example = "ps | uniq ^user")] example = "ps | uniq user")]
pub struct Uniq { pub struct Uniq {
field: Option<Field>, field: Option<Field>,
} }

View File

@ -2,11 +2,11 @@ use crate::lang::command::Command;
use crate::lang::command::OutputType::Passthrough; use crate::lang::command::OutputType::Passthrough;
use crate::lang::errors::{error, CrushResult}; use crate::lang::errors::{error, CrushResult};
use crate::lang::execution_context::CommandContext; use crate::lang::execution_context::CommandContext;
use crate::lang::pipe::{black_hole, pipe, empty_channel};
use crate::lang::{argument::Argument, data::table::ColumnType}; use crate::lang::{argument::Argument, data::table::ColumnType};
use crate::lang::{data::table::Row, value::Value}; use crate::lang::{data::table::Row, value::Value};
use signature::signature; use signature::signature;
use crate::lang::ast::Location; use crate::lang::ast::Location;
use crate::lang::pipe::pipe;
#[signature( #[signature(
r#where, r#where,

View File

@ -1,3 +1,4 @@
use std::collections::VecDeque;
/** /**
A simple wrapper around std::fs::read_dir to allow for unit testing via fakes. A simple wrapper around std::fs::read_dir to allow for unit testing via fakes.
@ -9,10 +10,9 @@ you'll need something cleverer.
*/ */
use std::path::{PathBuf}; use std::path::{PathBuf};
use crate::lang::errors::{CrushResult, to_crush_error, mandate}; use crate::lang::errors::{CrushResult, mandate, to_crush_error};
use std::fs::{ReadDir, read_dir}; use std::fs::{ReadDir, read_dir};
use ordered_map::{OrderedMap, Entry}; use ordered_map::{Entry, OrderedMap};
use std::collections::VecDeque;
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Directory { pub struct Directory {

View File

@ -1,6 +1,7 @@
use crate::util::hex::from_hex; use crate::util::hex::from_hex;
use crate::lang::errors::{CrushResult, to_crush_error, mandate, data_error}; use crate::lang::errors::{to_crush_error, data_error};
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::CrushResult;
pub fn escape_without_quotes(s: &str) -> String { pub fn escape_without_quotes(s: &str) -> String {
let mut res = String::with_capacity(s.len()); let mut res = String::with_capacity(s.len());
@ -41,7 +42,6 @@ enum State {
pub fn unescape(s: &str) -> CrushResult<String> { pub fn unescape(s: &str) -> CrushResult<String> {
use State::*; use State::*;
use crate::lang::errors::CrushResult;
let mut res = "".to_string(); let mut res = "".to_string();
let mut state = Normal; let mut state = Normal;

View File

@ -1,7 +1,4 @@
touch ./foo touch ./foo
touch ./foo ./foo:chmod "a=" "o+xr" "u+w" "g-r"
touch ./foo find ./foo | select permissions
touch ./foo rm ./foo
foo:chmod "a=" "o+xr" "u+w" "g-r"
find ./foo | select ^permissions
#rm ./foo

View File

@ -1,22 +1,23 @@
Point := (class) $Point := (class)
Point:__init__ = { $Point:__init__ = {
|x:float y:float| |$x:$float $y:$float|
this:x = x this:x = $x
this:y = y this:y = $y
} }
Point:len = { $Point:len = {
|| ||
math:sqrt this:x*this:x + this:y*this:y math:sqrt $this:x*$this:x + $this:y*$this:y
} }
Point:__add__ = { $Point:__add__ = {
|@unnamed| |@ $unnamed|
other := unnamed[0] $other := $unnamed[0]
Point:new x=(this:x + other:x) y=(this:y + other:y) Point:new x=($this:x + $other:x) y=($this:y + $other:y)
} }
p1 := (Point:new x=0.0 y=4.0)
p2 := (Point:new x=3.0 y=0.0) $p1 := (Point:new x=0.0 y=4.0)
p3 := p1 + p2 $p2 := (Point:new x=3.0 y=0.0)
p3:len $p3 := $p1 + $p2
$p3:len

View File

@ -1,3 +1,3 @@
ggg := {|a : (dict integer integer)| echo a} $ggg := {|$a : (dict $integer $integer)| echo $a}
hhh := ((dict integer integer):new) $hhh := ((dict $integer $integer):new)
ggg a=hhh ggg a=$hhh

View File

@ -41,14 +41,14 @@
"Time comparisons" "Time comparisons"
t1 := (time:now) $t1 := (time:now)
t2 := (time:now + (duration:of seconds=1)) $t2 := (time:now + (duration:of seconds=1))
t1 > t2 $t1 > $t2
t1 < t2 $t1 < $t2
t1 >= t2 $t1 >= $t2
t1 <= t2 $t1 <= $t2
t1 != t2 $t1 != $t2
t1 == t2 $t1 == $t2
"String comparisons" "String comparisons"
"a" > "b" "a" > "b"

View File

@ -1,4 +1,4 @@
convert 1.0 integer convert 1.0 $integer
convert 0.5 integer convert 0.5 $integer
convert 5 string convert 5 $string
typeof (convert 5 string) typeof (convert 5 $string)

View File

@ -1 +1 @@
find example_data/tree | select ^file | sort ^file find ./example_data/tree | select file | sort file

View File

@ -1,33 +1,33 @@
for i=(list:of 1 2) { for i=(list:of 1 2) {
echo i echo $i
} }
d := ((dict string integer):new) $d := ((dict $string $integer):new)
d["fooo"] = 3 $d["fooo"] = 3
for d { for $d {
echo key value echo $key $value
} }
for i=d { for i=$d {
echo i:key i:value echo $i:key $i:value
} }
table := (seq 3 | materialize) $table := (seq 3 | materialize)
for table { for $table {
echo value echo $value
} }
for i=table { for i=$table {
echo i echo $i
} }
for (seq 3) { for (seq 3) {
echo value echo $value
} }
for i=(seq 3) { for i=(seq 3) {
echo i echo $i
} }

View File

@ -1,3 +1,3 @@
seq 1000 | select num={(value*5):mod 7} | group ^num | sort ^num seq 1000 | select num={(value*5):mod 7} | group num | sort num
seq 1000 | select num={(value*5):mod 7} | group ^num c={count} | sort ^num seq 1000 | select num={(value*5):mod 7} | group num c={count} | sort num
seq 1000 | select num={(value*5):mod 7} | group ^num c={count} s={sum} | sort ^num seq 1000 | select num={(value*5):mod 7} | group num c={count} s={sum} | sort num

View File

@ -1,4 +1,4 @@
home:=(csv:from example_data/home.csv name=string country=string) $home:=(csv:from ./example_data/home.csv name=$string country=$string)
age:=(csv:from example_data/age.csv name=string age=integer) $age:=(csv:from ./example_data/age.csv name=$string age=$integer)
join name=home name=age | sort ^name join name=$home name=$age | sort name

View File

@ -1,8 +1,8 @@
json:from example_data/din%.json | json:from example_data/din%.json |
sort ^name sort name
json:from example_data/din%.json | json:from example_data/din%.json |
where {name =~ re"Tri.*"} where {$name =~ re"Tri.*"}
# Check serialisation and deserialisation, including field order # Check serialisation and deserialisation, including field order
seq 5|select ^value half={0.5 * value} str={convert value string} struct={data value=value} | json:to |json:from seq 5|select value half={0.5 * $value} str={convert $value $string} struct={data value=$value} | json:to |json:from

View File

@ -4,10 +4,10 @@ loop {
echo "NO" echo "NO"
} }
a := false $a := $false
loop { loop {
if a break if $a $break
a = true $a = $true
echo 2 echo 2
continue continue
echo "NO" echo "NO"

View File

@ -2,20 +2,20 @@
# of multiple commands into the same pipe. # of multiple commands into the same pipe.
# Create the pipe # Create the pipe
pipe := ((table_input_stream value=integer):pipe) $pipe := ((table_input_stream value=$integer):pipe)
# Attach the input end to a bunch of seq commands # Attach the input end to a bunch of seq commands
_1 := (seq 100_000 | pipe:output:write | bg) $_1 := (seq 100_000 | pipe:output:write | bg)
_2 := (seq 100_000 | pipe:output:write | bg) $_2 := (seq 100_000 | pipe:output:write | bg)
_3 := (seq 100_000 | pipe:output:write | bg) $_3 := (seq 100_000 | pipe:output:write | bg)
_4 := (seq 100_000 | pipe:output:write | bg) $_4 := (seq 100_000 | pipe:output:write | bg)
# Attach the output end to the sum command and # Attach the output end to the sum command and
sum_job_id := (pipe:input | sum | bg) $sum_job_id := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually # Close the output and input, so that the input can actually
# reach EOF once the above 4 invocation exit # reach EOF once the above 4 invocation exit
pipe:close pipe:close
# Wait for the sum command to finish # Wait for the sum command to finish
sum_job_id | fg $sum_job_id | fg

View File

@ -2,19 +2,19 @@
# of a command between multiple commands via the same pipe. # of a command between multiple commands via the same pipe.
# Create the pipe # Create the pipe
pipe := ((table_input_stream value=integer):pipe) $pipe := ((table_input_stream value=$integer):pipe)
# Attach the input end to a bunch of seq commands # Attach the input end to a bunch of seq commands
input := (seq 10_000 | pipe:output:write | bg) $input := (seq 10_000 | pipe:output:write | bg)
# Attach the output end to the sum command and # Attach the output end to the sum command and
sum_job_id1 := (pipe:input | sum | bg) $sum_job_id1 := (pipe:input | sum | bg)
sum_job_id2 := (pipe:input | sum | bg) $sum_job_id2 := (pipe:input | sum | bg)
sum_job_id3 := (pipe:input | sum | bg) $sum_job_id3 := (pipe:input | sum | bg)
sum_job_id4 := (pipe:input | sum | bg) $sum_job_id4 := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually # Close the output and input, so that the input can actually
# reach EOF once the above 4 invocation exit # reach EOF once the above 4 invocation exit
pipe:close pipe:close
# Wait for the sum command to finish # Wait for the sum command to finish
val (sum_job_id1 | fg) + (sum_job_id2 | fg) + (sum_job_id3 | fg) + (sum_job_id4 | fg) val ($sum_job_id1 | fg) + ($sum_job_id2 | fg) + ($sum_job_id3 | fg) + ($sum_job_id4 | fg)

View File

@ -2,23 +2,23 @@
# can use a single pipe both for input and output. # can use a single pipe both for input and output.
# Create the pipe # Create the pipe
pipe := ((table_input_stream value=integer):pipe) $pipe := ((table_input_stream value=$integer):pipe)
# Attach the input end to a bunch of seq commands # Attach the input end to a bunch of seq commands
input1 := (seq 100_000 | pipe:output:write | bg) $input1 := (seq 100_000 | pipe:output:write | bg)
input2 := (seq 100_000 | pipe:output:write | bg) $input2 := (seq 100_000 | pipe:output:write | bg)
input3 := (seq 100_000 | pipe:output:write | bg) $input3 := (seq 100_000 | pipe:output:write | bg)
input4 := (seq 100_000 | pipe:output:write | bg) $input4 := (seq 100_000 | pipe:output:write | bg)
# Attach the output end to the sum command and # Attach the output end to the sum command and
sum_job_id1 := (pipe:input | sum | bg) $sum_job_id1 := (pipe:input | sum | bg)
sum_job_id2 := (pipe:input | sum | bg) $sum_job_id2 := (pipe:input | sum | bg)
sum_job_id3 := (pipe:input | sum | bg) $sum_job_id3 := (pipe:input | sum | bg)
sum_job_id4 := (pipe:input | sum | bg) $sum_job_id4 := (pipe:input | sum | bg)
# Close the output and input, so that the input can actually # Close the output and input, so that the input can actually
# reach EOF once the above invocations exit # reach EOF once the above invocations exit
pipe:close pipe:close
# Wait for the sum commands to finish # Wait for the sum commands to finish
val (sum_job_id1 | fg) + (sum_job_id2 | fg) + (sum_job_id3 | fg) + (sum_job_id4 | fg) val ($sum_job_id1 | fg) + ($sum_job_id2 | fg) + ($sum_job_id3 | fg) + ($sum_job_id4 | fg)

View File

@ -1,15 +1,15 @@
rm ./.test_file rm ./.test_file
# Pipe output of find into a file # Pipe output of find into a file
find example_data/tree|select ^file ^type | sort ^file | pup:to ./.test_file find ./example_data/tree|select file type | sort file | pup:to ./.test_file
# And read it back out again # And read it back out again
pup:from ./.test_file pup:from ./.test_file
# Create a closure that close on outside variables # Create a closure that close on outside variables
a := 4 $a := 4
b := 7.5 $b := 7.5
fff := {|c:integer=1 d| echo a*b*c; for (seq 3) d } $fff := {|$c:$integer=1 $d| echo $a*$b*$c; for (seq 3) $d }
# Serialize the closure # Serialize the closure
val fff | pup:to ./.test_file val $fff | pup:to ./.test_file
# Unset the variables used by the closure # Unset the variables used by the closure
var:unset "a" "b" "fff" var:unset "a" "b" "fff"
# Deserialize the closure and check that the variables still exists. # Deserialize the closure and check that the variables still exists.

View File

@ -1,8 +1,8 @@
tm := (time:parse format="%+" "2020-01-02T03:04:05+06:07") $tm := (time:parse format="%+" "2020-01-02T03:04:05+06:07")
tm = tm - (duration:of hours=1 seconds=66) $tm = tm - (duration:of hours=1 seconds=66)
tm:format "%s" $tm:format "%s"
t1 := (time:now) $t1 := (time:now)
sleep (duration:of milliseconds=50) sleep (duration:of milliseconds=50)
t2 := (time:now) $t2 := (time:now)
(t2 - t1) >= (duration:of milliseconds=50) ($t2 - $t1) >= (duration:of milliseconds=50)

View File

@ -2,12 +2,12 @@ echo 1
# Check that newline is ignored immediately after pipe # Check that newline is ignored immediately after pipe
(list:of 1 2 3) | (list:of 1 2 3) |
where {value == 2} where {$value == 2}
# Check that newline is ignored if preceded by backslash # Check that newline is ignored if preceded by backslash
(list:of 1 2 3)\ (list:of 1 2 3)\
| where {value == 3} | where {$value == 3}
# Check that newline is ignored if preceded by backslash in job # Check that newline is ignored if preceded by backslash in job
(list:of 1\ (list:of 1\
2 3 4) | where {value == 4} 2 3 4) | where {$value == 4}

View File

@ -1,7 +1,7 @@
age := (lines:from example_data/age.csv|materialize) $age := (lines:from ./example_data/age.csv|materialize)
home := (lines:from example_data/home.csv|materialize) $home := (lines:from ./example_data/home.csv|materialize)
zip (lines:from example_data/age.csv) (lines:from example_data/home.csv) zip (lines:from ./example_data/age.csv) (lines:from ./example_data/home.csv)
zip age home | head 1 zip $age $home | head 1
zip (lines:from example_data/age.csv) home | head 1 zip (lines:from ./example_data/age.csv) $home | head 1
zip age (lines:from example_data/home.csv) | head 1 zip $age (lines:from ./example_data/home.csv) | head 1