mirror of
https://github.com/liljencrantz/crush.git
synced 2024-10-04 06:17:17 +03:00
Change syntax for the whole language... :-/
This commit is contained in:
parent
ecba86aaac
commit
8d96142ac9
@ -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
|
||||
resemble a Christmas tree:
|
||||
|
||||
| Name | Description |
|
||||
| --- | --- |
|
||||
| Name | Description |
|
||||
| --- |----------------------------------------------------------|
|
||||
| `operator` | All the different Crush operators, such as `neg` and `+` |
|
||||
| `numeric_literal` | Integer and floating point literals, such as `6` |
|
||||
| `string_literal` | String literals, like `"Burrow"` |
|
||||
| `file_literal` | File literals, like `'Cargo.toml'` |
|
||||
| `label` | Variables and members, like `global` |
|
||||
| `field` | Field definitions, such as `^name` |
|
||||
| `numeric_literal` | Integer and floating point literals, such as `6` |
|
||||
| `string_literal` | String literals, like `"Burrow"` |
|
||||
| `file_literal` | File literals, like `'Cargo.toml'` |
|
||||
| `label` | Variables and members, like `$global` |
|
||||
| `field` | Field definitions, such as `name` |
|
||||
|
||||
The `term` namespace contains useful constants containing ANSI color codes.
|
||||
A configuration example:
|
||||
|
@ -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,
|
||||
aggregate and group rows of data.
|
||||
|
||||
crush# ll | sort ^size
|
||||
crush# ll | sort size
|
||||
user size modified type file
|
||||
fox 31 2019-10-03 13:43:12 +0200 file .gitignore
|
||||
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
|
||||
...
|
||||
|
||||
crush# ll | where {type == "directory"}
|
||||
crush# ll | where {$type == "directory"}
|
||||
user size modified type file
|
||||
fox 4_096 2019-11-22 21:56:30 +0100 directory target
|
||||
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
|
||||
|
||||
# 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
|
||||
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,
|
||||
@ -302,7 +302,7 @@ former displays a help messages, the latter lists the content of a value.
|
||||
|
||||
Example:
|
||||
|
||||
ps | sort ^cpu
|
||||
ps | sort cpu
|
||||
crush# dir list
|
||||
[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
|
||||
|
||||
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:
|
||||
|
||||
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
|
||||
Idle 108
|
||||
Sleeping 170
|
||||
|
@ -1,23 +1,21 @@
|
||||
|
||||
ls := {
|
||||
|sort_by:field=^file @args|
|
||||
$ls := {
|
||||
|@ $args |
|
||||
"List names of files non-recursively"
|
||||
" 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:
|
||||
|
||||
ls / sort_by=^size"
|
||||
find recursive=false @args | sort sort_by| select ^file
|
||||
ls /"
|
||||
find recursive=$false @ $args | sort file| select file
|
||||
}
|
||||
|
||||
ll := {
|
||||
|sort_by:field=^file @args|
|
||||
$ll := {
|
||||
|@ $args|
|
||||
"List files non-recursively"
|
||||
" sort_by can be one of ^user, ^size, ^modified, ^type or ^file.
|
||||
|
||||
"
|
||||
Example:
|
||||
|
||||
ll .. sort_by=^modified"
|
||||
find recursive=false @args | sort sort_by
|
||||
ll .."
|
||||
find recursive=$false @ $args | sort file
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ impl CommandNode {
|
||||
error("Stray arguments")
|
||||
}
|
||||
} else {
|
||||
let cmd = self.expressions[0].generate_argument(env)?;
|
||||
let cmd = self.expressions[0].generate_command(env)?;
|
||||
let arguments = self.expressions[1..]
|
||||
.iter()
|
||||
.map(|e| e.generate_argument(env))
|
||||
@ -259,7 +259,7 @@ pub enum Node {
|
||||
Regex(TrackedString),
|
||||
Field(TrackedString),
|
||||
String(TrackedString),
|
||||
File(TrackedString, bool),
|
||||
File(TrackedString, bool), // true if filename is quoted
|
||||
Integer(TrackedString),
|
||||
Float(TrackedString),
|
||||
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> {
|
||||
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 {
|
||||
Node::Assignment(target, op, value) => match op.deref() {
|
||||
"=" => {
|
||||
return match target.as_ref() {
|
||||
Node::Label(t) => Ok(ArgumentDefinition::named(
|
||||
Node::Field(t) => Ok(ArgumentDefinition::named(
|
||||
t.deref(),
|
||||
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"),
|
||||
@ -367,7 +395,7 @@ impl Node {
|
||||
)?),
|
||||
s.location),
|
||||
Node::GetAttr(node, label) => {
|
||||
let parent = node.generate_argument(env)?;
|
||||
let parent = node.generate(env, is_command)?;
|
||||
match parent.unnamed_value()? {
|
||||
ValueDefinition::Value(Value::Field(mut f), location) => {
|
||||
f.push(label.string.clone());
|
||||
@ -380,7 +408,12 @@ impl Node {
|
||||
Box::new(node.generate_argument(env)?.unnamed_value()?),
|
||||
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::Closure(s, c) => {
|
||||
let param = s.as_ref().map(|v| {
|
||||
@ -429,6 +462,7 @@ impl Node {
|
||||
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
|
||||
],
|
||||
env,
|
||||
true,
|
||||
),
|
||||
|
||||
Node::GetAttr(container, attr) => container.method_invocation(
|
||||
@ -441,6 +475,7 @@ impl Node {
|
||||
ArgumentDefinition::unnamed(value.generate_argument(env)?.unnamed_value()?),
|
||||
],
|
||||
env,
|
||||
true,
|
||||
),
|
||||
|
||||
_ => error("Invalid left side in assignment"),
|
||||
@ -467,7 +502,7 @@ impl Node {
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -506,24 +541,31 @@ impl Node {
|
||||
name: &TrackedString,
|
||||
arguments: Vec<ArgumentDefinition>,
|
||||
env: &Scope,
|
||||
as_command: bool,
|
||||
) -> CrushResult<Option<CommandInvocation>> {
|
||||
Ok(Some(CommandInvocation::new(
|
||||
ValueDefinition::GetAttr(
|
||||
Box::from(self.generate_argument(env)?.unnamed_value()?),
|
||||
Box::from(self.generate(env, as_command)?.unnamed_value()?),
|
||||
name.clone(),
|
||||
),
|
||||
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('?') {
|
||||
Box::from(Node::Glob(s.clone()))
|
||||
} else if s.string.contains('/') || s.string.contains('.') {
|
||||
Box::from(Node::File(s.clone(), false))
|
||||
} 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> {
|
||||
if s.string.contains('%') || s.string.contains('?') {
|
||||
Box::from(Node::Glob(s.clone()))
|
||||
@ -584,7 +626,7 @@ fn expand_user(s: &str, location: Location) -> Box<Node> {
|
||||
simple_substitution(
|
||||
vec![
|
||||
attr(&vec!["global", "user", "find"], location),
|
||||
Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location))
|
||||
Node::String(TrackedString::from(&format!("\"{}\"", &s[1..]), location)),
|
||||
],
|
||||
location,
|
||||
),
|
||||
@ -660,7 +702,8 @@ pub enum TokenType {
|
||||
FactorOperator,
|
||||
TermOperator,
|
||||
QuotedString,
|
||||
LabelOrWildcard,
|
||||
StringOrWildcard,
|
||||
Label,
|
||||
Flag,
|
||||
Field,
|
||||
QuotedFile,
|
||||
|
@ -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_invocation::CommandInvocation;
|
||||
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::help::Help;
|
||||
use crate::lang::job::Job;
|
||||
@ -595,7 +595,11 @@ impl Closure {
|
||||
if named.contains_key(&name.string) {
|
||||
let value = named.remove(&name.string).unwrap();
|
||||
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)?;
|
||||
} else if !unnamed.is_empty() {
|
||||
@ -604,7 +608,12 @@ impl Closure {
|
||||
let env = context.env.clone();
|
||||
env.redeclare(&name.string, default.eval_and_bind(context)?)?;
|
||||
} 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 {
|
||||
return argument_error_legacy("Not a type");
|
||||
|
@ -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::data::scope::Scope;
|
||||
use crate::lang::{argument::ArgumentDefinition, argument::ArgumentVecCompiler, value::Value};
|
||||
use crate::lang::command::Command;
|
||||
use crate::lang::execution_context::CommandContext;
|
||||
use crate::lang::value::{ValueDefinition, ValueType};
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::thread::ThreadId;
|
||||
@ -18,24 +17,6 @@ pub struct CommandInvocation {
|
||||
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 {
|
||||
for arg in local_arguments {
|
||||
if arg.value.can_block(context) {
|
||||
@ -107,22 +88,12 @@ pub fn eval_non_blocking(command: &ValueDefinition, arguments: &Vec<ArgumentDefi
|
||||
Ok((this, value)) => {
|
||||
eval_internal(this, value, arguments.clone(), context, command.location())
|
||||
}
|
||||
Err(err) => {
|
||||
let (cmd, sub) = match command {
|
||||
ValueDefinition::Label(str) => (str, None),
|
||||
ValueDefinition::GetAttr(parent, sub) => match parent.deref() {
|
||||
ValueDefinition::Label(str) => if context.scope.get(str.string.as_str())?.is_none() {
|
||||
(str, Some(sub))
|
||||
} else {
|
||||
return Err(err);
|
||||
},
|
||||
_ => return Err(err),
|
||||
},
|
||||
_ => return Err(err),
|
||||
};
|
||||
|
||||
try_external_command(cmd, sub, arguments.clone(), context)
|
||||
}
|
||||
Err(err) =>
|
||||
if let ValueDefinition::Label(str) = command {
|
||||
try_external_command(str, arguments.clone(), context)
|
||||
} else {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(
|
||||
cmd: &TrackedString,
|
||||
sub: Option<&TrackedString>,
|
||||
mut arguments: Vec<ArgumentDefinition>,
|
||||
context: JobContext,
|
||||
) -> CrushResult<Option<ThreadId>> {
|
||||
|
||||
match resolve_external_command(&cmd.string, &context.scope)? {
|
||||
None => error(format!("Unknown command name {}", cmd).as_str()),
|
||||
Some(path) => {
|
||||
@ -289,16 +276,6 @@ fn try_external_command(
|
||||
0,
|
||||
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 {
|
||||
command: ValueDefinition::Value(
|
||||
Value::Command(
|
||||
|
@ -281,6 +281,9 @@ fn complete_partial_argument(
|
||||
|
||||
LastArgument::Label(label) => {
|
||||
complete_label(Value::Scope(scope.clone()), &label, &argument_type, cursor, res)?;
|
||||
}
|
||||
|
||||
LastArgument::Field(label) => {
|
||||
if parse_result.last_argument_name.is_none() {
|
||||
if let CompletionCommand::Known(cmd) = parse_result.command {
|
||||
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)?;
|
||||
}
|
||||
|
||||
@ -320,6 +323,10 @@ pub fn complete(
|
||||
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) => {
|
||||
complete_label(parent, &label, &ValueType::Any, cursor, &mut res)?;
|
||||
}
|
||||
@ -642,8 +649,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn complete_namespaced_argument() {
|
||||
let line = "xxx abcd:bc";
|
||||
let cursor = 11;
|
||||
let line = "xxx $abcd:bc";
|
||||
let cursor = line.len();
|
||||
|
||||
let s = Scope::create_root();
|
||||
s.create_namespace("abcd", "bla", Box::new(|env| {
|
||||
@ -653,19 +660,19 @@ mod tests {
|
||||
|
||||
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(&completions[0].complete(line), "xxx abcd:bcde ");
|
||||
assert_eq!(&completions[0].complete(line), "xxx $abcd:bcde ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn complete_simple_argument() {
|
||||
let line = "abcd ab";
|
||||
let cursor = 7;
|
||||
let line = "abcd $ab";
|
||||
let cursor = line.len();
|
||||
|
||||
let s = Scope::create_root();
|
||||
s.declare("abcd", Value::Empty()).unwrap();
|
||||
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(&completions[0].complete(line), "abcd abcd ");
|
||||
assert_eq!(&completions[0].complete(line), "abcd $abcd ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -682,26 +689,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn check_multiple_token() {
|
||||
let line = "ab cd ef";
|
||||
let cursor = 5;
|
||||
let line = "ab $cd ef";
|
||||
let cursor = 6;
|
||||
|
||||
let s = Scope::create_root();
|
||||
s.declare("cdef", Value::Empty()).unwrap();
|
||||
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(&completions[0].complete(line), "ab cdef ef");
|
||||
assert_eq!(&completions[0].complete(line), "ab $cdef ef");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_named_argument() {
|
||||
let line = "ab foo=cd";
|
||||
let cursor = 9;
|
||||
let line = "ab foo=$cd";
|
||||
let cursor = 10;
|
||||
|
||||
let s = Scope::create_root();
|
||||
s.declare("cdef", Value::Empty()).unwrap();
|
||||
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
|
||||
assert_eq!(completions.len(), 1);
|
||||
assert_eq!(&completions[0].complete(line), "ab foo=cdef ");
|
||||
assert_eq!(&completions[0].complete(line), "ab foo=$cdef ");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -718,7 +725,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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 s = scope_with_function();
|
||||
@ -726,7 +733,7 @@ mod tests {
|
||||
s.declare("type", Value::Type(ValueType::Empty)).unwrap();
|
||||
let completions = complete(line, cursor, &s, &parser(), &empty_lister()).unwrap();
|
||||
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]
|
||||
|
@ -9,6 +9,7 @@ use crate::util::glob::Glob;
|
||||
use crate::lang::parser::Parser;
|
||||
use crate::util::escape::unescape;
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
pub enum CompletionCommand {
|
||||
Unknown,
|
||||
@ -28,7 +29,8 @@ impl Clone for CompletionCommand {
|
||||
pub enum LastArgument {
|
||||
Unknown,
|
||||
Label(String),
|
||||
Field(Value, String),
|
||||
Field(String),
|
||||
Member(Value, String),
|
||||
File(String, bool),
|
||||
QuotedString(String),
|
||||
Switch(String),
|
||||
@ -68,9 +70,9 @@ impl PartialCommandResult {
|
||||
if false && cmd.arguments().len() == 1 {
|
||||
Some(&cmd.arguments()[0])
|
||||
} else {
|
||||
|
||||
let mut previous_named = HashSet::new();
|
||||
let mut previous_unnamed = 0usize;
|
||||
|
||||
for arg in &self.previous_arguments {
|
||||
match &arg.name {
|
||||
Some(name) => {
|
||||
@ -83,9 +85,9 @@ impl PartialCommandResult {
|
||||
let mut unnamed_used = 0usize;
|
||||
for arg in cmd.arguments() {
|
||||
if arg.unnamed {
|
||||
return Some(arg)
|
||||
return Some(arg);
|
||||
}
|
||||
if previous_named.contains(&arg.name ) {
|
||||
if previous_named.contains(&arg.name) {
|
||||
continue;
|
||||
} else {
|
||||
unnamed_used += 1;
|
||||
@ -115,12 +117,44 @@ impl PartialCommandResult {
|
||||
pub enum ParseResult {
|
||||
Nothing,
|
||||
PartialLabel(String),
|
||||
PartialField(String),
|
||||
PartialMember(Value, String),
|
||||
PartialFile(String, bool),
|
||||
PartialQuotedString(String),
|
||||
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> {
|
||||
match node {
|
||||
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")
|
||||
}
|
||||
|
||||
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 {
|
||||
Node::Label(l) => scope.get(&l.string),
|
||||
|
||||
Node::Field(l) =>
|
||||
if is_command {
|
||||
scope.get(&l.string)
|
||||
} else {
|
||||
Ok(None)
|
||||
},
|
||||
|
||||
Node::GetAttr(n, l) =>
|
||||
match fetch_value(n, scope)? {
|
||||
match fetch_value(n, scope, is_command)? {
|
||||
Some(parent) => parent.field(&l.string),
|
||||
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> {
|
||||
match fetch_value(node, scope)? {
|
||||
match fetch_value(node, scope, true)? {
|
||||
Some(Value::Command(command)) => Ok(CompletionCommand::Known(command)),
|
||||
_ => Ok(CompletionCommand::Unknown),
|
||||
}
|
||||
@ -235,18 +276,18 @@ fn parse_previous_argument(arg: &Node) -> PreviousArgument {
|
||||
match arg {
|
||||
Node::Assignment(key, op, value) => {
|
||||
match (key.as_ref(), op.as_str()) {
|
||||
(Node::Label(name), "=") => {
|
||||
(Node::Field(name), "=") => {
|
||||
let inner = parse_previous_argument(value.as_ref());
|
||||
return PreviousArgument {
|
||||
name: Some(name.string.clone()),
|
||||
value: inner.value
|
||||
}
|
||||
value: inner.value,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
PreviousArgument {
|
||||
name: None,
|
||||
@ -278,9 +319,13 @@ pub fn parse(
|
||||
Ok(ParseResult::PartialLabel(
|
||||
label.prefix(cursor).string)),
|
||||
|
||||
Node::Field(label) =>
|
||||
Ok(ParseResult::PartialField(
|
||||
label.prefix(cursor).string)),
|
||||
|
||||
Node::GetAttr(parent, field) =>
|
||||
Ok(ParseResult::PartialMember(
|
||||
mandate(fetch_value(parent, scope)?, "Unknown value")?,
|
||||
mandate(fetch_value(parent, scope, true)?, "Unknown value")?,
|
||||
field.prefix(cursor).string)),
|
||||
|
||||
Node::Path(_, _) =>
|
||||
@ -296,7 +341,7 @@ pub fn parse(
|
||||
|
||||
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 {
|
||||
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) =>
|
||||
Ok(ParseResult::PartialArgument(
|
||||
PartialCommandResult {
|
||||
command: c,
|
||||
previous_arguments,
|
||||
last_argument: LastArgument::Field(
|
||||
mandate(fetch_value(parent, scope)?, "unknown value")?,
|
||||
last_argument: LastArgument::Member(
|
||||
mandate(fetch_value(parent, scope, false)?, "unknown value")?,
|
||||
field.prefix(cursor).string),
|
||||
last_argument_name,
|
||||
})),
|
||||
@ -451,8 +506,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn find_command_in_operator() {
|
||||
let ast = ast("ps | where {^cpu == (max_)}").unwrap();
|
||||
let cmd = find_command_in_job_list(ast, 25).unwrap();
|
||||
assert_eq!(cmd.location, Location::new(21, 25))
|
||||
let ast = ast("ps | where {cpu == (max_)}").unwrap();
|
||||
let cmd = find_command_in_job_list(ast, 24).unwrap();
|
||||
assert_eq!(cmd.location, Location::new(20, 24))
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ impl RustylineHelper {
|
||||
QuotedString => highlight.get(&Value::string("string_literal")),
|
||||
Regex => highlight.get(&Value::string("string_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")),
|
||||
Float => highlight.get(&Value::string("numeric_literal")),
|
||||
Field => highlight.get(&Value::string("field")),
|
||||
|
@ -102,15 +102,15 @@ ParameterList: Vec<ParameterNode> = {
|
||||
}
|
||||
|
||||
Parameter: ParameterNode = {
|
||||
<start: @L> <l: LabelOrWildcard> <end: @R> <d: Default> =>
|
||||
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), None, d),
|
||||
<start: @L> <l: Label> <end: @R> <d: Default> =>
|
||||
ParameterNode::Parameter(TrackedString::from(&l[1..], Location::new(start, end)), None, d),
|
||||
|
||||
<start: @L> <l: LabelOrWildcard> <end: @R> Colon <t: Item> <d: Default> =>
|
||||
ParameterNode::Parameter(TrackedString::from(l, Location::new(start, end)), Some(t), d),
|
||||
<start: @L> <l: Label> <end: @R> Colon <t: Item> <d: Default> =>
|
||||
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> = {
|
||||
@ -119,9 +119,11 @@ Default: Option<Node> = {
|
||||
}
|
||||
|
||||
Item: Box<Node> = {
|
||||
<start: @L> <l: LabelOrWildcard> <end: @R> =>
|
||||
Node::parse_label_or_wildcard(&TrackedString::from(l, Location::new(start, end))),
|
||||
<start: @L> <l: StringOrWildcard> <end: @R> =>
|
||||
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> =>
|
||||
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> =>
|
||||
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(),
|
||||
Box::from(Node::Label(TrackedString::from("true", Location::new(start, start+2)))))),
|
||||
|
||||
<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)),
|
||||
"(" <j:Job> ")" => Box::from(Node::Substitution(j)),
|
||||
}
|
||||
@ -169,7 +171,8 @@ Token: TokenNode = {
|
||||
@L FactorOperator @R => TokenNode::new(TokenType::FactorOperator, <>),
|
||||
@L TermOperator @R => TokenNode::new(TokenType::TermOperator, <>),
|
||||
@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 FileOrWildcard @R => TokenNode::new(TokenType::FileOrWildcard, <>),
|
||||
@L Flag @R => TokenNode::new(TokenType::Flag, <>),
|
||||
@ -202,7 +205,8 @@ match {
|
||||
r"(\*|//)" => FactorOperator,
|
||||
r"(\+|-)" => TermOperator,
|
||||
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]+" => Flag,
|
||||
r"\^[\._a-zA-Z][\._a-zA-Z0-9]*" => Field,
|
||||
|
@ -6,7 +6,7 @@ use crate::lang::data::binary::BinaryReader;
|
||||
use crate::lang::errors::to_crush_error;
|
||||
use crate::lang::data::list::ListReader;
|
||||
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::Row;
|
||||
use crate::lang::data::table::Table;
|
||||
|
@ -38,7 +38,7 @@ use crate::util::regex::RegexFileMatcher;
|
||||
use ordered_map::OrderedMap;
|
||||
pub use value_definition::ValueDefinition;
|
||||
pub use value_type::ValueType;
|
||||
use std::fmt::{Display, Formatter, Debug};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use num_format::Grouping;
|
||||
use crate::util::escape::escape;
|
||||
|
||||
@ -75,10 +75,7 @@ impl Display for Value {
|
||||
Value::String(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::Field(val) => {
|
||||
f.write_str("^")?;
|
||||
f.write_str(&val.join(":"))
|
||||
}
|
||||
Value::Field(val) => f.write_str(&val.join(":")),
|
||||
Value::Glob(val) => std::fmt::Display::fmt(val, f),
|
||||
Value::Regex(val, _) => {
|
||||
f.write_str("re\"")?;
|
||||
|
@ -1,7 +1,7 @@
|
||||
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::{argument::ArgumentDefinition, command::CrushCommand, job::Job};
|
||||
use crate::lang::{command::CrushCommand, job::Job};
|
||||
use crate::{
|
||||
lang::errors::CrushResult, lang::pipe::pipe, lang::pipe::empty_channel,
|
||||
lang::value::Value,
|
||||
|
@ -87,7 +87,7 @@ fn echo(context: CommandContext) -> CrushResult<()> {
|
||||
member,
|
||||
can_block = false,
|
||||
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 {
|
||||
#[description("the member to extract.")]
|
||||
|
@ -1,10 +1,8 @@
|
||||
use lazy_static::lazy_static;
|
||||
use crate::lang::command::OutputType::Known;
|
||||
use crate::lang::errors::{to_crush_error, CrushResult};
|
||||
use crate::lang::execution_context::CommandContext;
|
||||
use crate::lang::data::scope::Scope;
|
||||
use crate::lang::data::table::ColumnType;
|
||||
use crate::{data::table::Row, lang::value::Value, lang::value::ValueType};
|
||||
use crate::{lang::value::Value, lang::value::ValueType};
|
||||
use nix::sys::signal;
|
||||
use nix::unistd::Pid;
|
||||
use signature::signature;
|
||||
@ -23,7 +21,7 @@ mod macos {
|
||||
use signature::signature;
|
||||
|
||||
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("ppid", ValueType::Integer),
|
||||
ColumnType::new("user", ValueType::String),
|
||||
@ -58,12 +56,12 @@ mod macos {
|
||||
}
|
||||
|
||||
#[signature(
|
||||
ps,
|
||||
list,
|
||||
can_block = true,
|
||||
short = "Return a table stream containing information on all running processes on the system",
|
||||
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())),
|
||||
long = "ps accepts no arguments.")]
|
||||
pub struct Ps {}
|
||||
output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
|
||||
long = "proc:list accepts no arguments.")]
|
||||
pub struct List {}
|
||||
|
||||
use libproc::libproc::bsd_info::BSDInfo;
|
||||
use libproc::libproc::file_info::{pidfdinfo, ListFDs, ProcFDType};
|
||||
@ -124,11 +122,11 @@ mod macos {
|
||||
TaskAllInfo { pbsd, ptinfo }
|
||||
}
|
||||
|
||||
fn ps(context: CommandContext) -> CrushResult<()> {
|
||||
fn list(context: CommandContext) -> CrushResult<()> {
|
||||
let mut base_procs = Vec::new();
|
||||
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 mut info: mach_timebase_info = mach_timebase_info{numer: 0, denom: 0};
|
||||
@ -243,7 +241,7 @@ mod linux {
|
||||
use std::collections::HashMap;
|
||||
|
||||
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("ppid", ValueType::Integer),
|
||||
ColumnType::new("status", ValueType::String),
|
||||
@ -276,16 +274,16 @@ mod linux {
|
||||
|
||||
|
||||
#[signature(
|
||||
ps,
|
||||
list,
|
||||
can_block = true,
|
||||
short = "Return a table stream containing information on all running processes on the system",
|
||||
output = Known(ValueType::TableInputStream(PS_OUTPUT_TYPE.clone())),
|
||||
long = "ps accepts no arguments.")]
|
||||
pub struct Ps {}
|
||||
output = Known(ValueType::TableInputStream(LIST_OUTPUT_TYPE.clone())),
|
||||
long = "proc:list accepts no arguments.")]
|
||||
pub struct List {}
|
||||
|
||||
fn ps(context: CommandContext) -> CrushResult<()> {
|
||||
Ps::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||
let output = context.output.initialize(PS_OUTPUT_TYPE.clone())?;
|
||||
fn list(context: CommandContext) -> CrushResult<()> {
|
||||
List::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||
let output = context.output.initialize(LIST_OUTPUT_TYPE.clone())?;
|
||||
let users = create_user_map()?;
|
||||
|
||||
match psutil::process::processes() {
|
||||
@ -356,12 +354,11 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||
"Process related commands",
|
||||
Box::new(move |env| {
|
||||
#[cfg(target_os = "linux")]
|
||||
linux::Ps::declare(env)?;
|
||||
linux::List::declare(env)?;
|
||||
#[cfg(target_os = "macos")]
|
||||
macos::Ps::declare(env)?;
|
||||
macos::List::declare(env)?;
|
||||
Kill::declare(env)?;
|
||||
Ok(())
|
||||
}))?;
|
||||
root.r#use(&e);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ drop,
|
||||
can_block = true,
|
||||
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.",
|
||||
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,
|
||||
)]
|
||||
pub struct Drop {
|
||||
|
@ -1,7 +1,6 @@
|
||||
use crate::lang::command::Command;
|
||||
use crate::lang::errors::{error, CrushResult};
|
||||
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::{data::table::Row, value::Value};
|
||||
use signature::signature;
|
||||
@ -15,7 +14,7 @@ can_block = true,
|
||||
output = Known(Empty),
|
||||
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.",
|
||||
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 {
|
||||
#[description("the command to run.")]
|
||||
body: Command,
|
||||
|
@ -6,9 +6,16 @@ use signature::signature;
|
||||
|
||||
#[signature(enumerate, short = "Prepend a column containing the row number to each row of the input.")]
|
||||
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<()> {
|
||||
let cfg: Enumerate = Enumerate::parse(context.arguments, &context.global_state.printer())?;
|
||||
match context.input.recv()?.stream() {
|
||||
Some(mut input) => {
|
||||
let mut output_type = vec![
|
||||
@ -16,12 +23,12 @@ fn enumerate(context: CommandContext) -> CrushResult<()> {
|
||||
output_type.extend(input.types().to_vec());
|
||||
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() {
|
||||
let mut out = vec![Value::Integer(line)];
|
||||
out.extend(Vec::from(row));
|
||||
output.send(Row::new(out))?;
|
||||
line += 1;
|
||||
line += cfg.step;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ use crate::lang::global_state::GlobalState;
|
||||
group,
|
||||
can_block = true,
|
||||
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 {
|
||||
#[unnamed()]
|
||||
|
@ -45,11 +45,12 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||
sum_avg::Avg::declare(env)?;
|
||||
sum_avg::Min::declare(env)?;
|
||||
sum_avg::Max::declare(env)?;
|
||||
sum_avg::Mul::declare(env)?;
|
||||
env.declare_command(
|
||||
"select", select::select, true,
|
||||
"select copy_fields:field... [%] new_field=definition:command",
|
||||
"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![],
|
||||
)?;
|
||||
seq::Seq::declare(env)?;
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::lang::command::Command;
|
||||
use crate::lang::errors::error;
|
||||
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::errors::argument_error_legacy,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::mem;
|
||||
use crate::lang::errors::CrushResult;
|
||||
use crate::lang::execution_context::CommandContext;
|
||||
use crate::lang::data::table::ColumnType;
|
||||
@ -22,9 +23,7 @@ pub fn seq(context: CommandContext) -> CrushResult<()> {
|
||||
.initialize(vec![ColumnType::new("value", ValueType::Integer)])?;
|
||||
|
||||
if (cfg.to > cfg.from) != (cfg.step > 0) {
|
||||
let tmp = cfg.to;
|
||||
cfg.to = cfg.from;
|
||||
cfg.from = tmp;
|
||||
mem::swap(&mut cfg.to, &mut cfg.from);
|
||||
}
|
||||
|
||||
let mut idx = cfg.from;
|
||||
|
@ -11,7 +11,7 @@ use std::cmp::Ordering;
|
||||
#[signature(
|
||||
sort,
|
||||
short = "Sort input based on column",
|
||||
example = "ps | sort ^cpu",
|
||||
example = "ps | sort cpu",
|
||||
output = Passthrough)]
|
||||
pub struct Sort {
|
||||
#[unnamed()]
|
||||
|
@ -42,7 +42,7 @@ sum_function!(sum_duration, Duration, Duration::seconds(0), Duration);
|
||||
#[signature(
|
||||
sum,
|
||||
short = "Calculate the sum for the specific column across all rows.",
|
||||
example = "ps | sum ^cpu")]
|
||||
example = "proc:list | sum cpu")]
|
||||
pub struct Sum {
|
||||
field: Option<Field>,
|
||||
}
|
||||
@ -94,7 +94,7 @@ avg_function!(avg_duration, Duration, Duration::seconds(0), Duration, i32);
|
||||
#[signature(
|
||||
avg,
|
||||
short = "Calculate the average for the specific column across all rows.",
|
||||
example = "ps | avg ^cpu")]
|
||||
example = "proc:list | avg cpu")]
|
||||
pub struct Avg {
|
||||
field: Option<Field>,
|
||||
}
|
||||
@ -159,7 +159,7 @@ aggr_function!(max_time, Time, |a, b| std::cmp::max(a, b));
|
||||
#[signature(
|
||||
min,
|
||||
short = "Calculate the minimum for the specific column across all rows.",
|
||||
example = "ps | min ^cpu")]
|
||||
example = "proc:list | min cpu")]
|
||||
pub struct Min {
|
||||
field: Option<Field>,
|
||||
}
|
||||
@ -186,7 +186,7 @@ fn min(context: CommandContext) -> CrushResult<()> {
|
||||
#[signature(
|
||||
max,
|
||||
short = "Calculate the maximum for the specific column across all rows.",
|
||||
example = "ps | max ^cpu")]
|
||||
example = "proc:list | max cpu")]
|
||||
pub struct Max {
|
||||
field: Option<Field>,
|
||||
}
|
||||
@ -209,3 +209,46 @@ fn max(context: CommandContext) -> CrushResult<()> {
|
||||
_ => 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"),
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use crate::lang::value::Field;
|
||||
uniq,
|
||||
output = Passthrough,
|
||||
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 {
|
||||
field: Option<Field>,
|
||||
}
|
||||
|
@ -2,11 +2,11 @@ use crate::lang::command::Command;
|
||||
use crate::lang::command::OutputType::Passthrough;
|
||||
use crate::lang::errors::{error, CrushResult};
|
||||
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::{data::table::Row, value::Value};
|
||||
use signature::signature;
|
||||
use crate::lang::ast::Location;
|
||||
use crate::lang::pipe::pipe;
|
||||
|
||||
#[signature(
|
||||
r#where,
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::collections::VecDeque;
|
||||
/**
|
||||
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 crate::lang::errors::{CrushResult, to_crush_error, mandate};
|
||||
use crate::lang::errors::{CrushResult, mandate, to_crush_error};
|
||||
use std::fs::{ReadDir, read_dir};
|
||||
use ordered_map::{OrderedMap, Entry};
|
||||
use std::collections::VecDeque;
|
||||
use ordered_map::{Entry, OrderedMap};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Directory {
|
||||
|
@ -1,6 +1,7 @@
|
||||
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 crate::CrushResult;
|
||||
|
||||
pub fn escape_without_quotes(s: &str) -> String {
|
||||
let mut res = String::with_capacity(s.len());
|
||||
@ -41,7 +42,6 @@ enum State {
|
||||
|
||||
pub fn unescape(s: &str) -> CrushResult<String> {
|
||||
use State::*;
|
||||
use crate::lang::errors::CrushResult;
|
||||
|
||||
let mut res = "".to_string();
|
||||
let mut state = Normal;
|
||||
|
@ -1,7 +1,4 @@
|
||||
touch ./foo
|
||||
touch ./foo
|
||||
touch ./foo
|
||||
touch ./foo
|
||||
foo:chmod "a=" "o+xr" "u+w" "g-r"
|
||||
find ./foo | select ^permissions
|
||||
#rm ./foo
|
||||
./foo:chmod "a=" "o+xr" "u+w" "g-r"
|
||||
find ./foo | select permissions
|
||||
rm ./foo
|
||||
|
@ -1,22 +1,23 @@
|
||||
Point := (class)
|
||||
Point:__init__ = {
|
||||
|x:float y:float|
|
||||
this:x = x
|
||||
this:y = y
|
||||
$Point := (class)
|
||||
$Point:__init__ = {
|
||||
|$x:$float $y:$float|
|
||||
this:x = $x
|
||||
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__ = {
|
||||
|@unnamed|
|
||||
other := unnamed[0]
|
||||
Point:new x=(this:x + other:x) y=(this:y + other:y)
|
||||
$Point:__add__ = {
|
||||
|@ $unnamed|
|
||||
$other := $unnamed[0]
|
||||
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)
|
||||
p3 := p1 + p2
|
||||
p3:len
|
||||
|
||||
$p1 := (Point:new x=0.0 y=4.0)
|
||||
$p2 := (Point:new x=3.0 y=0.0)
|
||||
$p3 := $p1 + $p2
|
||||
$p3:len
|
||||
|
@ -1,3 +1,3 @@
|
||||
ggg := {|a : (dict integer integer)| echo a}
|
||||
hhh := ((dict integer integer):new)
|
||||
ggg a=hhh
|
||||
$ggg := {|$a : (dict $integer $integer)| echo $a}
|
||||
$hhh := ((dict $integer $integer):new)
|
||||
ggg a=$hhh
|
||||
|
@ -41,14 +41,14 @@
|
||||
|
||||
"Time comparisons"
|
||||
|
||||
t1 := (time:now)
|
||||
t2 := (time:now + (duration:of seconds=1))
|
||||
t1 > t2
|
||||
t1 < t2
|
||||
t1 >= t2
|
||||
t1 <= t2
|
||||
t1 != t2
|
||||
t1 == t2
|
||||
$t1 := (time:now)
|
||||
$t2 := (time:now + (duration:of seconds=1))
|
||||
$t1 > $t2
|
||||
$t1 < $t2
|
||||
$t1 >= $t2
|
||||
$t1 <= $t2
|
||||
$t1 != $t2
|
||||
$t1 == $t2
|
||||
|
||||
"String comparisons"
|
||||
"a" > "b"
|
||||
|
@ -1,4 +1,4 @@
|
||||
convert 1.0 integer
|
||||
convert 0.5 integer
|
||||
convert 5 string
|
||||
typeof (convert 5 string)
|
||||
convert 1.0 $integer
|
||||
convert 0.5 $integer
|
||||
convert 5 $string
|
||||
typeof (convert 5 $string)
|
||||
|
@ -1 +1 @@
|
||||
find example_data/tree | select ^file | sort ^file
|
||||
find ./example_data/tree | select file | sort file
|
||||
|
@ -1,33 +1,33 @@
|
||||
for i=(list:of 1 2) {
|
||||
echo i
|
||||
echo $i
|
||||
}
|
||||
|
||||
d := ((dict string integer):new)
|
||||
d["fooo"] = 3
|
||||
$d := ((dict $string $integer):new)
|
||||
$d["fooo"] = 3
|
||||
|
||||
for d {
|
||||
echo key value
|
||||
for $d {
|
||||
echo $key $value
|
||||
}
|
||||
|
||||
for i=d {
|
||||
echo i:key i:value
|
||||
for i=$d {
|
||||
echo $i:key $i:value
|
||||
}
|
||||
|
||||
table := (seq 3 | materialize)
|
||||
$table := (seq 3 | materialize)
|
||||
|
||||
for table {
|
||||
echo value
|
||||
for $table {
|
||||
echo $value
|
||||
}
|
||||
|
||||
for i=table {
|
||||
echo i
|
||||
for i=$table {
|
||||
echo $i
|
||||
}
|
||||
|
||||
|
||||
for (seq 3) {
|
||||
echo value
|
||||
echo $value
|
||||
}
|
||||
|
||||
for i=(seq 3) {
|
||||
echo i
|
||||
echo $i
|
||||
}
|
||||
|
@ -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 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 | 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
|
||||
|
@ -1,4 +1,4 @@
|
||||
home:=(csv:from example_data/home.csv name=string country=string)
|
||||
age:=(csv:from example_data/age.csv name=string age=integer)
|
||||
$home:=(csv:from ./example_data/home.csv name=$string country=$string)
|
||||
$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
|
||||
|
@ -1,8 +1,8 @@
|
||||
json:from example_data/din%.json |
|
||||
sort ^name
|
||||
sort name
|
||||
|
||||
json:from example_data/din%.json |
|
||||
where {name =~ re"Tri.*"}
|
||||
where {$name =~ re"Tri.*"}
|
||||
|
||||
# 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
|
||||
|
@ -4,10 +4,10 @@ loop {
|
||||
echo "NO"
|
||||
}
|
||||
|
||||
a := false
|
||||
$a := $false
|
||||
loop {
|
||||
if a break
|
||||
a = true
|
||||
if $a $break
|
||||
$a = $true
|
||||
echo 2
|
||||
continue
|
||||
echo "NO"
|
||||
|
@ -2,20 +2,20 @@
|
||||
# of multiple commands into the same 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
|
||||
_1 := (seq 100_000 | pipe:output:write | bg)
|
||||
_2 := (seq 100_000 | pipe:output:write | bg)
|
||||
_3 := (seq 100_000 | pipe:output:write | bg)
|
||||
_4 := (seq 100_000 | pipe:output:write | bg)
|
||||
$_1 := (seq 100_000 | pipe:output:write | bg)
|
||||
$_2 := (seq 100_000 | pipe:output:write | bg)
|
||||
$_3 := (seq 100_000 | pipe:output:write | bg)
|
||||
$_4 := (seq 100_000 | pipe:output:write | bg)
|
||||
|
||||
# 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
|
||||
# reach EOF once the above 4 invocation exit
|
||||
pipe:close
|
||||
|
||||
# Wait for the sum command to finish
|
||||
sum_job_id | fg
|
||||
$sum_job_id | fg
|
||||
|
@ -2,19 +2,19 @@
|
||||
# of a command between multiple commands via the same 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
|
||||
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
|
||||
sum_job_id1 := (pipe:input | sum | bg)
|
||||
sum_job_id2 := (pipe:input | sum | bg)
|
||||
sum_job_id3 := (pipe:input | sum | bg)
|
||||
sum_job_id4 := (pipe:input | sum | bg)
|
||||
$sum_job_id1 := (pipe:input | sum | bg)
|
||||
$sum_job_id2 := (pipe:input | sum | bg)
|
||||
$sum_job_id3 := (pipe:input | sum | bg)
|
||||
$sum_job_id4 := (pipe:input | sum | bg)
|
||||
|
||||
# Close the output and input, so that the input can actually
|
||||
# reach EOF once the above 4 invocation exit
|
||||
pipe:close
|
||||
# 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)
|
||||
|
@ -2,23 +2,23 @@
|
||||
# can use a single pipe both for input and output.
|
||||
|
||||
# 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
|
||||
input1 := (seq 100_000 | pipe:output:write | bg)
|
||||
input2 := (seq 100_000 | pipe:output:write | bg)
|
||||
input3 := (seq 100_000 | pipe:output:write | bg)
|
||||
input4 := (seq 100_000 | pipe:output:write | bg)
|
||||
$input1 := (seq 100_000 | pipe:output:write | bg)
|
||||
$input2 := (seq 100_000 | pipe:output:write | bg)
|
||||
$input3 := (seq 100_000 | pipe:output:write | bg)
|
||||
$input4 := (seq 100_000 | pipe:output:write | bg)
|
||||
|
||||
# Attach the output end to the sum command and
|
||||
sum_job_id1 := (pipe:input | sum | bg)
|
||||
sum_job_id2 := (pipe:input | sum | bg)
|
||||
sum_job_id3 := (pipe:input | sum | bg)
|
||||
sum_job_id4 := (pipe:input | sum | bg)
|
||||
$sum_job_id1 := (pipe:input | sum | bg)
|
||||
$sum_job_id2 := (pipe:input | sum | bg)
|
||||
$sum_job_id3 := (pipe:input | sum | bg)
|
||||
$sum_job_id4 := (pipe:input | sum | bg)
|
||||
|
||||
# Close the output and input, so that the input can actually
|
||||
# reach EOF once the above invocations exit
|
||||
pipe:close
|
||||
|
||||
# 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)
|
||||
|
@ -1,15 +1,15 @@
|
||||
rm ./.test_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
|
||||
pup:from ./.test_file
|
||||
|
||||
# Create a closure that close on outside variables
|
||||
a := 4
|
||||
b := 7.5
|
||||
fff := {|c:integer=1 d| echo a*b*c; for (seq 3) d }
|
||||
$a := 4
|
||||
$b := 7.5
|
||||
$fff := {|$c:$integer=1 $d| echo $a*$b*$c; for (seq 3) $d }
|
||||
# Serialize the closure
|
||||
val fff | pup:to ./.test_file
|
||||
val $fff | pup:to ./.test_file
|
||||
# Unset the variables used by the closure
|
||||
var:unset "a" "b" "fff"
|
||||
# Deserialize the closure and check that the variables still exists.
|
||||
|
@ -1,8 +1,8 @@
|
||||
tm := (time:parse format="%+" "2020-01-02T03:04:05+06:07")
|
||||
tm = tm - (duration:of hours=1 seconds=66)
|
||||
tm:format "%s"
|
||||
$tm := (time:parse format="%+" "2020-01-02T03:04:05+06:07")
|
||||
$tm = tm - (duration:of hours=1 seconds=66)
|
||||
$tm:format "%s"
|
||||
|
||||
t1 := (time:now)
|
||||
$t1 := (time:now)
|
||||
sleep (duration:of milliseconds=50)
|
||||
t2 := (time:now)
|
||||
(t2 - t1) >= (duration:of milliseconds=50)
|
||||
$t2 := (time:now)
|
||||
($t2 - $t1) >= (duration:of milliseconds=50)
|
||||
|
@ -2,12 +2,12 @@ echo 1
|
||||
|
||||
# Check that newline is ignored immediately after pipe
|
||||
(list:of 1 2 3) |
|
||||
where {value == 2}
|
||||
where {$value == 2}
|
||||
|
||||
# Check that newline is ignored if preceded by backslash
|
||||
(list:of 1 2 3)\
|
||||
| where {value == 3}
|
||||
| where {$value == 3}
|
||||
|
||||
# Check that newline is ignored if preceded by backslash in job
|
||||
(list:of 1\
|
||||
2 3 4) | where {value == 4}
|
||||
2 3 4) | where {$value == 4}
|
||||
|
@ -1,7 +1,7 @@
|
||||
age := (lines:from example_data/age.csv|materialize)
|
||||
home := (lines:from example_data/home.csv|materialize)
|
||||
$age := (lines:from ./example_data/age.csv|materialize)
|
||||
$home := (lines:from ./example_data/home.csv|materialize)
|
||||
|
||||
zip (lines:from example_data/age.csv) (lines:from example_data/home.csv)
|
||||
zip age home | head 1
|
||||
zip (lines:from example_data/age.csv) home | head 1
|
||||
zip age (lines:from example_data/home.csv) | head 1
|
||||
zip (lines:from ./example_data/age.csv) (lines:from ./example_data/home.csv)
|
||||
zip $age $home | head 1
|
||||
zip (lines:from ./example_data/age.csv) $home | head 1
|
||||
zip $age (lines:from ./example_data/home.csv) | head 1
|
||||
|
Loading…
Reference in New Issue
Block a user