mirror of
https://github.com/liljencrantz/crush.git
synced 2024-10-03 22:07:10 +03:00
Compare commits
10 Commits
21c5395679
...
e177a02fd5
Author | SHA1 | Date | |
---|---|---|---|
|
e177a02fd5 | ||
|
8a94efe6b6 | ||
|
60fd099edc | ||
|
cd2710c8da | ||
|
14383e2d5d | ||
|
8830644a66 | ||
|
8d96142ac9 | ||
|
ecba86aaac | ||
|
2997cfb062 | ||
|
11e09d09c5 |
1210
Cargo.lock
generated
1210
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -56,6 +56,7 @@ psutil = "3.2.2"
|
|||||||
num-format = { version = "0.4", features = ["with-system-locale"] }
|
num-format = { version = "0.4", features = ["with-system-locale"] }
|
||||||
unicode-width = "0.1.5"
|
unicode-width = "0.1.5"
|
||||||
os_pipe = "0.9.2"
|
os_pipe = "0.9.2"
|
||||||
|
uptime_lib = "0.2.2"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
dbus = "0.8.4"
|
dbus = "0.8.4"
|
||||||
|
@ -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:
|
||||||
|
@ -13,8 +13,8 @@ what I felt was a more suitable type system.
|
|||||||
|
|
||||||
## Similarity to Nushell
|
## Similarity to Nushell
|
||||||
|
|
||||||
On the surface, Crush looks identical to nushell, but less polished. Crush lacks
|
On the surface, Crush looks identical to nushell, but less polished. Crush has
|
||||||
syntax highlighting, tab completion and has a worse screen rendering. But that
|
less polished syntax highlighting, tab completion and screen rendering. But that
|
||||||
is because the focus of Crush right now is to create a well defined, powerful
|
is because the focus of Crush right now is to create a well defined, powerful
|
||||||
and convenient language that supports things like arithmetic operations,
|
and convenient language that supports things like arithmetic operations,
|
||||||
closures, loops and flow control while remaining useful for interactive use.
|
closures, loops and flow control while remaining useful for interactive use.
|
||||||
|
105
docs/overview.md
105
docs/overview.md
@ -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,
|
||||||
@ -109,7 +109,7 @@ crush# list:of "carrot" "carrot" "acorn" | json:to
|
|||||||
|
|
||||||
One of the Crush serializers, Pup, is a native file format for Crush. The
|
One of the Crush serializers, Pup, is a native file format for Crush. The
|
||||||
Pup-format is protobuf-based, and its schema is available
|
Pup-format is protobuf-based, and its schema is available
|
||||||
[here](src/crush.proto). The advantage of Pup is that all crush types,
|
[here](../src/crush.proto). The advantage of Pup is that all crush types,
|
||||||
including classes and closures, can be losslessly serialized into this format.
|
including classes and closures, can be losslessly serialized into this format.
|
||||||
But because Pup is Crush-specific, it's useless for data sharing to
|
But because Pup is Crush-specific, it's useless for data sharing to
|
||||||
other languages.
|
other languages.
|
||||||
@ -142,7 +142,7 @@ are false.
|
|||||||
|
|
||||||
The `and` and `or` operators are used to combine logical expressions:
|
The `and` and `or` operators are used to combine logical expressions:
|
||||||
|
|
||||||
crush# false or true
|
crush# $false or $true
|
||||||
true
|
true
|
||||||
crush# if (./tree:exists) and ((./tree:stat):is_file) {echo "yay"}
|
crush# if (./tree:exists) and ((./tree:stat):is_file) {echo "yay"}
|
||||||
|
|
||||||
@ -183,16 +183,17 @@ current working directory as a single element of the `file` type.
|
|||||||
|
|
||||||
### Variables of any type
|
### Variables of any type
|
||||||
|
|
||||||
Variables must be declared (using the `:=` operator) before use.
|
Use the $ sigil to refer to variables. Variables must be declared
|
||||||
|
(using the `:=` operator) before use.
|
||||||
|
|
||||||
crush# some_number := 4 # The := operator declares a new variable
|
crush# $some_number := 4 # The := operator declares a new variable
|
||||||
crush# some_number * 5
|
crush# $some_number * 5
|
||||||
20
|
20
|
||||||
|
|
||||||
Once declared, a variable can be reassigned to using the `=` operator.
|
Once declared, a variable can be reassigned to using the `=` operator.
|
||||||
|
|
||||||
crush# some_number = 6
|
crush# $some_number = 6
|
||||||
crush# some_number * 5
|
crush# $some_number * 5
|
||||||
30
|
30
|
||||||
|
|
||||||
Like in any sane programming language, variables can be of any type supported by
|
Like in any sane programming language, variables can be of any type supported by
|
||||||
@ -218,13 +219,14 @@ the other or a combination of both. The following three invocations are equivale
|
|||||||
|
|
||||||
It is quite common to want to pass boolean arguments to commands, which is why
|
It is quite common to want to pass boolean arguments to commands, which is why
|
||||||
Crush has a special shorthand syntax for it. Passing in `--foo` is equivalent
|
Crush has a special shorthand syntax for it. Passing in `--foo` is equivalent
|
||||||
to passing in `foo=true`.
|
to passing in `foo=$true`.
|
||||||
|
|
||||||
### Subshells
|
### Subshells
|
||||||
|
|
||||||
Sometimes you want to use the output of one command as an *argument* to another
|
Sometimes you want to use the output of one command as an *argument* to another
|
||||||
command, just like a subshell in e.g. bash. This is different from using the
|
command, just like a subshell in e.g. bash. This is different from what a pipe does,
|
||||||
output as the *input*, and is done by putting the command within parenthesis (`()`), like so:
|
which is using the output as the *input*, and is done by putting the command within
|
||||||
|
parenthesis (`()`), like so:
|
||||||
|
|
||||||
crush# echo (pwd)
|
crush# echo (pwd)
|
||||||
|
|
||||||
@ -233,14 +235,14 @@ output as the *input*, and is done by putting the command within parenthesis (`(
|
|||||||
In Crush, braces (`{}`) are used to create a closure. Assigning a closure to a
|
In Crush, braces (`{}`) are used to create a closure. Assigning a closure to a
|
||||||
variable is how you create a function.
|
variable is how you create a function.
|
||||||
|
|
||||||
crush# print_greeting := {echo "Hello"}
|
crush# $print_greeting := {echo "Hello"}
|
||||||
crush# print_greeting
|
crush# print_greeting
|
||||||
Hello
|
Hello
|
||||||
|
|
||||||
Any named arguments passed when calling a closure and added to the local scope
|
Any named arguments passed when calling a closure and added to the local scope
|
||||||
of the invocation:
|
of the invocation:
|
||||||
|
|
||||||
crush# print_a := {echo a}
|
crush# $print_a := {echo $a}
|
||||||
crush# print_a a="Greetings"
|
crush# print_a a="Greetings"
|
||||||
Greetings
|
Greetings
|
||||||
|
|
||||||
@ -251,13 +253,13 @@ The following closure requires the caller to supply the argument `a`, and allows
|
|||||||
the caller to specify the argument `b`, which must by of type integer. If the
|
the caller to specify the argument `b`, which must by of type integer. If the
|
||||||
caller does not specify it, it falls back to a default value of 7.
|
caller does not specify it, it falls back to a default value of 7.
|
||||||
|
|
||||||
crush# print_things := {|a b: integer = 7|}
|
crush# print_things := {|a b: $integer = 7|}
|
||||||
|
|
||||||
Additionally, the `@` operator can be used to create a list of all unnamed
|
Additionally, the `@` operator can be used to create a list of all unnamed
|
||||||
arguments, and the `@@` operator can be used to create a list of all named
|
arguments, and the `@@` operator can be used to create a list of all named
|
||||||
arguments not mentioned elsewhere in the parameter list.
|
arguments not mentioned elsewhere in the parameter list.
|
||||||
|
|
||||||
crush# print_everything := {|@unnamed @@named| echo "Named" named "Unnamed" unnamed}
|
crush# print_everything := {|@unnamed @@named| echo "Named" $named "Unnamed" $unnamed}
|
||||||
|
|
||||||
The `@` and `@@` operators are also used during command invocation to perform
|
The `@` and `@@` operators are also used during command invocation to perform
|
||||||
the mirrored operation. The following code creates an `lss` function that calls
|
the mirrored operation. The following code creates an `lss` function that calls
|
||||||
@ -271,7 +273,7 @@ the `select` command to only show one column from the output.
|
|||||||
Crush comes with a variety of types:
|
Crush comes with a variety of types:
|
||||||
|
|
||||||
* lists of any type,
|
* lists of any type,
|
||||||
* dicts of any pair of types type, (Some types can not be used as keys!)
|
* dicts of a pair of types, (Some types can not be used as keys!)
|
||||||
* strings,
|
* strings,
|
||||||
* regular expressions,
|
* regular expressions,
|
||||||
* globs,
|
* globs,
|
||||||
@ -295,15 +297,15 @@ commands.
|
|||||||
When playing around with Crush, the `help` and `dir`commands are useful. The
|
When playing around with Crush, the `help` and `dir`commands are useful. The
|
||||||
former displays a help messages, the latter lists the content of a value.
|
former displays a help messages, the latter lists the content of a value.
|
||||||
|
|
||||||
crush# help
|
crush# help $sort
|
||||||
sort column:field
|
sort column:field
|
||||||
|
|
||||||
Sort input based on column
|
Sort input based on column
|
||||||
|
|
||||||
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]
|
||||||
|
|
||||||
### The content of your current working directory lives in your namespace
|
### The content of your current working directory lives in your namespace
|
||||||
@ -347,21 +349,21 @@ what you'd expect.
|
|||||||
|
|
||||||
If you assign the output of the find command to a variable like so:
|
If you assign the output of the find command to a variable like so:
|
||||||
|
|
||||||
crush# all_the_files := (find /)
|
crush# $all_the_files := (find /)
|
||||||
|
|
||||||
What will really be stored in the `all_the_files` variable is simply a stream. A
|
What will really be stored in the `all_the_files` variable is simply a stream. A
|
||||||
small number of lines of output will be eagerly evaluated, before the thread
|
small number of lines of output will be eagerly evaluated, before the thread
|
||||||
executing the find command will start blocking. If the stream is consumed, for
|
executing the find command will start blocking. If the stream is consumed, for
|
||||||
example by writing
|
example by writing
|
||||||
|
|
||||||
crush# all_the_files
|
crush# $all_the_files
|
||||||
|
|
||||||
then all hell will break loose on your screen as tens of thousands of lines are
|
then all hell will break loose on your screen as tens of thousands of lines are
|
||||||
printed to your screen.
|
printed to your screen.
|
||||||
|
|
||||||
Another option would be to pipe the output via the head command
|
Another option would be to pipe the output via the head command
|
||||||
|
|
||||||
crush# all_the_files | head 1
|
crush# $all_the_files | head 1
|
||||||
|
|
||||||
Which will consume one line of output from the stream. This command can be
|
Which will consume one line of output from the stream. This command can be
|
||||||
re-executed until the stream is empty.
|
re-executed until the stream is empty.
|
||||||
@ -369,10 +371,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
|
||||||
@ -395,7 +397,7 @@ operator instead. `?` is still used for single character wildcards.
|
|||||||
|
|
||||||
The operator `%%` is used for performing globbing recursively into subdirectories.
|
The operator `%%` is used for performing globbing recursively into subdirectories.
|
||||||
Another way of looking ath the same syntax is to say that `%` and `?` match any
|
Another way of looking ath the same syntax is to say that `%` and `?` match any
|
||||||
character except `/`, whereas `%%` also matches `/`.
|
character except `/`, whereas `%%` matches any character including `/`.
|
||||||
|
|
||||||
# Count the number of lines of rust code in the crush source code
|
# Count the number of lines of rust code in the crush source code
|
||||||
crush# lines src/%%.rs|count
|
crush# lines src/%%.rs|count
|
||||||
@ -506,8 +508,8 @@ computers memory, and even infinite data sets.
|
|||||||
But sometimes, streaming data sets are inconvenient, especially if one wants to
|
But sometimes, streaming data sets are inconvenient, especially if one wants to
|
||||||
use the same dataset twice.
|
use the same dataset twice.
|
||||||
|
|
||||||
crush# files := ls
|
crush# $files := ls
|
||||||
crush# files
|
crush# $files
|
||||||
user size modified type file
|
user size modified type file
|
||||||
fox 1_307 2020-03-26 01:08:45 +0100 file ideas
|
fox 1_307 2020-03-26 01:08:45 +0100 file ideas
|
||||||
fox 4_096 2019-11-22 21:56:30 +0100 directory target
|
fox 4_096 2019-11-22 21:56:30 +0100 directory target
|
||||||
@ -522,17 +524,17 @@ use the same dataset twice.
|
|||||||
fox 8_382 2020-03-29 00:54:13 +0100 file todo
|
fox 8_382 2020-03-29 00:54:13 +0100 file todo
|
||||||
fox 75 2020-03-07 17:09:15 +0100 file build.rs
|
fox 75 2020-03-07 17:09:15 +0100 file build.rs
|
||||||
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# files
|
crush# $files
|
||||||
|
|
||||||
Notice how there is no output the second time the content of the `files` variable is
|
Notice how there is no output the second time the content of the `files` variable is
|
||||||
displayed, because the table_input_stream has already been consumed.
|
displayed, because the table_input_stream has already been consumed.
|
||||||
|
|
||||||
Enter the materialize command, which takes any value and recursively converts
|
Enter the materialize command, which takes any value and recursively converts
|
||||||
all transient (table_input_stream and binary_stream) components into an equivalent
|
all transient (table_input_stream and binary_stream) components into an equivalent
|
||||||
in-memory form (stable, and binary, respectively).
|
in-memory form (table, and binary, respectively).
|
||||||
|
|
||||||
crush# materialized_files := (ls|materialize)
|
crush# $materialized_files := (ls|materialize)
|
||||||
crush# materialized_files
|
crush# $materialized_files
|
||||||
user size modified type file
|
user size modified type file
|
||||||
fox 1307 2020-03-26 01:08:45 +0100 file ideas
|
fox 1307 2020-03-26 01:08:45 +0100 file ideas
|
||||||
fox 4096 2019-11-22 21:56:30 +0100 directory target
|
fox 4096 2019-11-22 21:56:30 +0100 directory target
|
||||||
@ -547,7 +549,7 @@ in-memory form (stable, and binary, respectively).
|
|||||||
fox 8_382 2020-03-29 00:54:13 +0100 file todo
|
fox 8_382 2020-03-29 00:54:13 +0100 file todo
|
||||||
fox 75 2020-03-07 17:09:15 +0100 file build.rs
|
fox 75 2020-03-07 17:09:15 +0100 file build.rs
|
||||||
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# materialized_files
|
crush# $materialized_files
|
||||||
user size modified type file
|
user size modified type file
|
||||||
fox 1307 2020-03-26 01:08:45 +0100 file ideas
|
fox 1307 2020-03-26 01:08:45 +0100 file ideas
|
||||||
fox 4096 2019-11-22 21:56:30 +0100 directory target
|
fox 4096 2019-11-22 21:56:30 +0100 directory target
|
||||||
@ -563,7 +565,7 @@ in-memory form (stable, and binary, respectively).
|
|||||||
fox 75 2020-03-07 17:09:15 +0100 file build.rs
|
fox 75 2020-03-07 17:09:15 +0100 file build.rs
|
||||||
fox 711 2019-10-03 14:19:46 +0200 file crush.iml
|
fox 711 2019-10-03 14:19:46 +0200 file crush.iml
|
||||||
|
|
||||||
When the `table_input_stream` is materialized into a `table`, it can be displayed
|
When the `table_input_stream` is materialized into a `table`, it can be accessed
|
||||||
multiple times.
|
multiple times.
|
||||||
|
|
||||||
### Flow control
|
### Flow control
|
||||||
@ -612,13 +614,10 @@ non-interactive commands work as expected:
|
|||||||
|
|
||||||
Crush features several shortcuts to make working with external commands easier.
|
Crush features several shortcuts to make working with external commands easier.
|
||||||
|
|
||||||
* Firstly, subcommands like `git status` are mapped into method calls like
|
* Named arguments are transparently translated into options. Single
|
||||||
`git:status`. That way you do not have to quote the subcommand name, e.g.
|
|
||||||
`git "status"`.
|
|
||||||
* Secondly, named arguments are transparently translated into options. Single
|
|
||||||
character argument names are turned into options with a single hyphen, and
|
character argument names are turned into options with a single hyphen, and
|
||||||
multi-character argument names are turned into GNU style long options with
|
multi-character argument names are turned into GNU style long options with
|
||||||
two hyphens, e.g. `git:commit m="hello"` is converted into
|
two hyphens, e.g. `git commit m="hello"` is converted into
|
||||||
`git commit -m "hello"` and `git:commit message="hello"` is converted into
|
`git commit -m "hello"` and `git:commit message="hello"` is converted into
|
||||||
`git commit --message "hello"`.
|
`git commit --message "hello"`.
|
||||||
* Thirdly, named arguments with a value of boolean true are simply turned into
|
* Thirdly, named arguments with a value of boolean true are simply turned into
|
||||||
@ -661,26 +660,26 @@ To run a closure on multiple remote hosts, use `remote:pexec` instead.
|
|||||||
|
|
||||||
You can create custom types in Crush, by using the class command:
|
You can create custom types in Crush, by using the class command:
|
||||||
|
|
||||||
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__ = {
|
||||||
|other|
|
|$other|
|
||||||
Point:new x=(this:x + other:x) y=(this:y + other:y)
|
Point:new x=($this:x + $other:x) y=($this:y + $other:y)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := (Point:new x=1.0 y=2.0)
|
$p := (Point:new x=1.0 y=2.0)
|
||||||
p:len
|
$p:len
|
||||||
|
|
||||||
Crush supports single inheritance (by passing in the parent to the class
|
Crush supports single inheritance (by passing in the parent to the class
|
||||||
command). The class command will create a new struct, that contains a method
|
command). The class command will create a new struct, that contains a method
|
||||||
|
@ -37,7 +37,7 @@ input and produce a single value as output. The input and output of a command
|
|||||||
is passed down via a so called pipeline:
|
is passed down via a so called pipeline:
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
ps | sort ^cpu
|
ps | sort cpu
|
||||||
```
|
```
|
||||||
|
|
||||||
Many commands consume and produce table streams as input and output. These commands
|
Many commands consume and produce table streams as input and output. These commands
|
||||||
@ -54,18 +54,25 @@ A character sequence enclosed within double quotes become a string literal value
|
|||||||
e.g. `"hello"`.
|
e.g. `"hello"`.
|
||||||
|
|
||||||
A character sequence enclosed within single quotes become a file literal, e.g.
|
A character sequence enclosed within single quotes become a file literal, e.g.
|
||||||
`'Cargo.yaml'`, i.e. a value of the type `file`. A character sequence that begins
|
`'Cargo.yaml'`, i.e. a value of the type `file`. An unquoted character sequence
|
||||||
with a dot (`.`), a tilde (`~`) or slash (`/`) is also interpreted as a file literal, e.g.
|
that contains a dot (`.`) or a slash ('/'), or begins with a tilde (`~`) is also
|
||||||
`./Cargo.yam` or `/etc`.
|
interpreted as a file literal, e.g. `Cargo.yaml` or `~/.ssh`.
|
||||||
|
|
||||||
A character sequence starting with a caret (`^`) becomes a field literal, e.g.
|
A character sequence starting with a caret (`$`) is intepreted as a variable
|
||||||
`^user`. Field literals are used by some Crush commands to specify specific columns
|
lookup. The first sequewnce in a command (i.e. the command name) is interpreted
|
||||||
in a stream. For example, to tell the sort command to sort the input stream by the
|
as a variable lookup even without the leading `$`. Commands live in
|
||||||
column `user`, one would write `sort ^user`.
|
|
||||||
|
|
||||||
Other character sequences are interpreted as a variable lookup. Commands live in
|
|
||||||
the same namespace as all other variables.
|
the same namespace as all other variables.
|
||||||
|
|
||||||
|
All other unquoted character sequences are field literals, e.g.
|
||||||
|
`user` or `hat`. Field literals are basically short strings. They
|
||||||
|
are used by some Crush commands to specify specific columns
|
||||||
|
in a stream. For example, to tell the sort command to sort the
|
||||||
|
input stream by the column `user`, one would write `sort user`.
|
||||||
|
Commands that expect files as argument, like `cd` or `find`,
|
||||||
|
generally accept fields as files. They also accept globs and
|
||||||
|
regular expression literals (see below), and expand them into
|
||||||
|
file names.
|
||||||
|
|
||||||
## Operators
|
## Operators
|
||||||
|
|
||||||
Crush features a number of operators to enable users to write mathematical
|
Crush features a number of operators to enable users to write mathematical
|
||||||
@ -95,8 +102,9 @@ command. To do this, simply put the command within parenthesis:
|
|||||||
if the command that you run in a substitution returns a stream, the outer command will
|
if the command that you run in a substitution returns a stream, the outer command will
|
||||||
be run in parallel with the substitution and may in fact finish running first.
|
be run in parallel with the substitution and may in fact finish running first.
|
||||||
|
|
||||||
This example will create a command that locates all the files in your computer, and assign
|
This example will create a command that locates all the files in your computer, and assigns
|
||||||
the output stream to a variable.
|
the output stream to a variable. The `find` command in this example will block because there
|
||||||
|
is nothing reading its output.
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
all_the_files := (find /)
|
all_the_files := (find /)
|
||||||
@ -113,7 +121,8 @@ A few of the namespaces in Crush are:
|
|||||||
|
|
||||||
* the `crush` namespace, which contains runtime information about the current shell
|
* the `crush` namespace, which contains runtime information about the current shell
|
||||||
as well as methods and variables that allow you to reconfigure it,
|
as well as methods and variables that allow you to reconfigure it,
|
||||||
* the `fd` namespace, which contains information about file descriptors, e.g. all
|
* the `user` namespace which contains information about all the users of this system,
|
||||||
open network sockets, unix sockets and open files.
|
* the `fd` namespace, which contains information about open file descriptors, e.g. all
|
||||||
|
open network sockets, unix sockets and open files, and
|
||||||
* the `host` namespace, which contains information about the current host, including
|
* the `host` namespace, which contains information about the current host, including
|
||||||
host name, CPU status, memory usage and operating system.
|
host name, CPU status, memory usage and operating system.
|
||||||
|
@ -79,7 +79,7 @@ fn extract_type(ty: &Type) -> SignatureResult<(&'static str, Vec<&'static str>)>
|
|||||||
"Command" => "Command",
|
"Command" => "Command",
|
||||||
"Duration" => "Duration",
|
"Duration" => "Duration",
|
||||||
"Struct" => "Struct",
|
"Struct" => "Struct",
|
||||||
"Field" => "Field",
|
"Symbol" => "Symbol",
|
||||||
"Value" => "Value",
|
"Value" => "Value",
|
||||||
"Stream" => "Stream",
|
"Stream" => "Stream",
|
||||||
"Number" => "Number",
|
"Number" => "Number",
|
||||||
@ -169,7 +169,7 @@ fn simple_type_to_value(simple_type: &str) -> TokenStream {
|
|||||||
"char" => quote! {crate::lang::value::Value::String(_value)},
|
"char" => quote! {crate::lang::value::Value::String(_value)},
|
||||||
"Command" => quote! {crate::lang::value::Value::Command(_value)},
|
"Command" => quote! {crate::lang::value::Value::Command(_value)},
|
||||||
"Duration" => quote! {crate::lang::value::Value::Duration(_value)},
|
"Duration" => quote! {crate::lang::value::Value::Duration(_value)},
|
||||||
"Field" => quote! {crate::lang::value::Value::Field(_value)},
|
"Symbol" => quote! {crate::lang::value::Value::Symbol(_value)},
|
||||||
"Struct" => quote! {crate::lang::value::Value::Struct(_value)},
|
"Struct" => quote! {crate::lang::value::Value::Struct(_value)},
|
||||||
"Stream" => quote! {_value},
|
"Stream" => quote! {_value},
|
||||||
"Value" => quote! {_value},
|
"Value" => quote! {_value},
|
||||||
@ -192,7 +192,7 @@ fn simple_type_to_value_type(simple_type: &str) -> TokenStream {
|
|||||||
"char" => quote! {crate::lang::value::ValueType::String},
|
"char" => quote! {crate::lang::value::ValueType::String},
|
||||||
"Command" => quote! {crate::lang::value::ValueType::Command},
|
"Command" => quote! {crate::lang::value::ValueType::Command},
|
||||||
"Duration" => quote! {crate::lang::value::ValueType::Duration},
|
"Duration" => quote! {crate::lang::value::ValueType::Duration},
|
||||||
"Field" => quote! {crate::lang::value::ValueType::Field},
|
"Symbol" => quote! {crate::lang::value::ValueType::Symbol},
|
||||||
"Struct" => quote! {crate::lang::value::ValueType::Struct},
|
"Struct" => quote! {crate::lang::value::ValueType::Struct},
|
||||||
_ => quote! {crate::lang::value::ValueType::Any},
|
_ => quote! {crate::lang::value::ValueType::Any},
|
||||||
}
|
}
|
||||||
@ -213,7 +213,7 @@ fn simple_type_to_value_description(simple_type: &str) -> &str {
|
|||||||
"char" => "string",
|
"char" => "string",
|
||||||
"Command" => "command",
|
"Command" => "command",
|
||||||
"Duration" => "duration",
|
"Duration" => "duration",
|
||||||
"Field" => "field",
|
"Symbol" => "symbol",
|
||||||
"Value" => "any value",
|
"Value" => "any value",
|
||||||
"Stream" => "stream",
|
"Stream" => "stream",
|
||||||
"Struct" => "Struct",
|
"Struct" => "Struct",
|
||||||
@ -241,7 +241,7 @@ fn simple_type_to_mutator(simple_type: &str, allowed_values: &Option<Ident>) ->
|
|||||||
"Stream" => {
|
"Stream" => {
|
||||||
quote! {
|
quote! {
|
||||||
crate::lang::errors::mandate_argument(
|
crate::lang::errors::mandate_argument(
|
||||||
_value.stream(),
|
_value.stream()?,
|
||||||
"Expected a type that can be streamed",
|
"Expected a type that can be streamed",
|
||||||
_location)?,
|
_location)?,
|
||||||
}
|
}
|
||||||
@ -299,7 +299,7 @@ fn simple_type_dump_list(simple_type: &str) -> &str {
|
|||||||
"ValueType" => "dump_type",
|
"ValueType" => "dump_type",
|
||||||
"f64" => "dump_float",
|
"f64" => "dump_float",
|
||||||
"Value" => "dump_value",
|
"Value" => "dump_value",
|
||||||
"Field" => "dump_field",
|
"Symbol" => "dump_symbol",
|
||||||
_ => panic!("Unknown type"),
|
_ => panic!("Unknown type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -320,7 +320,7 @@ fn parse_type_data(
|
|||||||
let (type_name, args) = extract_type(ty)?;
|
let (type_name, args) = extract_type(ty)?;
|
||||||
match type_name {
|
match type_name {
|
||||||
"i128" | "bool" | "String" | "char" | "ValueType" | "f64" | "Command" | "Duration"
|
"i128" | "bool" | "String" | "char" | "ValueType" | "f64" | "Command" | "Duration"
|
||||||
| "Field" | "Value" | "usize" | "i64" | "u64" | "i32" | "u32" | "Stream" | "Struct" => {
|
| "Symbol" | "Value" | "usize" | "i64" | "u64" | "i32" | "u32" | "Stream" | "Struct" => {
|
||||||
if !args.is_empty() {
|
if !args.is_empty() {
|
||||||
fail!(ty.span(), "This type can't be paramterizised")
|
fail!(ty.span(), "This type can't be paramterizised")
|
||||||
} else {
|
} else {
|
||||||
|
@ -24,7 +24,7 @@ message Element {
|
|||||||
Closure closure = 9; // A Value::Command that is a closure
|
Closure closure = 9; // A Value::Command that is a closure
|
||||||
bytes binary = 10; // A Value::binary
|
bytes binary = 10; // A Value::binary
|
||||||
Duration duration = 11; // A Value::Duration
|
Duration duration = 11; // A Value::Duration
|
||||||
uint64 field = 12; // A Value::Field
|
string symbol = 12; // A Value::Symbol
|
||||||
string glob = 13; // A Value::Glob
|
string glob = 13; // A Value::Glob
|
||||||
string regex = 14; // A Value::RegEx
|
string regex = 14; // A Value::RegEx
|
||||||
Scope user_scope = 15; // A Value::Scope
|
Scope user_scope = 15; // A Value::Scope
|
||||||
@ -204,7 +204,7 @@ message Type {
|
|||||||
COMMAND = 4;
|
COMMAND = 4;
|
||||||
BINARY = 5;
|
BINARY = 5;
|
||||||
DURATION = 6;
|
DURATION = 6;
|
||||||
FIELD = 7;
|
SYMBOL = 7;
|
||||||
GLOB = 8;
|
GLOB = 8;
|
||||||
REGEX = 9;
|
REGEX = 9;
|
||||||
SCOPE = 10;
|
SCOPE = 10;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
use crate::lang::errors::{argument_error_legacy, error, CrushResult, argument_error};
|
/**
|
||||||
|
Code for managing arguments passed in to commands
|
||||||
|
*/
|
||||||
|
use crate::lang::errors::{argument_error, argument_error_legacy, CrushResult, error};
|
||||||
use crate::lang::execution_context::CompileContext;
|
use crate::lang::execution_context::CompileContext;
|
||||||
use crate::lang::value::Value;
|
use crate::lang::value::Value;
|
||||||
use crate::lang::value::ValueDefinition;
|
use crate::lang::value::ValueDefinition;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use crate::lang::ast::{TrackedString, Location};
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::lang::ast::tracked_string::TrackedString;
|
||||||
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ArgumentType {
|
pub enum ArgumentType {
|
||||||
@ -112,35 +117,35 @@ impl Argument {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ArgumentVecCompiler {
|
pub trait ArgumentEvaluator {
|
||||||
fn compile(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)>;
|
fn eval(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ArgumentVecCompiler for Vec<ArgumentDefinition> {
|
impl ArgumentEvaluator for Vec<ArgumentDefinition> {
|
||||||
fn compile(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)> {
|
fn eval(&self, context: &mut CompileContext) -> CrushResult<(Vec<Argument>, Option<Value>)> {
|
||||||
let mut this = None;
|
let mut this = None;
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
for a in self {
|
for a in self {
|
||||||
if a.argument_type.is_this() {
|
if a.argument_type.is_this() {
|
||||||
this = Some(a.value.compile_bound(context)?);
|
this = Some(a.value.eval_and_bind(context)?);
|
||||||
} else {
|
} else {
|
||||||
match &a.argument_type {
|
match &a.argument_type {
|
||||||
ArgumentType::Some(name) => {
|
ArgumentType::Some(name) => {
|
||||||
res.push(Argument::named(
|
res.push(Argument::named(
|
||||||
&name.string,
|
&name.string,
|
||||||
a.value.compile_bound(context)?,
|
a.value.eval_and_bind(context)?,
|
||||||
a.location,
|
a.location,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentType::None => {
|
ArgumentType::None => {
|
||||||
res.push(Argument::unnamed(
|
res.push(Argument::unnamed(
|
||||||
a.value.compile_bound(context)?,
|
a.value.eval_and_bind(context)?,
|
||||||
a.location,
|
a.location,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
ArgumentType::ArgumentList => match a.value.compile_bound(context)? {
|
ArgumentType::ArgumentList => match a.value.eval_and_bind(context)? {
|
||||||
Value::List(l) => {
|
Value::List(l) => {
|
||||||
let mut copy = l.dump();
|
let mut copy = l.dump();
|
||||||
for v in copy.drain(..) {
|
for v in copy.drain(..) {
|
||||||
@ -153,7 +158,7 @@ impl ArgumentVecCompiler for Vec<ArgumentDefinition> {
|
|||||||
_ => return argument_error_legacy("Argument list must be of type list"),
|
_ => return argument_error_legacy("Argument list must be of type list"),
|
||||||
},
|
},
|
||||||
|
|
||||||
ArgumentType::ArgumentDict => match a.value.compile_bound(context)? {
|
ArgumentType::ArgumentDict => match a.value.eval_and_bind(context)? {
|
||||||
Value::Dict(d) => {
|
Value::Dict(d) => {
|
||||||
let mut copy = d.elements();
|
let mut copy = d.elements();
|
||||||
for (key, value) in copy.drain(..) {
|
for (key, value) in copy.drain(..) {
|
||||||
@ -177,6 +182,13 @@ impl ArgumentVecCompiler for Vec<ArgumentDefinition> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ArgumentDefinition {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.value.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn column_names(arguments: &Vec<Argument>) -> Vec<String> {
|
pub fn column_names(arguments: &Vec<Argument>) -> Vec<String> {
|
||||||
let mut taken = HashSet::new();
|
let mut taken = HashSet::new();
|
||||||
taken.insert("_".to_string());
|
taken.insert("_".to_string());
|
||||||
|
28
src/lang/ast/location.rs
Normal file
28
src/lang/ast/location.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Location {
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Location {
|
||||||
|
pub fn new(start: usize, end: usize) -> Location {
|
||||||
|
Location { start, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn union(&self, other: Location) -> Location {
|
||||||
|
Location {
|
||||||
|
start: min(self.start, other.start),
|
||||||
|
end: max(self.end, other.end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, cursor: usize) -> bool {
|
||||||
|
cursor >= self.start && cursor <= self.end
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
use crate::lang::argument::ArgumentDefinition;
|
use crate::lang::argument::ArgumentDefinition;
|
||||||
use crate::lang::command::{Command, Parameter};
|
use crate::lang::command::{Command, Parameter};
|
||||||
use crate::lang::command_invocation::CommandInvocation;
|
use crate::lang::command_invocation::CommandInvocation;
|
||||||
use crate::lang::errors::{error, to_crush_error, CrushResult};
|
use crate::lang::errors::{CrushResult, error, to_crush_error};
|
||||||
use crate::lang::job::Job;
|
use crate::lang::job::Job;
|
||||||
use crate::lang::data::scope::Scope;
|
use crate::lang::data::scope::Scope;
|
||||||
use crate::lang::value::{Value, ValueDefinition, ValueType};
|
use crate::lang::value::{Value, ValueDefinition, ValueType};
|
||||||
@ -10,9 +10,36 @@ use regex::Regex;
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::cmp::{min, max};
|
use std::cmp::{max, min};
|
||||||
|
use location::Location;
|
||||||
|
use tracked_string::TrackedString;
|
||||||
use crate::util::escape::unescape;
|
use crate::util::escape::unescape;
|
||||||
|
|
||||||
|
pub mod location;
|
||||||
|
pub mod tracked_string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A type representing a node in the abstract syntax tree that is the output of parsing a Crush script.
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Node {
|
||||||
|
Assignment(Box<Node>, String, Box<Node>),
|
||||||
|
Unary(TrackedString, Box<Node>),
|
||||||
|
Glob(TrackedString),
|
||||||
|
Identifier(TrackedString),
|
||||||
|
Regex(TrackedString),
|
||||||
|
Field(TrackedString),
|
||||||
|
String(TrackedString),
|
||||||
|
File(TrackedString, bool), // true if filename is quoted
|
||||||
|
Integer(TrackedString),
|
||||||
|
Float(TrackedString),
|
||||||
|
GetItem(Box<Node>, Box<Node>),
|
||||||
|
GetAttr(Box<Node>, TrackedString),
|
||||||
|
Path(Box<Node>, TrackedString),
|
||||||
|
Substitution(JobNode),
|
||||||
|
Closure(Option<Vec<ParameterNode>>, JobListNode),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct JobListNode {
|
pub struct JobListNode {
|
||||||
pub jobs: Vec<JobNode>,
|
pub jobs: Vec<JobNode>,
|
||||||
@ -166,7 +193,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))
|
||||||
@ -176,99 +203,6 @@ impl CommandNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct TrackedString {
|
|
||||||
pub string: String,
|
|
||||||
pub location: Location,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TrackedString {
|
|
||||||
pub fn from(string: &str, location: Location) -> TrackedString {
|
|
||||||
TrackedString {
|
|
||||||
string: string.to_string(),
|
|
||||||
location,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn literal(start: usize, string: &str, end: usize) -> TrackedString {
|
|
||||||
TrackedString {
|
|
||||||
string: string.to_string(),
|
|
||||||
location: Location::new(start, end),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prefix(&self, pos: usize) -> TrackedString {
|
|
||||||
if !self.location.contains(pos) {
|
|
||||||
if self.location.start > pos {
|
|
||||||
TrackedString {
|
|
||||||
string: "".to_string(),
|
|
||||||
location: Location::new(self.location.start, self.location.start),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let len = pos - self.location.start;
|
|
||||||
TrackedString {
|
|
||||||
string: self.string[0..len].to_string(),
|
|
||||||
location: Location::new(self.location.start, self.location.start + len),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for TrackedString {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.write_str(&self.string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Copy, PartialEq, Eq)]
|
|
||||||
pub struct Location {
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Location {
|
|
||||||
pub fn new(start: usize, end: usize) -> Location {
|
|
||||||
Location { start, end }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union(&self, other: Location) -> Location {
|
|
||||||
Location {
|
|
||||||
start: min(self.start, other.start),
|
|
||||||
end: max(self.end, other.end),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, cursor: usize) -> bool {
|
|
||||||
cursor >= self.start && cursor <= self.end
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.end - self.start
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum Node {
|
|
||||||
Assignment(Box<Node>, String, Box<Node>),
|
|
||||||
Unary(TrackedString, Box<Node>),
|
|
||||||
Glob(TrackedString),
|
|
||||||
Label(TrackedString),
|
|
||||||
Regex(TrackedString),
|
|
||||||
Field(TrackedString),
|
|
||||||
String(TrackedString),
|
|
||||||
File(TrackedString, bool),
|
|
||||||
Integer(TrackedString),
|
|
||||||
Float(TrackedString),
|
|
||||||
GetItem(Box<Node>, Box<Node>),
|
|
||||||
GetAttr(Box<Node>, TrackedString),
|
|
||||||
Path(Box<Node>, TrackedString),
|
|
||||||
Substitution(JobNode),
|
|
||||||
Closure(Option<Vec<ParameterNode>>, JobListNode),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn propose_name(name: &TrackedString, v: ValueDefinition) -> ValueDefinition {
|
fn propose_name(name: &TrackedString, v: ValueDefinition) -> ValueDefinition {
|
||||||
match v {
|
match v {
|
||||||
ValueDefinition::ClosureDefinition(_, p, j, l) =>
|
ValueDefinition::ClosureDefinition(_, p, j, l) =>
|
||||||
@ -280,7 +214,7 @@ fn propose_name(name: &TrackedString, v: ValueDefinition) -> ValueDefinition {
|
|||||||
impl Node {
|
impl Node {
|
||||||
pub fn prefix(&self, pos: usize) -> CrushResult<Node> {
|
pub fn prefix(&self, pos: usize) -> CrushResult<Node> {
|
||||||
match self {
|
match self {
|
||||||
Node::Label(s) => Ok(Node::Label(s.prefix(pos))),
|
Node::Identifier(s) => Ok(Node::Identifier(s.prefix(pos))),
|
||||||
_ => Ok(self.clone()),
|
_ => Ok(self.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -289,7 +223,7 @@ impl Node {
|
|||||||
use Node::*;
|
use Node::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Glob(s) | Label(s) | Field(s) |
|
Glob(s) | Identifier(s) | Field(s) |
|
||||||
String(s) | Integer(s) | Float(s) |
|
String(s) | Integer(s) | Float(s) |
|
||||||
Regex(s) | File(s, _) =>
|
Regex(s) | File(s, _) =>
|
||||||
s.location,
|
s.location,
|
||||||
@ -311,16 +245,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::Identifier(_) => "identifier",
|
||||||
|
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"),
|
||||||
@ -346,7 +308,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
_ => return error("Unknown operator"),
|
_ => return error("Unknown operator"),
|
||||||
},
|
},
|
||||||
Node::Label(l) => ValueDefinition::Label(l.clone()),
|
Node::Identifier(l) => ValueDefinition::Identifier(l.clone()),
|
||||||
Node::Regex(l) => ValueDefinition::Value(
|
Node::Regex(l) => ValueDefinition::Value(
|
||||||
Value::Regex(
|
Value::Regex(
|
||||||
l.string.clone(),
|
l.string.clone(),
|
||||||
@ -366,21 +328,19 @@ impl Node {
|
|||||||
s.string.replace("_", "").parse::<f64>()
|
s.string.replace("_", "").parse::<f64>()
|
||||||
)?),
|
)?),
|
||||||
s.location),
|
s.location),
|
||||||
Node::GetAttr(node, label) => {
|
Node::GetAttr(node, identifier) =>
|
||||||
let parent = node.generate_argument(env)?;
|
ValueDefinition::GetAttr(Box::new(node.generate(env, is_command)?.unnamed_value()?), identifier.clone()),
|
||||||
match parent.unnamed_value()? {
|
|
||||||
ValueDefinition::Value(Value::Field(mut f), location) => {
|
Node::Path(node, identifier) => ValueDefinition::Path(
|
||||||
f.push(label.string.clone());
|
|
||||||
ValueDefinition::Value(Value::Field(f), location)
|
|
||||||
}
|
|
||||||
value => ValueDefinition::GetAttr(Box::new(value), label.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node::Path(node, label) => ValueDefinition::Path(
|
|
||||||
Box::new(node.generate_argument(env)?.unnamed_value()?),
|
Box::new(node.generate_argument(env)?.unnamed_value()?),
|
||||||
label.clone(),
|
identifier.clone(),
|
||||||
),
|
),
|
||||||
Node::Field(f) => ValueDefinition::Value(Value::Field(vec![f.string[1..].to_string()]), f.location),
|
Node::Field(f) =>
|
||||||
|
if is_command {
|
||||||
|
ValueDefinition::Identifier(f.clone())
|
||||||
|
} else {
|
||||||
|
ValueDefinition::Value(Value::Symbol(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| {
|
||||||
@ -413,7 +373,7 @@ impl Node {
|
|||||||
) -> CrushResult<Option<CommandInvocation>> {
|
) -> CrushResult<Option<CommandInvocation>> {
|
||||||
match op.deref() {
|
match op.deref() {
|
||||||
"=" => match target.as_ref() {
|
"=" => match target.as_ref() {
|
||||||
Node::Label(t) => Node::function_invocation(
|
Node::Identifier(t) => Node::function_invocation(
|
||||||
env.global_static_cmd(vec!["global", "var", "set"])?,
|
env.global_static_cmd(vec!["global", "var", "set"])?,
|
||||||
t.location,
|
t.location,
|
||||||
vec![ArgumentDefinition::named(
|
vec![ArgumentDefinition::named(
|
||||||
@ -429,6 +389,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,12 +402,13 @@ 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"),
|
||||||
},
|
},
|
||||||
":=" => match target.as_ref() {
|
":=" => match target.as_ref() {
|
||||||
Node::Label(t) => Node::function_invocation(
|
Node::Identifier(t) => Node::function_invocation(
|
||||||
env.global_static_cmd(vec!["global", "var", "let"])?,
|
env.global_static_cmd(vec!["global", "var", "let"])?,
|
||||||
t.location,
|
t.location,
|
||||||
vec![ArgumentDefinition::named(
|
vec![ArgumentDefinition::named(
|
||||||
@ -467,7 +429,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() {
|
||||||
@ -476,7 +438,7 @@ impl Node {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Node::Glob(_)
|
Node::Glob(_)
|
||||||
| Node::Label(_)
|
| Node::Identifier(_)
|
||||||
| Node::Regex(_)
|
| Node::Regex(_)
|
||||||
| Node::Field(_)
|
| Node::Field(_)
|
||||||
| Node::String(_)
|
| Node::String(_)
|
||||||
@ -506,24 +468,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_identifier(s: &TrackedString) -> Box<Node> {
|
||||||
|
Box::from(Node::Identifier(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()))
|
||||||
@ -534,7 +503,7 @@ impl Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn path(parts: &[&str], location: Location) -> Node {
|
fn path(parts: &[&str], location: Location) -> Node {
|
||||||
let mut res = Node::Label(TrackedString::from(parts[0], location));
|
let mut res = Node::Identifier(TrackedString::from(parts[0], location));
|
||||||
for part in &parts[1..] {
|
for part in &parts[1..] {
|
||||||
res = Node::Path(Box::from(res), TrackedString::from(part, location));
|
res = Node::Path(Box::from(res), TrackedString::from(part, location));
|
||||||
}
|
}
|
||||||
@ -542,7 +511,7 @@ fn path(parts: &[&str], location: Location) -> Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn attr(parts: &[&str], location: Location) -> Node {
|
fn attr(parts: &[&str], location: Location) -> Node {
|
||||||
let mut res = Node::Label(TrackedString::from(parts[0], location));
|
let mut res = Node::Identifier(TrackedString::from(parts[0], location));
|
||||||
for part in &parts[1..] {
|
for part in &parts[1..] {
|
||||||
res = Node::GetAttr(Box::from(res), TrackedString::from(part, location));
|
res = Node::GetAttr(Box::from(res), TrackedString::from(part, location));
|
||||||
}
|
}
|
||||||
@ -584,7 +553,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 +629,8 @@ pub enum TokenType {
|
|||||||
FactorOperator,
|
FactorOperator,
|
||||||
TermOperator,
|
TermOperator,
|
||||||
QuotedString,
|
QuotedString,
|
||||||
LabelOrWildcard,
|
StringOrWildcard,
|
||||||
|
Identifier,
|
||||||
Flag,
|
Flag,
|
||||||
Field,
|
Field,
|
||||||
QuotedFile,
|
QuotedFile,
|
49
src/lang/ast/tracked_string.rs
Normal file
49
src/lang/ast/tracked_string.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct TrackedString {
|
||||||
|
pub string: String,
|
||||||
|
pub location: Location,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackedString {
|
||||||
|
pub fn from(string: &str, location: Location) -> TrackedString {
|
||||||
|
TrackedString {
|
||||||
|
string: string.to_string(),
|
||||||
|
location,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn literal(start: usize, string: &str, end: usize) -> TrackedString {
|
||||||
|
TrackedString {
|
||||||
|
string: string.to_string(),
|
||||||
|
location: Location::new(start, end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prefix(&self, pos: usize) -> TrackedString {
|
||||||
|
if !self.location.contains(pos) {
|
||||||
|
if self.location.start > pos {
|
||||||
|
TrackedString {
|
||||||
|
string: "".to_string(),
|
||||||
|
location: Location::new(self.location.start, self.location.start),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.clone()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let len = pos - self.location.start;
|
||||||
|
TrackedString {
|
||||||
|
string: self.string[0..len].to_string(),
|
||||||
|
location: Location::new(self.location.start, self.location.start + len),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TrackedString {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.string)
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
use crate::lang::argument::{Argument, ArgumentDefinition, ArgumentType};
|
use crate::lang::argument::{Argument, ArgumentDefinition, ArgumentType};
|
||||||
use crate::lang::command::{BoundCommand, Command, CrushCommand, OutputType, Parameter, ArgumentDescription};
|
use crate::lang::command::{ArgumentDescription, BoundCommand, Command, CrushCommand, OutputType, Parameter};
|
||||||
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, argument_error_legacy, CrushResult, error, mandate};
|
||||||
use crate::lang::execution_context::{CompileContext, CommandContext, JobContext};
|
use crate::lang::execution_context::{CommandContext, CompileContext, JobContext};
|
||||||
use crate::lang::help::Help;
|
use crate::lang::help::Help;
|
||||||
use crate::lang::job::Job;
|
use crate::lang::job::Job;
|
||||||
use crate::lang::data::list::List;
|
use crate::lang::data::list::List;
|
||||||
@ -16,7 +16,8 @@ use crate::lang::pipe::{black_hole, empty_channel};
|
|||||||
use crate::lang::value::{Value, ValueDefinition, ValueType};
|
use crate::lang::value::{Value, ValueDefinition, ValueType};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use crate::lang::ast::{TrackedString, Location};
|
use crate::lang::ast::tracked_string::TrackedString;
|
||||||
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
pub struct Closure {
|
pub struct Closure {
|
||||||
name: Option<TrackedString>,
|
name: Option<TrackedString>,
|
||||||
@ -29,7 +30,7 @@ pub struct Closure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CrushCommand for Closure {
|
impl CrushCommand for Closure {
|
||||||
fn invoke(&self, context: CommandContext) -> CrushResult<()> {
|
fn eval(&self, context: CommandContext) -> CrushResult<()> {
|
||||||
let job_definitions = self.job_definitions.clone();
|
let job_definitions = self.job_definitions.clone();
|
||||||
let parent_env = self.env.clone();
|
let parent_env = self.env.clone();
|
||||||
let env = parent_env.create_child(&context.scope, false);
|
let env = parent_env.create_child(&context.scope, false);
|
||||||
@ -58,7 +59,7 @@ impl CrushCommand for Closure {
|
|||||||
black_hole()
|
black_hole()
|
||||||
};
|
};
|
||||||
|
|
||||||
let job = job_definition.invoke(JobContext::new(
|
let job = job_definition.eval(JobContext::new(
|
||||||
input,
|
input,
|
||||||
output,
|
output,
|
||||||
env.clone(),
|
env.clone(),
|
||||||
@ -75,7 +76,7 @@ impl CrushCommand for Closure {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool {
|
fn might_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +115,7 @@ impl CrushCommand for Closure {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output(&self, _input: &OutputType) -> Option<&ValueType> {
|
fn output_type(&self, _input: &OutputType) -> Option<&ValueType> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +202,6 @@ impl<'a> ClosureSerializer<'a> {
|
|||||||
model::parameter::Parameter::Named(n.serialize(self.elements, self.state)? as u64),
|
model::parameter::Parameter::Named(n.serialize(self.elements, self.state)? as u64),
|
||||||
Parameter::Parameter(n, t, d) => {
|
Parameter::Parameter(n, t, d) => {
|
||||||
model::parameter::Parameter::Normal(model::NormalParameter {
|
model::parameter::Parameter::Normal(model::NormalParameter {
|
||||||
|
|
||||||
name: n.serialize(self.elements, self.state)? as u64,
|
name: n.serialize(self.elements, self.state)? as u64,
|
||||||
|
|
||||||
r#type: Some(self.value_definition(t)?),
|
r#type: Some(self.value_definition(t)?),
|
||||||
@ -303,7 +303,7 @@ impl<'a> ClosureSerializer<'a> {
|
|||||||
model::value_definition::ValueDefinition::Job(self.job(j)?)
|
model::value_definition::ValueDefinition::Job(self.job(j)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueDefinition::Label(l) => {
|
ValueDefinition::Identifier(l) => {
|
||||||
model::value_definition::ValueDefinition::Label(
|
model::value_definition::ValueDefinition::Label(
|
||||||
l.serialize(self.elements, self.state)? as u64)
|
l.serialize(self.elements, self.state)? as u64)
|
||||||
}
|
}
|
||||||
@ -485,7 +485,7 @@ impl<'a> ClosureDeserializer<'a> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
model::value_definition::ValueDefinition::Label(s) => {
|
model::value_definition::ValueDefinition::Label(s) => {
|
||||||
ValueDefinition::Label(TrackedString::deserialize(*s as usize, self.elements, self.state)?)
|
ValueDefinition::Identifier(TrackedString::deserialize(*s as usize, self.elements, self.state)?)
|
||||||
}
|
}
|
||||||
model::value_definition::ValueDefinition::GetAttr(a) => ValueDefinition::GetAttr(
|
model::value_definition::ValueDefinition::GetAttr(a) => ValueDefinition::GetAttr(
|
||||||
Box::from(self.value_definition(mandate(
|
Box::from(self.value_definition(mandate(
|
||||||
@ -531,13 +531,14 @@ impl Help for Closure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Extracts the help message from a closure definition */
|
||||||
fn extract_help(jobs: &mut Vec<Job>) -> String {
|
fn extract_help(jobs: &mut Vec<Job>) -> String {
|
||||||
if jobs.is_empty() {
|
if jobs.is_empty() {
|
||||||
return "".to_string();
|
return "".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let j = &jobs[0];
|
let j = &jobs[0];
|
||||||
match j.as_string() {
|
match j.extract_help_message() {
|
||||||
Some(help) => {
|
Some(help) => {
|
||||||
if jobs.len() > 1 {
|
if jobs.len() > 1 {
|
||||||
jobs.remove(0);
|
jobs.remove(0);
|
||||||
@ -570,80 +571,98 @@ impl Closure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn push_arguments_to_env_with_signature(
|
||||||
|
signature: &Vec<Parameter>,
|
||||||
|
mut arguments: Vec<Argument>,
|
||||||
|
context: &mut CompileContext,
|
||||||
|
) -> CrushResult<()> {
|
||||||
|
let mut named = HashMap::new();
|
||||||
|
let mut unnamed = Vec::new();
|
||||||
|
for arg in arguments.drain(..) {
|
||||||
|
match arg.argument_type {
|
||||||
|
Some(name) => {
|
||||||
|
named.insert(name.clone(), arg.value);
|
||||||
|
}
|
||||||
|
None => unnamed.push(arg.value),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let mut unnamed_name = None;
|
||||||
|
let mut named_name = None;
|
||||||
|
|
||||||
|
for param in signature {
|
||||||
|
match param {
|
||||||
|
Parameter::Parameter(name, value_type, default) => {
|
||||||
|
if let Value::Type(value_type) = value_type.eval_and_bind(context)? {
|
||||||
|
if named.contains_key(&name.string) {
|
||||||
|
let value = named.remove(&name.string).unwrap();
|
||||||
|
if !value_type.is(&value) {
|
||||||
|
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() {
|
||||||
|
context.env.redeclare(&name.string, unnamed.remove(0))?;
|
||||||
|
} else if let Some(default) = default {
|
||||||
|
let env = context.env.clone();
|
||||||
|
env.redeclare(&name.string, default.eval_and_bind(context)?)?;
|
||||||
|
} else {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Parameter::Named(name) => {
|
||||||
|
if named_name.is_some() {
|
||||||
|
return argument_error_legacy("Multiple named argument maps specified");
|
||||||
|
}
|
||||||
|
named_name = Some(name);
|
||||||
|
}
|
||||||
|
Parameter::Unnamed(name) => {
|
||||||
|
if unnamed_name.is_some() {
|
||||||
|
return argument_error_legacy("Multiple named argument maps specified");
|
||||||
|
}
|
||||||
|
unnamed_name = Some(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(unnamed_name) = unnamed_name {
|
||||||
|
context.env.redeclare(
|
||||||
|
unnamed_name.string.as_ref(),
|
||||||
|
Value::List(List::new(ValueType::Any, unnamed)),
|
||||||
|
)?;
|
||||||
|
} else if !unnamed.is_empty() {
|
||||||
|
return argument_error_legacy("No target for unnamed arguments");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(named_name) = named_name {
|
||||||
|
let d = Dict::new(ValueType::String, ValueType::Any);
|
||||||
|
for (k, v) in named {
|
||||||
|
d.insert(Value::string(&k), v)?;
|
||||||
|
}
|
||||||
|
context.env.redeclare(named_name.string.as_ref(), Value::Dict(d))?;
|
||||||
|
} else if !named.is_empty() {
|
||||||
|
return argument_error_legacy("No target for extra named arguments");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn push_arguments_to_env(
|
fn push_arguments_to_env(
|
||||||
signature: &Option<Vec<Parameter>>,
|
signature: &Option<Vec<Parameter>>,
|
||||||
mut arguments: Vec<Argument>,
|
mut arguments: Vec<Argument>,
|
||||||
context: &mut CompileContext,
|
context: &mut CompileContext,
|
||||||
) -> CrushResult<()> {
|
) -> CrushResult<()> {
|
||||||
if let Some(signature) = signature {
|
if let Some(signature) = signature {
|
||||||
let mut named = HashMap::new();
|
Self::push_arguments_to_env_with_signature(signature, arguments, context)
|
||||||
let mut unnamed = Vec::new();
|
|
||||||
for arg in arguments.drain(..) {
|
|
||||||
match arg.argument_type {
|
|
||||||
Some(name) => {
|
|
||||||
named.insert(name.clone(), arg.value);
|
|
||||||
}
|
|
||||||
None => unnamed.push(arg.value),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let mut unnamed_name = None;
|
|
||||||
let mut named_name = None;
|
|
||||||
|
|
||||||
for param in signature {
|
|
||||||
match param {
|
|
||||||
Parameter::Parameter(name, value_type, default) => {
|
|
||||||
if let Value::Type(value_type) = value_type.compile_bound(context)? {
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
context.env.redeclare(&name.string, value)?;
|
|
||||||
} else if !unnamed.is_empty() {
|
|
||||||
context.env.redeclare(&name.string, unnamed.remove(0))?;
|
|
||||||
} else if let Some(default) = default {
|
|
||||||
let env = context.env.clone();
|
|
||||||
env.redeclare(&name.string, default.compile_bound(context)?)?;
|
|
||||||
} else {
|
|
||||||
return argument_error_legacy("Missing variable!!!");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return argument_error_legacy("Not a type");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Parameter::Named(name) => {
|
|
||||||
if named_name.is_some() {
|
|
||||||
return argument_error_legacy("Multiple named argument maps specified");
|
|
||||||
}
|
|
||||||
named_name = Some(name);
|
|
||||||
}
|
|
||||||
Parameter::Unnamed(name) => {
|
|
||||||
if unnamed_name.is_some() {
|
|
||||||
return argument_error_legacy("Multiple named argument maps specified");
|
|
||||||
}
|
|
||||||
unnamed_name = Some(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(unnamed_name) = unnamed_name {
|
|
||||||
context.env.redeclare(
|
|
||||||
unnamed_name.string.as_ref(),
|
|
||||||
Value::List(List::new(ValueType::Any, unnamed)),
|
|
||||||
)?;
|
|
||||||
} else if !unnamed.is_empty() {
|
|
||||||
return argument_error_legacy("No target for unnamed arguments");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(named_name) = named_name {
|
|
||||||
let d = Dict::new(ValueType::String, ValueType::Any);
|
|
||||||
for (k, v) in named {
|
|
||||||
d.insert(Value::string(&k), v)?;
|
|
||||||
}
|
|
||||||
context.env.redeclare(named_name.string.as_ref(), Value::Dict(d))?;
|
|
||||||
} else if !named.is_empty() {
|
|
||||||
return argument_error_legacy("No target for extra named arguments");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
for arg in arguments.drain(..) {
|
for arg in arguments.drain(..) {
|
||||||
match arg.argument_type {
|
match arg.argument_type {
|
||||||
@ -655,8 +674,8 @@ impl Closure {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize(
|
pub fn deserialize(
|
||||||
|
@ -13,7 +13,7 @@ use crate::lang::value::{Value, ValueDefinition, ValueType};
|
|||||||
use closure::Closure;
|
use closure::Closure;
|
||||||
use ordered_map::OrderedMap;
|
use ordered_map::OrderedMap;
|
||||||
use std::fmt::{Formatter, Display};
|
use std::fmt::{Formatter, Display};
|
||||||
use crate::lang::ast::TrackedString;
|
use crate::lang::ast::tracked_string::TrackedString;
|
||||||
use crate::lang::completion::Completion;
|
use crate::lang::completion::Completion;
|
||||||
use crate::lang::completion::parse::PartialCommandResult;
|
use crate::lang::completion::parse::PartialCommandResult;
|
||||||
|
|
||||||
@ -62,8 +62,8 @@ pub struct ArgumentDescription {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait CrushCommand: Help {
|
pub trait CrushCommand: Help {
|
||||||
fn invoke(&self, context: CommandContext) -> CrushResult<()>;
|
fn eval(&self, context: CommandContext) -> CrushResult<()>;
|
||||||
fn can_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool;
|
fn might_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool;
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
fn copy(&self) -> Command;
|
fn copy(&self) -> Command;
|
||||||
fn help(&self) -> &dyn Help;
|
fn help(&self) -> &dyn Help;
|
||||||
@ -73,7 +73,7 @@ pub trait CrushCommand: Help {
|
|||||||
state: &mut SerializationState,
|
state: &mut SerializationState,
|
||||||
) -> CrushResult<usize>;
|
) -> CrushResult<usize>;
|
||||||
fn bind(&self, this: Value) -> Command;
|
fn bind(&self, this: Value) -> Command;
|
||||||
fn output<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType>;
|
fn output_type<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType>;
|
||||||
fn arguments(&self) -> &Vec<ArgumentDescription>;
|
fn arguments(&self) -> &Vec<ArgumentDescription>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,12 +220,12 @@ impl dyn CrushCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CrushCommand for SimpleCommand {
|
impl CrushCommand for SimpleCommand {
|
||||||
fn invoke(&self, context: CommandContext) -> CrushResult<()> {
|
fn eval(&self, context: CommandContext) -> CrushResult<()> {
|
||||||
let c = self.call;
|
let c = self.call;
|
||||||
c(context)
|
c(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool {
|
fn might_block(&self, _arg: &[ArgumentDefinition], _context: &mut CompileContext) -> bool {
|
||||||
self.can_block
|
self.can_block
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +270,7 @@ impl CrushCommand for SimpleCommand {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> {
|
fn output_type<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> {
|
||||||
self.output.calculate(input)
|
self.output.calculate(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +315,7 @@ impl std::fmt::Debug for SimpleCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CrushCommand for ConditionCommand {
|
impl CrushCommand for ConditionCommand {
|
||||||
fn invoke(&self, context: CommandContext) -> CrushResult<()> {
|
fn eval(&self, context: CommandContext) -> CrushResult<()> {
|
||||||
let c = self.call;
|
let c = self.call;
|
||||||
c(context)?;
|
c(context)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -325,10 +325,10 @@ impl CrushCommand for ConditionCommand {
|
|||||||
"conditional command"
|
"conditional command"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
|
fn might_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
|
||||||
arguments
|
arguments
|
||||||
.iter()
|
.iter()
|
||||||
.any(|arg| arg.value.can_block(arguments, context))
|
.any(|arg| arg.value.can_block(context))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy(&self) -> Command {
|
fn copy(&self) -> Command {
|
||||||
@ -365,7 +365,7 @@ impl CrushCommand for ConditionCommand {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output(&self, _input: &OutputType) -> Option<&ValueType> {
|
fn output_type(&self, _input: &OutputType) -> Option<&ValueType> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,13 +388,13 @@ impl Help for ConditionCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::PartialEq for ConditionCommand {
|
impl PartialEq for ConditionCommand {
|
||||||
fn eq(&self, _other: &ConditionCommand) -> bool {
|
fn eq(&self, _other: &ConditionCommand) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::Eq for ConditionCommand {}
|
impl Eq for ConditionCommand {}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Parameter {
|
pub enum Parameter {
|
||||||
@ -434,13 +434,13 @@ pub struct BoundCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CrushCommand for BoundCommand {
|
impl CrushCommand for BoundCommand {
|
||||||
fn invoke(&self, mut context: CommandContext) -> CrushResult<()> {
|
fn eval(&self, mut context: CommandContext) -> CrushResult<()> {
|
||||||
context.this = Some(self.this.clone());
|
context.this = Some(self.this.clone());
|
||||||
self.command.invoke(context)
|
self.command.eval(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
|
fn might_block(&self, arguments: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
|
||||||
self.command.can_block(arguments, context)
|
self.command.might_block(arguments, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
@ -482,8 +482,8 @@ impl CrushCommand for BoundCommand {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> {
|
fn output_type<'a>(&'a self, input: &'a OutputType) -> Option<&'a ValueType> {
|
||||||
self.command.output(input)
|
self.command.output_type(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arguments(&self) -> &Vec<ArgumentDescription> {
|
fn arguments(&self) -> &Vec<ArgumentDescription> {
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
use crate::lang::errors::{error, CrushResult, CrushErrorType};
|
use crate::lang::errors::{CrushResult, error};
|
||||||
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::ArgumentEvaluator, 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;
|
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;
|
||||||
use crate::lang::ast::Location;
|
use crate::data::r#struct::Struct;
|
||||||
|
use crate::lang::ast::tracked_string::TrackedString;
|
||||||
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CommandInvocation {
|
pub struct CommandInvocation {
|
||||||
@ -17,6 +18,239 @@ pub struct CommandInvocation {
|
|||||||
arguments: Vec<ArgumentDefinition>,
|
arguments: Vec<ArgumentDefinition>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn arg_can_block(local_arguments: &Vec<ArgumentDefinition>, context: &mut CompileContext) -> bool {
|
||||||
|
for arg in local_arguments {
|
||||||
|
if arg.value.can_block(context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommandInvocation {
|
||||||
|
pub fn new(command: ValueDefinition, arguments: Vec<ArgumentDefinition>) -> CommandInvocation {
|
||||||
|
CommandInvocation { command, arguments }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extracts the help message from a closure definition */
|
||||||
|
pub fn extract_help_message(&self) -> Option<String> {
|
||||||
|
if self.arguments.len() != 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &self.command {
|
||||||
|
ValueDefinition::Value(Value::String(s), _) => Some(s.to_string()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arguments(&self) -> &[ArgumentDefinition] {
|
||||||
|
&self.arguments
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(&self) -> &ValueDefinition {
|
||||||
|
&self.command
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Evaluates all the arguments into values, and puts them into a CommandContext,
|
||||||
|
ready to be exacuted by the main command.
|
||||||
|
*/
|
||||||
|
fn execution_context(
|
||||||
|
local_arguments: Vec<ArgumentDefinition>,
|
||||||
|
mut this: Option<Value>,
|
||||||
|
job_context: JobContext,
|
||||||
|
) -> CrushResult<CommandContext> {
|
||||||
|
let (arguments, arg_this) = local_arguments.eval(&mut CompileContext::from(&job_context))?;
|
||||||
|
|
||||||
|
if arg_this.is_some() {
|
||||||
|
this = arg_this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(job_context.command_context(arguments, this))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn can_block(&self, context: &mut CompileContext) -> bool {
|
||||||
|
if self.command.can_block(context) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match self.command.eval(context) {
|
||||||
|
Ok((_, Value::Command(command))) =>
|
||||||
|
command.might_block(&self.arguments, context) || arg_can_block(&self.arguments, context),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
|
||||||
|
eval(&self.command, &self.arguments, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval_non_blocking(command: &ValueDefinition, arguments: &Vec<ArgumentDefinition>, context: JobContext) -> CrushResult<Option<ThreadId>> {
|
||||||
|
match command.eval(&mut CompileContext::from(&context))
|
||||||
|
{
|
||||||
|
// Try to find the command in this thread. This may fail if the command is found via a subshell, in which case we need to spawn a thread
|
||||||
|
Ok((this, value)) => {
|
||||||
|
eval_internal(this, value, arguments.clone(), context, command.location())
|
||||||
|
}
|
||||||
|
Err(err) =>
|
||||||
|
if let ValueDefinition::Identifier(str) = command {
|
||||||
|
try_external_command(str, arguments.clone(), context)
|
||||||
|
} else {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(command: &ValueDefinition, arguments: &Vec<ArgumentDefinition>, context: JobContext) -> CrushResult<Option<ThreadId>> {
|
||||||
|
if command.can_block(&mut CompileContext::from(&context)) {
|
||||||
|
eval_non_blocking(command, arguments, context)
|
||||||
|
} else {
|
||||||
|
let command = command.clone();
|
||||||
|
let arguments = arguments.clone();
|
||||||
|
let t = context.global_state.threads().clone();
|
||||||
|
Ok(Some(t.spawn(
|
||||||
|
&command.to_string(),
|
||||||
|
move || {
|
||||||
|
match eval_non_blocking(&command, &arguments, context.clone()) {
|
||||||
|
Ok(Some(id)) => context.global_state.threads().join_one(
|
||||||
|
id,
|
||||||
|
&context.global_state.printer(),
|
||||||
|
),
|
||||||
|
Err(e) => context.global_state.printer().crush_error(e),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_internal(
|
||||||
|
this: Option<Value>,
|
||||||
|
value: Value,
|
||||||
|
local_arguments: Vec<ArgumentDefinition>,
|
||||||
|
context: JobContext,
|
||||||
|
location: Location,
|
||||||
|
) -> CrushResult<Option<ThreadId>> {
|
||||||
|
match value {
|
||||||
|
Value::Command(command) => eval_command(command, this, local_arguments, context),
|
||||||
|
Value::Type(t) => eval_type(t, local_arguments, context, location),
|
||||||
|
Value::Struct(s) => eval_struct(s, local_arguments, context, location),
|
||||||
|
v => eval_other(v, local_arguments, context, location),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_other(
|
||||||
|
value: Value,
|
||||||
|
local_arguments: Vec<ArgumentDefinition>,
|
||||||
|
context: JobContext,
|
||||||
|
location: Location,
|
||||||
|
) -> CrushResult<Option<ThreadId>> {
|
||||||
|
if local_arguments.len() == 0 {
|
||||||
|
eval_command(
|
||||||
|
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
||||||
|
None,
|
||||||
|
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(value, location))],
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
error(&format!("{} is not a command.", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_type(
|
||||||
|
value_type: ValueType,
|
||||||
|
local_arguments: Vec<ArgumentDefinition>,
|
||||||
|
context: JobContext,
|
||||||
|
location: Location,
|
||||||
|
) -> CrushResult<Option<ThreadId>> {
|
||||||
|
match value_type.fields().get("__call__") {
|
||||||
|
None => eval_command(
|
||||||
|
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
||||||
|
None,
|
||||||
|
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
|
||||||
|
Value::Type(value_type),
|
||||||
|
location,
|
||||||
|
))],
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
Some(call) => eval_command(
|
||||||
|
call.as_ref().copy(),
|
||||||
|
Some(Value::Type(value_type)),
|
||||||
|
local_arguments,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_struct(
|
||||||
|
struct_value: Struct,
|
||||||
|
local_arguments: Vec<ArgumentDefinition>,
|
||||||
|
context: JobContext,
|
||||||
|
location: Location,
|
||||||
|
) -> CrushResult<Option<ThreadId>> {
|
||||||
|
match struct_value.get("__call__") {
|
||||||
|
Some(Value::Command(call)) => {
|
||||||
|
eval_command(call, Some(Value::Struct(struct_value)), local_arguments, context)
|
||||||
|
}
|
||||||
|
Some(v) => error(
|
||||||
|
format!(
|
||||||
|
"__call__ should be a command, was of type {}",
|
||||||
|
v.value_type().to_string()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
if local_arguments.len() == 0 {
|
||||||
|
eval_command(
|
||||||
|
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
||||||
|
None,
|
||||||
|
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
|
||||||
|
Value::Struct(struct_value),
|
||||||
|
location,
|
||||||
|
))],
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
error(
|
||||||
|
format!(
|
||||||
|
"Struct must have a member __call__ to be used as a command {}",
|
||||||
|
struct_value.to_string()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_command(
|
||||||
|
command: Command,
|
||||||
|
this: Option<Value>,
|
||||||
|
local_arguments: Vec<ArgumentDefinition>,
|
||||||
|
context: JobContext,
|
||||||
|
) -> CrushResult<Option<ThreadId>> {
|
||||||
|
if !command.might_block(&local_arguments, &mut CompileContext::from(&context))
|
||||||
|
&& !arg_can_block(&local_arguments, &mut CompileContext::from(&context))
|
||||||
|
{
|
||||||
|
let new_context =
|
||||||
|
CommandInvocation::execution_context(local_arguments, this, context.clone())?;
|
||||||
|
context.global_state.printer().handle_error(command.eval(new_context));
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
let t = context.global_state.threads().clone();
|
||||||
|
let name = command.name().to_string();
|
||||||
|
Ok(Some(t.spawn(
|
||||||
|
&name,
|
||||||
|
move || {
|
||||||
|
let res = CommandInvocation::execution_context(local_arguments, this, context.clone())?;
|
||||||
|
command.eval(res)
|
||||||
|
},
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> {
|
fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathBuf>> {
|
||||||
if let Some(Value::List(path)) = env.get("cmd_path")? {
|
if let Some(Value::List(path)) = env.get("cmd_path")? {
|
||||||
let path_vec = path.dump();
|
let path_vec = path.dump();
|
||||||
@ -35,284 +269,11 @@ fn resolve_external_command(name: &str, env: &Scope) -> CrushResult<Option<PathB
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arg_can_block(local_arguments: &Vec<ArgumentDefinition>, context: &mut CompileContext) -> bool {
|
|
||||||
for arg in local_arguments {
|
|
||||||
if arg.value.can_block(local_arguments, context) {
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandInvocation {
|
|
||||||
pub fn new(command: ValueDefinition, arguments: Vec<ArgumentDefinition>) -> CommandInvocation {
|
|
||||||
CommandInvocation { command, arguments }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_string(&self) -> Option<String> {
|
|
||||||
if self.arguments.len() != 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match &self.command {
|
|
||||||
ValueDefinition::Value(Value::String(s), _) => Some(s.to_string()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn arguments(&self) -> &[ArgumentDefinition] {
|
|
||||||
&self.arguments
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn command(&self) -> &ValueDefinition {
|
|
||||||
&self.command
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
pub fn spawn_stream(
|
|
||||||
&self,
|
|
||||||
env: &Scope,
|
|
||||||
mut argument_stream: InputStream,
|
|
||||||
output: ValueSender,
|
|
||||||
) -> CrushResult<JobJoinHandle> {
|
|
||||||
let cmd = env.get(&self.name);
|
|
||||||
match cmd {
|
|
||||||
Some(Value::Command(command)) => {
|
|
||||||
let c = command.call;
|
|
||||||
Ok(handle(build(format_name(&self.name)).spawn(
|
|
||||||
move || {
|
|
||||||
loop {
|
|
||||||
match argument_stream.recv() {
|
|
||||||
Ok(mut row) => {}
|
|
||||||
Err(_) => break,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
error("Can't stream call")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
fn execution_context(
|
|
||||||
local_arguments: Vec<ArgumentDefinition>,
|
|
||||||
mut this: Option<Value>,
|
|
||||||
job_context: JobContext,
|
|
||||||
) -> CrushResult<CommandContext> {
|
|
||||||
let (arguments, arg_this) = local_arguments.compile(&mut CompileContext::from(&job_context))?;
|
|
||||||
|
|
||||||
if arg_this.is_some() {
|
|
||||||
this = arg_this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(job_context.command_context(arguments, this))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_block(&self, arg: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
|
|
||||||
let cmd = self.command.compile_internal(context, false);
|
|
||||||
match cmd {
|
|
||||||
Ok((_, Value::Command(command))) => {
|
|
||||||
command.can_block(arg, context) || arg_can_block(&self.arguments, context)
|
|
||||||
}
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn invoke(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
|
|
||||||
match self
|
|
||||||
.command
|
|
||||||
.compile_internal(&mut CompileContext::from(&context), false)
|
|
||||||
{
|
|
||||||
Ok((this, value)) => invoke_value(this, value, self.arguments.clone(), context, self.command.location()),
|
|
||||||
Err(err) => {
|
|
||||||
if err.is(CrushErrorType::BlockError) {
|
|
||||||
let cmd = self.command.clone();
|
|
||||||
let arguments = self.arguments.clone();
|
|
||||||
let t = context.global_state.threads().clone();
|
|
||||||
let location = self.command.location();
|
|
||||||
Ok(Some(t.spawn(
|
|
||||||
&self.command.to_string(),
|
|
||||||
move || {
|
|
||||||
match cmd.clone().compile_unbound(&mut CompileContext::from(&context)) {
|
|
||||||
Ok((this, value)) => context.global_state.printer().handle_error(invoke_value(
|
|
||||||
this,
|
|
||||||
value,
|
|
||||||
arguments,
|
|
||||||
context.clone(),
|
|
||||||
location,
|
|
||||||
)),
|
|
||||||
|
|
||||||
_ => context.global_state.printer().handle_error(try_external_command(
|
|
||||||
cmd,
|
|
||||||
arguments,
|
|
||||||
context.clone(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
)?))
|
|
||||||
} else {
|
|
||||||
try_external_command(self.command.clone(), self.arguments.clone(), context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invoke_value(
|
|
||||||
this: Option<Value>,
|
|
||||||
value: Value,
|
|
||||||
local_arguments: Vec<ArgumentDefinition>,
|
|
||||||
context: JobContext,
|
|
||||||
location: Location,
|
|
||||||
) -> CrushResult<Option<ThreadId>> {
|
|
||||||
match value {
|
|
||||||
Value::Command(command) => invoke_command(command, this, local_arguments, context),
|
|
||||||
Value::File(f) => {
|
|
||||||
if local_arguments.len() == 0 {
|
|
||||||
let meta = f.metadata();
|
|
||||||
if meta.is_ok() && meta.unwrap().is_dir() {
|
|
||||||
invoke_command(
|
|
||||||
context
|
|
||||||
.scope
|
|
||||||
.global_static_cmd(vec!["global", "fs", "cd"])?,
|
|
||||||
None,
|
|
||||||
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
|
|
||||||
Value::File(f),
|
|
||||||
location,
|
|
||||||
))],
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
invoke_command(
|
|
||||||
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
|
||||||
None,
|
|
||||||
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
|
|
||||||
Value::File(f),
|
|
||||||
location,
|
|
||||||
))],
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error(
|
|
||||||
format!(
|
|
||||||
"Not a command {}",
|
|
||||||
f.to_str().unwrap_or("<invalid filename>")
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Type(t) => match t.fields().get("__call__") {
|
|
||||||
None => invoke_command(
|
|
||||||
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
|
||||||
None,
|
|
||||||
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
|
|
||||||
Value::Type(t),
|
|
||||||
location,
|
|
||||||
))],
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
Some(call) => invoke_command(
|
|
||||||
call.as_ref().copy(),
|
|
||||||
Some(Value::Type(t)),
|
|
||||||
local_arguments,
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
Value::Struct(s) => match s.get("__call__") {
|
|
||||||
Some(Value::Command(call)) => {
|
|
||||||
invoke_command(call, Some(Value::Struct(s)), local_arguments, context)
|
|
||||||
}
|
|
||||||
Some(v) => error(
|
|
||||||
format!(
|
|
||||||
"__call__ should be a command, was of type {}",
|
|
||||||
v.value_type().to_string()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
),
|
|
||||||
_ => {
|
|
||||||
if local_arguments.len() == 0 {
|
|
||||||
invoke_command(
|
|
||||||
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
|
||||||
None,
|
|
||||||
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(
|
|
||||||
Value::Struct(s),
|
|
||||||
location,
|
|
||||||
))],
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
error(
|
|
||||||
format!(
|
|
||||||
"Struct must have a member __call__ to be used as a command {}",
|
|
||||||
s.to_string()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
if local_arguments.len() == 0 {
|
|
||||||
invoke_command(
|
|
||||||
context.scope.global_static_cmd(vec!["global", "io", "val"])?,
|
|
||||||
None,
|
|
||||||
vec![ArgumentDefinition::unnamed(ValueDefinition::Value(value, location))],
|
|
||||||
context,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
error(&format!("Not a command {}", value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn invoke_command(
|
|
||||||
action: Command,
|
|
||||||
this: Option<Value>,
|
|
||||||
local_arguments: Vec<ArgumentDefinition>,
|
|
||||||
context: JobContext,
|
|
||||||
) -> CrushResult<Option<ThreadId>> {
|
|
||||||
if !action.can_block(&local_arguments, &mut CompileContext::from(&context))
|
|
||||||
&& !arg_can_block(&local_arguments, &mut CompileContext::from(&context))
|
|
||||||
{
|
|
||||||
let new_context =
|
|
||||||
CommandInvocation::execution_context(local_arguments, this, context.clone())?;
|
|
||||||
context.global_state.printer().handle_error(action.invoke(new_context));
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
let t = context.global_state.threads().clone();
|
|
||||||
let name = action.name().to_string();
|
|
||||||
Ok(Some(t.spawn(
|
|
||||||
&name,
|
|
||||||
move || {
|
|
||||||
let res = CommandInvocation::execution_context(local_arguments, this, context.clone())?;
|
|
||||||
action.invoke(res)
|
|
||||||
},
|
|
||||||
)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_external_command(
|
fn try_external_command(
|
||||||
def: ValueDefinition,
|
cmd: &TrackedString,
|
||||||
mut arguments: Vec<ArgumentDefinition>,
|
mut arguments: Vec<ArgumentDefinition>,
|
||||||
context: JobContext,
|
context: JobContext,
|
||||||
) -> CrushResult<Option<ThreadId>> {
|
) -> CrushResult<Option<ThreadId>> {
|
||||||
let def_location = def.location();
|
|
||||||
let (cmd, sub) = match def {
|
|
||||||
ValueDefinition::Label(str) => (str, None),
|
|
||||||
ValueDefinition::GetAttr(parent, sub) => match parent.deref() {
|
|
||||||
ValueDefinition::Label(str) => (str.clone(), Some(sub)),
|
|
||||||
_ => return error("Not a command"),
|
|
||||||
},
|
|
||||||
_ => return error("Not a command"),
|
|
||||||
};
|
|
||||||
|
|
||||||
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) => {
|
||||||
@ -320,16 +281,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),
|
|
||||||
subcmd.location,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let call = CommandInvocation {
|
let call = CommandInvocation {
|
||||||
command: ValueDefinition::Value(
|
command: ValueDefinition::Value(
|
||||||
Value::Command(
|
Value::Command(
|
||||||
@ -337,17 +288,22 @@ fn try_external_command(
|
|||||||
.scope
|
.scope
|
||||||
.global_static_cmd(vec!["global", "control", "cmd"])?,
|
.global_static_cmd(vec!["global", "control", "cmd"])?,
|
||||||
),
|
),
|
||||||
def_location,
|
cmd.location,
|
||||||
),
|
),
|
||||||
arguments,
|
arguments,
|
||||||
};
|
};
|
||||||
call.invoke(context)
|
call.eval(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for CommandInvocation {
|
impl Display for CommandInvocation {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.command.fmt(f)
|
self.command.fmt(f)?;
|
||||||
|
for a in &self.arguments {
|
||||||
|
f.write_str(" ")?;
|
||||||
|
a.fmt(f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ fn completion_suffix(maybe_scope: CrushResult<Option<Value>>, t: &ValueType) ->
|
|||||||
Ok(Some(Value::Scope(_))) => ":",
|
Ok(Some(Value::Scope(_))) => ":",
|
||||||
Ok(Some(Value::Empty())) |
|
Ok(Some(Value::Empty())) |
|
||||||
Ok(Some(Value::Bool(_))) |
|
Ok(Some(Value::Bool(_))) |
|
||||||
Ok(Some(Value::Field(_))) |
|
Ok(Some(Value::Symbol(_))) |
|
||||||
Ok(Some(Value::Command(_))) => " ",
|
Ok(Some(Value::Command(_))) => " ",
|
||||||
_ => "",
|
_ => "",
|
||||||
}
|
}
|
||||||
@ -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]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::cmp::min;
|
||||||
use crate::lang::ast::{Node, CommandNode, JobListNode, JobNode};
|
use crate::lang::ast::{Node, CommandNode, JobListNode, JobNode};
|
||||||
use crate::lang::errors::{error, CrushResult, mandate, argument_error_legacy, to_crush_error};
|
use crate::lang::errors::{error, CrushResult, mandate, argument_error_legacy, to_crush_error};
|
||||||
use crate::lang::value::{ValueType, Value};
|
use crate::lang::value::{ValueType, Value};
|
||||||
@ -9,6 +10,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 +30,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 +71,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 +86,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,15 +118,47 @@ 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::Identifier(label) => Ok(label.string.clone()),
|
||||||
Node::Path(p, a) => {
|
Node::Path(p, a) => {
|
||||||
let res = simple_path(p.as_ref(), cursor)?;
|
let res = simple_path(p.as_ref(), cursor)?;
|
||||||
Ok(format!("{}/{}", res, &a.string))
|
Ok(format!("{}/{}", res, &a.string))
|
||||||
@ -192,12 +227,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::Identifier(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 +267,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 +277,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,
|
||||||
@ -274,13 +316,17 @@ pub fn parse(
|
|||||||
let cmd = &cmd.expressions[0];
|
let cmd = &cmd.expressions[0];
|
||||||
if cmd.location().contains(cursor) {
|
if cmd.location().contains(cursor) {
|
||||||
match cmd {
|
match cmd {
|
||||||
Node::Label(label) =>
|
Node::Identifier(label) =>
|
||||||
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 +342,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(
|
||||||
@ -321,7 +367,7 @@ pub fn parse(
|
|||||||
if name.location().contains(cursor) {
|
if name.location().contains(cursor) {
|
||||||
(Box::from(name.prefix(cursor)?), None, true)
|
(Box::from(name.prefix(cursor)?), None, true)
|
||||||
} else {
|
} else {
|
||||||
if let Node::Label(name) = name.as_ref() {
|
if let Node::Identifier(name) = name.as_ref() {
|
||||||
(value.clone(), Some(name.string.clone()), false)
|
(value.clone(), Some(name.string.clone()), false)
|
||||||
} else {
|
} else {
|
||||||
(value.clone(), None, false)
|
(value.clone(), None, false)
|
||||||
@ -333,22 +379,24 @@ pub fn parse(
|
|||||||
|
|
||||||
if argument_complete {
|
if argument_complete {
|
||||||
match arg.deref() {
|
match arg.deref() {
|
||||||
Node::Label(l) =>
|
Node::Field(l) => {
|
||||||
|
let substring = &l.string[0..min(l.string.len(), cursor - l.location.start)];
|
||||||
Ok(ParseResult::PartialArgument(
|
Ok(ParseResult::PartialArgument(
|
||||||
PartialCommandResult {
|
PartialCommandResult {
|
||||||
command: c,
|
command: c,
|
||||||
previous_arguments,
|
previous_arguments,
|
||||||
last_argument: LastArgument::Switch(l.string.clone()),
|
last_argument: LastArgument::Switch(substring.to_string()),
|
||||||
last_argument_name,
|
last_argument_name,
|
||||||
}
|
}
|
||||||
)),
|
))
|
||||||
|
}
|
||||||
|
|
||||||
_ => argument_error_legacy("Invalid argument name"),
|
_ => argument_error_legacy(format!("Invalid argument name {}", arg.type_name()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if arg.location().contains(cursor) {
|
if arg.location().contains(cursor) {
|
||||||
match arg.as_ref() {
|
match arg.as_ref() {
|
||||||
Node::Label(l) =>
|
Node::Identifier(l) =>
|
||||||
Ok(ParseResult::PartialArgument(
|
Ok(ParseResult::PartialArgument(
|
||||||
PartialCommandResult {
|
PartialCommandResult {
|
||||||
command: c,
|
command: c,
|
||||||
@ -358,13 +406,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,
|
||||||
})),
|
})),
|
||||||
@ -421,7 +479,7 @@ pub fn parse(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
use crate::lang::parser::lalrparser;
|
use crate::lang::parser::lalrparser;
|
||||||
|
|
||||||
fn ast(s: &str) -> CrushResult<JobListNode> {
|
fn ast(s: &str) -> CrushResult<JobListNode> {
|
||||||
@ -451,8 +509,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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,9 +127,9 @@ impl dyn BinaryReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn vec(vec: &Vec<u8>) -> Box<dyn BinaryReader + Send + Sync> {
|
pub fn vec(bytes: &[u8]) -> Box<dyn BinaryReader + Send + Sync> {
|
||||||
Box::from(VecReader {
|
Box::from(VecReader {
|
||||||
vec: vec.clone(),
|
vec: Vec::from(bytes),
|
||||||
offset: 0,
|
offset: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult};
|
use crate::lang::errors::{argument_error_legacy, error, mandate, CrushResult};
|
||||||
use crate::lang::pipe::CrushStream;
|
use crate::lang::pipe::{CrushStream, Stream};
|
||||||
use crate::lang::{data::table::ColumnType, data::table::Row, value::Field, value::Value, value::ValueType};
|
use crate::lang::{data::table::ColumnType, data::table::Row, value::Value, value::ValueType};
|
||||||
use crate::util::identity_arc::Identity;
|
use crate::util::identity_arc::Identity;
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
@ -8,6 +8,7 @@ use std::collections::HashSet;
|
|||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::lang::value::VecReader;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct List {
|
pub struct List {
|
||||||
@ -179,12 +180,18 @@ impl List {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn stream(&self) -> Stream {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
self.dump_value(&mut vec);
|
||||||
|
Box::new(VecReader::new(vec, self.cell_type.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
dump_to!(dump_string, String, String, |e: &String| e.to_string());
|
dump_to!(dump_string, String, String, |e: &String| e.to_string());
|
||||||
dump_to!(dump_integer, i128, Integer, |v: &i128| *v);
|
dump_to!(dump_integer, i128, Integer, |v: &i128| *v);
|
||||||
dump_to!(dump_bool, bool, Bool, |v: &bool| *v);
|
dump_to!(dump_bool, bool, Bool, |v: &bool| *v);
|
||||||
dump_to!(dump_type, ValueType, Type, |v: &ValueType| v.clone());
|
dump_to!(dump_type, ValueType, Type, |v: &ValueType| v.clone());
|
||||||
dump_to!(dump_float, f64, Float, |v: &f64| *v);
|
dump_to!(dump_float, f64, Float, |v: &f64| *v);
|
||||||
dump_to!(dump_field, Field, Field, |e: &Field| e.clone());
|
dump_to!(dump_symbol, String, Symbol, |e: &String| e.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for List {
|
impl Display for List {
|
||||||
@ -213,7 +220,7 @@ impl std::hash::Hash for List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::PartialEq for List {
|
impl PartialEq for List {
|
||||||
fn eq(&self, other: &List) -> bool {
|
fn eq(&self, other: &List) -> bool {
|
||||||
let us = self.cells.lock().unwrap().clone();
|
let us = self.cells.lock().unwrap().clone();
|
||||||
let them = other.cells.lock().unwrap().clone();
|
let them = other.cells.lock().unwrap().clone();
|
||||||
@ -229,7 +236,7 @@ impl std::cmp::PartialEq for List {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::cmp::PartialOrd for List {
|
impl PartialOrd for List {
|
||||||
fn partial_cmp(&self, other: &List) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &List) -> Option<Ordering> {
|
||||||
let us = self.cells.lock().unwrap().clone();
|
let us = self.cells.lock().unwrap().clone();
|
||||||
let them = other.cells.lock().unwrap().clone();
|
let them = other.cells.lock().unwrap().clone();
|
||||||
@ -246,40 +253,3 @@ impl std::cmp::PartialOrd for List {
|
|||||||
Some(Ordering::Equal)
|
Some(Ordering::Equal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ListReader {
|
|
||||||
list: List,
|
|
||||||
idx: usize,
|
|
||||||
types: Vec<ColumnType>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ListReader {
|
|
||||||
pub fn new(list: List, name: &str) -> ListReader {
|
|
||||||
ListReader {
|
|
||||||
types: vec![ColumnType::new(name, list.element_type())],
|
|
||||||
list,
|
|
||||||
idx: 0usize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CrushStream for ListReader {
|
|
||||||
fn read(&mut self) -> CrushResult<Row> {
|
|
||||||
self.idx += 1;
|
|
||||||
Ok(Row::new(vec![self.list.get(self.idx - 1)?]))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_timeout(
|
|
||||||
&mut self,
|
|
||||||
_timeout: Duration,
|
|
||||||
) -> Result<Row, crate::lang::pipe::RecvTimeoutError> {
|
|
||||||
match self.read() {
|
|
||||||
Ok(r) => Ok(r),
|
|
||||||
Err(_) => Err(crate::lang::pipe::RecvTimeoutError::Disconnected),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn types(&self) -> &[ColumnType] {
|
|
||||||
&self.types
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::lang::command::{Command, CrushCommand, OutputType, ArgumentDescription};
|
use crate::lang::command::{Command, CrushCommand, OutputType, ArgumentDescription};
|
||||||
use crate::lang::errors::{error, mandate, CrushResult, argument_error_legacy};
|
use crate::lang::errors::{error, mandate, CrushResult, argument_error_legacy, CrushError};
|
||||||
use crate::lang::execution_context::CommandContext;
|
use crate::lang::execution_context::CommandContext;
|
||||||
use crate::lang::help::Help;
|
use crate::lang::help::Help;
|
||||||
use crate::lang::data::r#struct::Struct;
|
use crate::lang::data::r#struct::Struct;
|
||||||
@ -9,6 +9,7 @@ use ordered_map::OrderedMap;
|
|||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::lang::errors::CrushErrorType::GenericError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This is where we store variables, including functions.
|
This is where we store variables, including functions.
|
||||||
@ -154,20 +155,20 @@ impl ScopeLoader {
|
|||||||
|
|
||||||
pub struct ScopeData {
|
pub struct ScopeData {
|
||||||
/** This is the parent scope used to perform variable name resolution. If a variable lookup
|
/** This is the parent scope used to perform variable name resolution. If a variable lookup
|
||||||
fails in the current scope, it proceeds to this scope. This is usually the scope in which this
|
fails in the current scope, it proceeds to this scope. This is usually the scope in which this
|
||||||
scope was *created*.
|
scope was *created*.
|
||||||
|
|
||||||
Not that when scopes are used as namespaces, they do not use this scope.
|
Not that when scopes are used as namespaces, they do not use this scope.
|
||||||
*/
|
*/
|
||||||
pub parent_scope: Option<Scope>,
|
pub parent_scope: Option<Scope>,
|
||||||
|
|
||||||
/** This is the scope in which the current scope was called. Since a closure can be called
|
/** This is the scope in which the current scope was called. Since a closure can be called
|
||||||
from inside any scope, it need not be the same as the parent scope. This scope is the one used
|
from inside any scope, it need not be the same as the parent scope. This scope is the one used
|
||||||
for break/continue loop control, and it is also the scope that builds up the namespace hierarchy. */
|
for break/continue loop control, and it is also the scope that builds up the namespace hierarchy. */
|
||||||
pub calling_scope: Option<Scope>,
|
pub calling_scope: Option<Scope>,
|
||||||
|
|
||||||
/** This is a list of scopes that are imported into the current scope. Anything directly inside
|
/** This is a list of scopes that are imported into the current scope. Anything directly inside
|
||||||
one of these scopes is also considered part of this scope. */
|
one of these scopes is also considered part of this scope. */
|
||||||
pub uses: Vec<Scope>,
|
pub uses: Vec<Scope>,
|
||||||
|
|
||||||
/** The actual data of this scope. */
|
/** The actual data of this scope. */
|
||||||
@ -177,11 +178,11 @@ pub struct ScopeData {
|
|||||||
pub is_loop: bool,
|
pub is_loop: bool,
|
||||||
|
|
||||||
/** True if this scope should stop execution, i.e. if the continue or break commands have been
|
/** True if this scope should stop execution, i.e. if the continue or break commands have been
|
||||||
called. */
|
called. */
|
||||||
pub is_stopped: bool,
|
pub is_stopped: bool,
|
||||||
|
|
||||||
/** True if this scope can not be further modified. Note that mutable variables in it, e.g.
|
/** True if this scope can not be further modified. Note that mutable variables in it, e.g.
|
||||||
lists can still be modified. */
|
lists can still be modified. */
|
||||||
pub is_readonly: bool,
|
pub is_readonly: bool,
|
||||||
|
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
@ -351,6 +352,15 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_calling_scope(&self) -> CrushResult<Scope> {
|
||||||
|
let data = self.lock()?;
|
||||||
|
if let Some(scope) = &data.calling_scope {
|
||||||
|
Ok(scope.clone())
|
||||||
|
} else {
|
||||||
|
error("Scope not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn do_break(&self) -> CrushResult<bool> {
|
pub fn do_break(&self) -> CrushResult<bool> {
|
||||||
let mut data = self.lock()?;
|
let mut data = self.lock()?;
|
||||||
if data.is_readonly {
|
if data.is_readonly {
|
||||||
|
@ -58,7 +58,7 @@ impl CrushStream for TableReader {
|
|||||||
Ok(self
|
Ok(self
|
||||||
.rows
|
.rows
|
||||||
.rows
|
.rows
|
||||||
.replace(self.idx - 1, Row::new(vec![Value::Integer(0)])))
|
.replace(self.idx - 1, Row::new(vec![Value::Empty()])))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_timeout(
|
fn read_timeout(
|
||||||
@ -147,19 +147,18 @@ impl ColumnType {
|
|||||||
impl Display for ColumnType {
|
impl Display for ColumnType {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
self.name.fmt(f)?;
|
self.name.fmt(f)?;
|
||||||
f.write_str("=(")?;
|
f.write_str("=($")?;
|
||||||
self.cell_type.fmt(f)?;
|
self.cell_type.fmt(f)?;
|
||||||
f.write_str(")")
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ColumnVec {
|
pub trait ColumnVec {
|
||||||
fn find_str(&self, needle: &str) -> CrushResult<usize>;
|
fn find(&self, needle: &str) -> CrushResult<usize>;
|
||||||
fn find(&self, needle: &[String]) -> CrushResult<usize>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColumnVec for &[ColumnType] {
|
impl ColumnVec for &[ColumnType] {
|
||||||
fn find_str(&self, needle: &str) -> CrushResult<usize> {
|
fn find(&self, needle: &str) -> CrushResult<usize> {
|
||||||
for (idx, field) in self.iter().enumerate() {
|
for (idx, field) in self.iter().enumerate() {
|
||||||
if field.name == needle {
|
if field.name == needle {
|
||||||
return Ok(idx);
|
return Ok(idx);
|
||||||
@ -177,29 +176,4 @@ impl ColumnVec for &[ColumnType] {
|
|||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find(&self, needle_vec: &[String]) -> CrushResult<usize> {
|
|
||||||
if needle_vec.len() != 1 {
|
|
||||||
argument_error_legacy("Expected direct field")
|
|
||||||
} else {
|
|
||||||
let needle = &needle_vec[0];
|
|
||||||
for (idx, field) in self.iter().enumerate() {
|
|
||||||
if &field.name == needle {
|
|
||||||
return Ok(idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
error(
|
|
||||||
format!(
|
|
||||||
"Unknown column {}, available columns are {}",
|
|
||||||
needle,
|
|
||||||
self.iter()
|
|
||||||
.map(|t| t.name.to_string())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join(", "),
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
use CrushErrorType::*;
|
use CrushErrorType::*;
|
||||||
use std::cmp::{min, max};
|
use std::cmp::{min, max};
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ pub fn pup(
|
|||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
cmd.invoke(CommandContext::new(&env, global_state));
|
cmd.eval(CommandContext::new(&env, global_state).with_output(snd));
|
||||||
global_state.threads().join(global_state.printer());
|
global_state.threads().join(global_state.printer());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -58,7 +58,7 @@ pub fn string(
|
|||||||
) -> CrushResult<()> {
|
) -> CrushResult<()> {
|
||||||
let jobs = global_state.parser().parse(command, &global_env)?;
|
let jobs = global_state.parser().parse(command, &global_env)?;
|
||||||
for job_definition in jobs {
|
for job_definition in jobs {
|
||||||
let handle = job_definition.invoke(JobContext::new(
|
let handle = job_definition.eval(JobContext::new(
|
||||||
empty_channel(),
|
empty_channel(),
|
||||||
output.clone(),
|
output.clone(),
|
||||||
global_env.clone(),
|
global_env.clone(),
|
||||||
|
@ -11,7 +11,7 @@ use crate::lang::pipe::{
|
|||||||
black_hole, empty_channel, InputStream, OutputStream, ValueReceiver, ValueSender,
|
black_hole, empty_channel, InputStream, OutputStream, ValueReceiver, ValueSender,
|
||||||
};
|
};
|
||||||
use crate::lang::printer::Printer;
|
use crate::lang::printer::Printer;
|
||||||
use crate::lang::value::{Value, ValueType};
|
use crate::lang::value::{Symbol, Value, ValueType};
|
||||||
use crate::util::glob::Glob;
|
use crate::util::glob::Glob;
|
||||||
use crate::util::replace::Replace;
|
use crate::util::replace::Replace;
|
||||||
use chrono::{DateTime, Duration, Local};
|
use chrono::{DateTime, Duration, Local};
|
||||||
@ -26,7 +26,7 @@ pub trait ArgumentVector {
|
|||||||
fn string(&mut self, idx: usize) -> CrushResult<String>;
|
fn string(&mut self, idx: usize) -> CrushResult<String>;
|
||||||
fn integer(&mut self, idx: usize) -> CrushResult<i128>;
|
fn integer(&mut self, idx: usize) -> CrushResult<i128>;
|
||||||
fn float(&mut self, idx: usize) -> CrushResult<f64>;
|
fn float(&mut self, idx: usize) -> CrushResult<f64>;
|
||||||
fn field(&mut self, idx: usize) -> CrushResult<Vec<String>>;
|
fn symbol(&mut self, idx: usize) -> CrushResult<Symbol>;
|
||||||
fn file(&mut self, idx: usize) -> CrushResult<PathBuf>;
|
fn file(&mut self, idx: usize) -> CrushResult<PathBuf>;
|
||||||
fn command(&mut self, idx: usize) -> CrushResult<Command>;
|
fn command(&mut self, idx: usize) -> CrushResult<Command>;
|
||||||
fn r#type(&mut self, idx: usize) -> CrushResult<ValueType>;
|
fn r#type(&mut self, idx: usize) -> CrushResult<ValueType>;
|
||||||
@ -39,7 +39,7 @@ pub trait ArgumentVector {
|
|||||||
fn optional_integer(&mut self, idx: usize) -> CrushResult<Option<i128>>;
|
fn optional_integer(&mut self, idx: usize) -> CrushResult<Option<i128>>;
|
||||||
fn optional_string(&mut self, idx: usize) -> CrushResult<Option<String>>;
|
fn optional_string(&mut self, idx: usize) -> CrushResult<Option<String>>;
|
||||||
fn optional_command(&mut self, idx: usize) -> CrushResult<Option<Command>>;
|
fn optional_command(&mut self, idx: usize) -> CrushResult<Option<Command>>;
|
||||||
fn optional_field(&mut self, idx: usize) -> CrushResult<Option<Vec<String>>>;
|
fn optional_symbol(&mut self, idx: usize) -> CrushResult<Option<Symbol>>;
|
||||||
fn optional_value(&mut self, idx: usize) -> CrushResult<Option<Value>>;
|
fn optional_value(&mut self, idx: usize) -> CrushResult<Option<Value>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ impl ArgumentVector for Vec<Argument> {
|
|||||||
argument_getter!(string, String, String, "string");
|
argument_getter!(string, String, String, "string");
|
||||||
argument_getter!(integer, i128, Integer, "integer");
|
argument_getter!(integer, i128, Integer, "integer");
|
||||||
argument_getter!(float, f64, Float, "float");
|
argument_getter!(float, f64, Float, "float");
|
||||||
argument_getter!(field, Vec<String>, Field, "field");
|
argument_getter!(symbol, Symbol, Symbol, "field");
|
||||||
argument_getter!(command, Command, Command, "command");
|
argument_getter!(command, Command, Command, "command");
|
||||||
argument_getter!(r#type, ValueType, Type, "type");
|
argument_getter!(r#type, ValueType, Type, "type");
|
||||||
argument_getter!(glob, Glob, Glob, "glob");
|
argument_getter!(glob, Glob, Glob, "glob");
|
||||||
@ -162,7 +162,7 @@ impl ArgumentVector for Vec<Argument> {
|
|||||||
optional_argument_getter!(optional_bool, bool, bool);
|
optional_argument_getter!(optional_bool, bool, bool);
|
||||||
optional_argument_getter!(optional_integer, i128, integer);
|
optional_argument_getter!(optional_integer, i128, integer);
|
||||||
optional_argument_getter!(optional_string, String, string);
|
optional_argument_getter!(optional_string, String, string);
|
||||||
optional_argument_getter!(optional_field, Vec<String>, field);
|
optional_argument_getter!(optional_symbol, Symbol, symbol);
|
||||||
optional_argument_getter!(optional_command, Command, command);
|
optional_argument_getter!(optional_command, Command, command);
|
||||||
optional_argument_getter!(optional_value, Value, value);
|
optional_argument_getter!(optional_value, Value, value);
|
||||||
}
|
}
|
||||||
@ -332,7 +332,7 @@ impl CommandContext {
|
|||||||
arguments,
|
arguments,
|
||||||
this,
|
this,
|
||||||
global_state: self.global_state,
|
global_state: self.global_state,
|
||||||
handle: self.handle.clone(),
|
handle: self.handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,7 +347,22 @@ impl CommandContext {
|
|||||||
arguments: self.arguments,
|
arguments: self.arguments,
|
||||||
this: self.this,
|
this: self.this,
|
||||||
global_state: self.global_state,
|
global_state: self.global_state,
|
||||||
handle: self.handle.clone(),
|
handle: self.handle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Return a new Command context that is identical to this one but with a different output sender.
|
||||||
|
*/
|
||||||
|
pub fn with_scope(self, scope : Scope) -> CommandContext {
|
||||||
|
CommandContext {
|
||||||
|
input: self.input,
|
||||||
|
output: self.output,
|
||||||
|
scope,
|
||||||
|
arguments: self.arguments,
|
||||||
|
this: self.this,
|
||||||
|
global_state: self.global_state,
|
||||||
|
handle: self.handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,10 @@ use std::io::Write;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::convert::{TryFrom};
|
use std::convert::{TryFrom};
|
||||||
|
|
||||||
|
/**
|
||||||
|
A type representing a set of files. It is used in the signature of builtin commands that
|
||||||
|
accept files, including globs, regexes, etc.
|
||||||
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Files {
|
pub struct Files {
|
||||||
had_entries: bool,
|
had_entries: bool,
|
||||||
@ -51,6 +55,7 @@ impl Files {
|
|||||||
match input.recv()? {
|
match input.recv()? {
|
||||||
Value::BinaryInputStream(b) => Ok(b),
|
Value::BinaryInputStream(b) => Ok(b),
|
||||||
Value::Binary(b) => Ok(<dyn BinaryReader>::vec(&b)),
|
Value::Binary(b) => Ok(<dyn BinaryReader>::vec(&b)),
|
||||||
|
Value::String(s) => Ok(<dyn BinaryReader>::vec(s.as_bytes())),
|
||||||
_ => argument_error_legacy("Expected either a file to read or binary pipe io"),
|
_ => argument_error_legacy("Expected either a file to read or binary pipe io"),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -69,7 +74,7 @@ impl Files {
|
|||||||
self.files[0].clone(),
|
self.files[0].clone(),
|
||||||
))?))
|
))?))
|
||||||
} else {
|
} else {
|
||||||
argument_error_legacy("Expected exactly one desitnation file")
|
argument_error_legacy("Expected at most one destination file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +83,8 @@ impl Files {
|
|||||||
Value::File(p) => self.files.push(p),
|
Value::File(p) => self.files.push(p),
|
||||||
Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), &mut self.files)?,
|
Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), &mut self.files)?,
|
||||||
Value::Regex(_, re) => re.match_files(&cwd()?, &mut self.files, printer),
|
Value::Regex(_, re) => re.match_files(&cwd()?, &mut self.files, printer),
|
||||||
value => match value.stream() {
|
Value::Symbol(f) => self.files.push(PathBuf::from(f)),
|
||||||
|
value => match value.stream()? {
|
||||||
None => return argument_error_legacy("Expected a file name"),
|
None => return argument_error_legacy("Expected a file name"),
|
||||||
Some(mut s) => {
|
Some(mut s) => {
|
||||||
let t = s.types();
|
let t = s.types();
|
||||||
|
@ -6,9 +6,10 @@ use crate::lang::threads::ThreadStore;
|
|||||||
use num_format::{Grouping, SystemLocale};
|
use num_format::{Grouping, SystemLocale};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
struct StateData {
|
/**
|
||||||
locale: SystemLocale,
|
A type representing the shared crush state, such as the printer, the running jobs, the running
|
||||||
}
|
threads, etc.
|
||||||
|
*/
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct GlobalState {
|
pub struct GlobalState {
|
||||||
@ -21,6 +22,10 @@ pub struct GlobalState {
|
|||||||
parser: Parser,
|
parser: Parser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct StateData {
|
||||||
|
locale: SystemLocale,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct JobId(usize);
|
pub struct JobId(usize);
|
||||||
|
|
||||||
@ -47,7 +52,7 @@ pub struct LiveJob {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A resource counter. Once it reaches zero, the job is done, and it is removed from the global job
|
A resource tracker. Once it reaches zero, the job is done, and it is removed from the global job
|
||||||
table.
|
table.
|
||||||
*/
|
*/
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -16,7 +16,7 @@ use crate::lang::global_state::GlobalState;
|
|||||||
use crate::lang::command::Command;
|
use crate::lang::command::Command;
|
||||||
use crate::lang::command_invocation::CommandInvocation;
|
use crate::lang::command_invocation::CommandInvocation;
|
||||||
use crate::lang::value::{ValueDefinition, Value};
|
use crate::lang::value::{ValueDefinition, Value};
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
use crate::lang::execution_context::JobContext;
|
use crate::lang::execution_context::JobContext;
|
||||||
|
|
||||||
const DEFAULT_PROMPT: &'static str = "crush# ";
|
const DEFAULT_PROMPT: &'static str = "crush# ";
|
||||||
@ -46,7 +46,7 @@ pub fn execute_prompt(
|
|||||||
ValueDefinition::Value(Value::Command(prompt), Location::new(0, 0)),
|
ValueDefinition::Value(Value::Command(prompt), Location::new(0, 0)),
|
||||||
vec![]);
|
vec![]);
|
||||||
let (snd, recv) = pipe();
|
let (snd, recv) = pipe();
|
||||||
cmd.invoke(JobContext::new(
|
cmd.eval(JobContext::new(
|
||||||
empty_channel(),
|
empty_channel(),
|
||||||
snd,
|
snd,
|
||||||
env.clone(),
|
env.clone(),
|
||||||
|
@ -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")),
|
||||||
|
@ -4,7 +4,7 @@ use crate::lang::execution_context::{CompileContext, JobContext};
|
|||||||
use crate::lang::pipe::pipe;
|
use crate::lang::pipe::pipe;
|
||||||
use std::thread::ThreadId;
|
use std::thread::ThreadId;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Job {
|
pub struct Job {
|
||||||
@ -23,7 +23,7 @@ impl Job {
|
|||||||
|
|
||||||
pub fn can_block(&self, context: &mut CompileContext) -> bool {
|
pub fn can_block(&self, context: &mut CompileContext) -> bool {
|
||||||
if self.commands.len() == 1 {
|
if self.commands.len() == 1 {
|
||||||
self.commands[0].can_block(self.commands[0].arguments(), context)
|
self.commands[0].can_block(context)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
@ -33,13 +33,13 @@ impl Job {
|
|||||||
&self.commands
|
&self.commands
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invoke(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
|
pub fn eval(&self, context: JobContext) -> CrushResult<Option<ThreadId>> {
|
||||||
let context = context.running(self.to_string());
|
let context = context.running(self.to_string());
|
||||||
let mut input = context.input.clone();
|
let mut input = context.input.clone();
|
||||||
let last_job_idx = self.commands.len() - 1;
|
let last_job_idx = self.commands.len() - 1;
|
||||||
for call_def in &self.commands[..last_job_idx] {
|
for call_def in &self.commands[..last_job_idx] {
|
||||||
let (output, next_input) = pipe();
|
let (output, next_input) = pipe();
|
||||||
call_def.invoke(context.with_io(input, output))?;
|
call_def.eval(context.with_io(input, output))?;
|
||||||
input = next_input;
|
input = next_input;
|
||||||
|
|
||||||
if context.scope.is_stopped() {
|
if context.scope.is_stopped() {
|
||||||
@ -52,15 +52,16 @@ impl Job {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let last_call_def = &self.commands[last_job_idx];
|
let last_call_def = &self.commands[last_job_idx];
|
||||||
last_call_def.invoke(context.with_io(input, context.output.clone())).map_err(|e| e.with_location(self.location))
|
last_call_def.eval(context.with_io(input, context.output.clone())).map_err(|e| e.with_location(self.location))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_string(&self) -> Option<String> {
|
/** Extracts the help message from a closure definition */
|
||||||
|
pub fn extract_help_message(&self) -> Option<String> {
|
||||||
if self.commands.len() != 1 {
|
if self.commands.len() != 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.commands[0].as_string()
|
self.commands[0].extract_help_message()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use crate::lang::ast::*;
|
use crate::lang::ast::*;
|
||||||
|
use crate::lang::ast::location::Location;
|
||||||
|
use crate::lang::ast::tracked_string::TrackedString;
|
||||||
|
|
||||||
grammar;
|
grammar;
|
||||||
|
|
||||||
@ -102,15 +104,15 @@ ParameterList: Vec<ParameterNode> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Parameter: ParameterNode = {
|
Parameter: ParameterNode = {
|
||||||
<start: @L> <l: LabelOrWildcard> <end: @R> <d: Default> =>
|
<start: @L> <l: Identifier> <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: Identifier> <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: Identifier> <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: Identifier> <end: @R> => ParameterNode::Named(TrackedString::from(&l[1..], Location::new(start, end))),
|
||||||
}
|
}
|
||||||
|
|
||||||
Default: Option<Node> = {
|
Default: Option<Node> = {
|
||||||
@ -119,9 +121,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: Identifier> <end: @R> =>
|
||||||
|
Node::parse_identifier(&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 +149,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::Identifier(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 +173,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 Identifier @R => TokenNode::new(TokenType::Identifier, <>),
|
||||||
|
@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,9 +207,10 @@ 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%\?][\./_0-9a-zA-Z%\?]*)?)" => FileOrWildcard,
|
r"\$[_0-9a-zA-Z][_0-9a-zA-Z]*" => Identifier,
|
||||||
r"--[_0-9a-zA-Z]+" => Flag,
|
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,
|
r"\^[\._a-zA-Z][\._a-zA-Z0-9]*" => Field,
|
||||||
r#"'([^\\']|\\.)*'"# => QuotedFile,
|
r#"'([^\\']|\\.)*'"# => QuotedFile,
|
||||||
r#"re"([^"]|\\.)*""# => Regex,
|
r#"re"([^"]|\\.)*""# => Regex,
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
/**
|
||||||
|
Core language implementation lives in this crate
|
||||||
|
*/
|
||||||
pub mod argument;
|
pub mod argument;
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
/**
|
||||||
|
A type representing a f64 or an i128. It is used in the signature of builtin commands that
|
||||||
|
accept any type of nomeric value as arguments, e.g. the math library.
|
||||||
|
*/
|
||||||
pub enum Number {
|
pub enum Number {
|
||||||
Float(f64),
|
Float(f64),
|
||||||
Integer(i128),
|
Integer(i128),
|
||||||
|
@ -3,6 +3,11 @@ use crate::util::glob::Glob;
|
|||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
/**
|
||||||
|
A type representing a set of text patterns. The test method can be used to check if the patterns
|
||||||
|
match.
|
||||||
|
*/
|
||||||
|
|
||||||
pub struct Patterns {
|
pub struct Patterns {
|
||||||
patterns: Vec<Value>,
|
patterns: Vec<Value>,
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,17 @@ pub fn pipe() -> (ValueSender, ValueReceiver) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A Sender/Receiver pair that is bounded to only one Value on the wire before blocking.
|
||||||
|
*/
|
||||||
|
pub fn printer_pipe() -> (ValueSender, ValueReceiver) {
|
||||||
|
let (send, recv) = bounded(1);
|
||||||
|
(
|
||||||
|
ValueSender { sender: send, is_pipeline: false },
|
||||||
|
ValueReceiver { receiver: recv, is_pipeline: false },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn streams(signature: Vec<ColumnType>) -> (OutputStream, InputStream) {
|
pub fn streams(signature: Vec<ColumnType>) -> (OutputStream, InputStream) {
|
||||||
let (output, input) = bounded(128);
|
let (output, input) = bounded(128);
|
||||||
(
|
(
|
||||||
|
@ -4,9 +4,8 @@
|
|||||||
|
|
||||||
use crate::lang::data::binary::BinaryReader;
|
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::printer::Printer;
|
use crate::lang::printer::Printer;
|
||||||
use crate::lang::pipe::{CrushStream, InputStream, ValueSender, pipe};
|
use crate::lang::pipe::{CrushStream, InputStream, ValueSender, printer_pipe, Stream};
|
||||||
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;
|
||||||
@ -17,12 +16,34 @@ use crate::lang::value::ValueType;
|
|||||||
use crate::lang::data::r#struct::Struct;
|
use crate::lang::data::r#struct::Struct;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use std::io::{BufReader, Read};
|
use std::io::{BufReader, Read};
|
||||||
|
use std::ops::Deref;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use crate::util::hex::to_hex;
|
use crate::util::hex::to_hex;
|
||||||
use crate::lang::global_state::GlobalState;
|
use crate::lang::global_state::GlobalState;
|
||||||
use num_format::Grouping;
|
use num_format::Grouping;
|
||||||
|
|
||||||
|
pub fn create_pretty_printer(
|
||||||
|
printer: Printer,
|
||||||
|
global_state: &GlobalState,
|
||||||
|
) -> ValueSender {
|
||||||
|
let global_state = global_state.clone();
|
||||||
|
let (o, i) = printer_pipe();
|
||||||
|
let printer_clone = printer.clone();
|
||||||
|
printer_clone.handle_error(to_crush_error(
|
||||||
|
thread::Builder::new()
|
||||||
|
.name("output-formater".to_string())
|
||||||
|
.spawn(move || {
|
||||||
|
let mut pp = PrettyPrinter { printer, grouping: global_state.grouping() };
|
||||||
|
while let Ok(val) = i.recv() {
|
||||||
|
pp.grouping = global_state.grouping();
|
||||||
|
pp.print_value(val);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
o
|
||||||
|
}
|
||||||
|
|
||||||
trait Width {
|
trait Width {
|
||||||
fn width(&self) -> usize;
|
fn width(&self) -> usize;
|
||||||
}
|
}
|
||||||
@ -39,26 +60,6 @@ impl Width for &str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_pretty_printer(
|
|
||||||
printer: Printer,
|
|
||||||
global_state: &GlobalState,
|
|
||||||
) -> ValueSender {
|
|
||||||
let global_state = global_state.clone();
|
|
||||||
let (o, i) = pipe();
|
|
||||||
let printer_clone = printer.clone();
|
|
||||||
printer_clone.handle_error(to_crush_error(
|
|
||||||
thread::Builder::new()
|
|
||||||
.name("output-formater".to_string())
|
|
||||||
.spawn(move || {
|
|
||||||
let mut pp = PrettyPrinter { printer, grouping: global_state.grouping() };
|
|
||||||
while let Ok(val) = i.recv() {
|
|
||||||
pp.grouping = global_state.grouping();
|
|
||||||
pp.print_value(val);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
));
|
|
||||||
o
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PrettyPrinter {
|
pub struct PrettyPrinter {
|
||||||
printer: Printer,
|
printer: Printer,
|
||||||
@ -136,14 +137,14 @@ impl PrettyPrinter {
|
|||||||
if list.len() < 8 {
|
if list.len() < 8 {
|
||||||
self.printer.line(list.to_string().as_str())
|
self.printer.line(list.to_string().as_str())
|
||||||
} else {
|
} else {
|
||||||
self.print_stream(&mut ListReader::new(list, "value"), 0)
|
self.print_stream(list.stream().as_mut(), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.printer.line(cell.to_pretty_string(self.grouping).as_str()),
|
_ => self.printer.line(cell.to_pretty_string(self.grouping).as_str()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_stream(&self, stream: &mut impl CrushStream, indent: usize) {
|
fn print_stream(&self, stream: &mut dyn CrushStream, indent: usize) {
|
||||||
let mut data: Vec<Row> = Vec::new();
|
let mut data: Vec<Row> = Vec::new();
|
||||||
let mut has_table = false;
|
let mut has_table = false;
|
||||||
|
|
||||||
@ -388,7 +389,7 @@ impl PrettyPrinter {
|
|||||||
Value::TableInputStream(mut output) => self.print_stream(&mut output, indent),
|
Value::TableInputStream(mut output) => self.print_stream(&mut output, indent),
|
||||||
Value::Table(rows) => self.print_stream(&mut TableReader::new(rows), indent),
|
Value::Table(rows) => self.print_stream(&mut TableReader::new(rows), indent),
|
||||||
Value::BinaryInputStream(mut b) => self.print_binary(b.as_mut(), indent),
|
Value::BinaryInputStream(mut b) => self.print_binary(b.as_mut(), indent),
|
||||||
Value::List(list) => self.print_stream(&mut ListReader::new(list, "value"), indent),
|
Value::List(list) => self.print_stream(list.stream().as_mut(), indent),
|
||||||
_ => {
|
_ => {
|
||||||
let mut line = " ".repeat(4 * indent);
|
let mut line = " ".repeat(4 * indent);
|
||||||
line.push_str(&ss);
|
line.push_str(&ss);
|
||||||
|
@ -16,7 +16,7 @@ use crate::lang::printer::PrinterMessage::*;
|
|||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
use termion::terminal_size;
|
use termion::terminal_size;
|
||||||
use std::cmp::max;
|
use std::cmp::max;
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Printer {
|
pub struct Printer {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use crate::lang::errors::{error, CrushResult};
|
use crate::lang::errors::{CrushResult, error};
|
||||||
use crate::lang::serialization::model::{element, Element};
|
use crate::lang::serialization::model::{element, Element};
|
||||||
use crate::lang::serialization::model;
|
use crate::lang::serialization::model;
|
||||||
|
|
||||||
use crate::lang::serialization::{DeserializationState, Serializable, SerializationState};
|
use crate::lang::serialization::{DeserializationState, Serializable, SerializationState};
|
||||||
use crate::lang::ast::{TrackedString, Location};
|
use crate::lang::ast::tracked_string::TrackedString;
|
||||||
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
impl Serializable<TrackedString> for TrackedString {
|
impl Serializable<TrackedString> for TrackedString {
|
||||||
fn deserialize(
|
fn deserialize(
|
||||||
|
@ -36,7 +36,7 @@ fn serialize_simple(
|
|||||||
Value::Bool(b) => element::Element::Bool(*b),
|
Value::Bool(b) => element::Element::Bool(*b),
|
||||||
Value::Empty() => element::Element::Empty(false),
|
Value::Empty() => element::Element::Empty(false),
|
||||||
Value::Time(d) => element::Element::Time(d.timestamp_nanos()),
|
Value::Time(d) => element::Element::Time(d.timestamp_nanos()),
|
||||||
Value::Field(f) => element::Element::Field(f.serialize(elements, state)? as u64),
|
Value::Symbol(f) => element::Element::Symbol(f.to_string()),
|
||||||
_ => return error("Expected simple value"),
|
_ => return error("Expected simple value"),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -88,7 +88,7 @@ impl Serializable<Value> for Value {
|
|||||||
id, elements, state,
|
id, elements, state,
|
||||||
)?)),
|
)?)),
|
||||||
|
|
||||||
element::Element::Field(f) => Ok(Value::Field(Vec::deserialize(*f as usize, elements, state)?)),
|
element::Element::Symbol(s) => Ok(Value::Symbol(s.to_string())),
|
||||||
element::Element::UserScope(_) | element::Element::InternalScope(_) => {
|
element::Element::UserScope(_) | element::Element::InternalScope(_) => {
|
||||||
Ok(Value::Scope(Scope::deserialize(id, elements, state)?))
|
Ok(Value::Scope(Scope::deserialize(id, elements, state)?))
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ impl Serializable<Value> for Value {
|
|||||||
| Value::Bool(_)
|
| Value::Bool(_)
|
||||||
| Value::Empty()
|
| Value::Empty()
|
||||||
| Value::Time(_)
|
| Value::Time(_)
|
||||||
| Value::Field(_) => serialize_simple(self, elements, state),
|
| Value::Symbol(_) => serialize_simple(self, elements, state),
|
||||||
|
|
||||||
Value::Integer(s) => s.serialize(elements, state),
|
Value::Integer(s) => s.serialize(elements, state),
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ impl Serializable<ValueType> for ValueType {
|
|||||||
4 => ValueType::Command,
|
4 => ValueType::Command,
|
||||||
5 => ValueType::Binary,
|
5 => ValueType::Binary,
|
||||||
6 => ValueType::Duration,
|
6 => ValueType::Duration,
|
||||||
7 => ValueType::Field,
|
7 => ValueType::Symbol,
|
||||||
8 => ValueType::Glob,
|
8 => ValueType::Glob,
|
||||||
9 => ValueType::Regex,
|
9 => ValueType::Regex,
|
||||||
10 => ValueType::Scope,
|
10 => ValueType::Scope,
|
||||||
@ -84,7 +84,7 @@ impl Serializable<ValueType> for ValueType {
|
|||||||
ValueType::Integer => SimpleTypeKind::Integer,
|
ValueType::Integer => SimpleTypeKind::Integer,
|
||||||
ValueType::Time => SimpleTypeKind::Time,
|
ValueType::Time => SimpleTypeKind::Time,
|
||||||
ValueType::Duration => SimpleTypeKind::Duration,
|
ValueType::Duration => SimpleTypeKind::Duration,
|
||||||
ValueType::Field => SimpleTypeKind::Field,
|
ValueType::Symbol => SimpleTypeKind::Symbol,
|
||||||
ValueType::Glob => SimpleTypeKind::Glob,
|
ValueType::Glob => SimpleTypeKind::Glob,
|
||||||
ValueType::Regex => SimpleTypeKind::Regex,
|
ValueType::Regex => SimpleTypeKind::Regex,
|
||||||
ValueType::Command => SimpleTypeKind::Command,
|
ValueType::Command => SimpleTypeKind::Command,
|
||||||
|
@ -8,7 +8,9 @@ use crossbeam::channel::Receiver;
|
|||||||
use crossbeam::channel::unbounded;
|
use crossbeam::channel::unbounded;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
|
/**
|
||||||
|
A thread management utility. Spawn, track and join on threads.
|
||||||
|
*/
|
||||||
struct ThreadData {
|
struct ThreadData {
|
||||||
handle: JoinHandle<CrushResult<()>>,
|
handle: JoinHandle<CrushResult<()>>,
|
||||||
creation_time: DateTime<Local>,
|
creation_time: DateTime<Local>,
|
||||||
@ -73,10 +75,10 @@ impl ThreadStore {
|
|||||||
}))?;
|
}))?;
|
||||||
let id = handle.thread().id();
|
let id = handle.thread().id();
|
||||||
let mut data = self.data.lock().unwrap();
|
let mut data = self.data.lock().unwrap();
|
||||||
data.threads.push(ThreadData {
|
data.threads.push(ThreadData {
|
||||||
handle,
|
handle,
|
||||||
creation_time: Local::now(),
|
creation_time: Local::now(),
|
||||||
});
|
});
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
The type representing all values in crush.
|
The type representing all values in crush.
|
||||||
*/
|
*/
|
||||||
mod value_definition;
|
mod value_definition;
|
||||||
mod value_type;
|
mod value_type;
|
||||||
|
|
||||||
@ -12,13 +12,13 @@ use std::str::FromStr;
|
|||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::lang::errors::{argument_error_legacy, mandate, CrushResult};
|
use crate::lang::errors::{argument_error_legacy, mandate, CrushResult, data_error, eof_error};
|
||||||
use crate::lang::data::r#struct::Struct;
|
use crate::lang::data::r#struct::Struct;
|
||||||
use crate::lang::data::r#struct::StructReader;
|
use crate::lang::data::r#struct::StructReader;
|
||||||
use crate::lang::data::scope::Scope;
|
use crate::lang::data::scope::Scope;
|
||||||
use crate::lang::pipe::{streams, InputStream, Stream, OutputStream};
|
use crate::lang::pipe::{streams, InputStream, Stream, OutputStream, CrushStream};
|
||||||
use crate::lang::data::{
|
use crate::lang::data::{
|
||||||
binary::BinaryReader, dict::Dict, dict::DictReader, list::List, list::ListReader,
|
binary::BinaryReader, dict::Dict, dict::DictReader, list::List,
|
||||||
table::ColumnType, table::TableReader,
|
table::ColumnType, table::TableReader,
|
||||||
};
|
};
|
||||||
use crate::util::time::duration_format;
|
use crate::util::time::duration_format;
|
||||||
@ -38,18 +38,20 @@ 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::data::table::Row;
|
||||||
use crate::util::escape::escape;
|
use crate::util::escape::escape;
|
||||||
|
use crate::util::replace::Replace;
|
||||||
|
|
||||||
pub type Field = Vec<String>;
|
pub type Symbol = String;
|
||||||
|
|
||||||
pub enum Value {
|
pub enum Value {
|
||||||
String(String),
|
String(String),
|
||||||
Integer(i128),
|
Integer(i128),
|
||||||
Time(DateTime<Local>),
|
Time(DateTime<Local>),
|
||||||
Duration(Duration),
|
Duration(Duration),
|
||||||
Field(Field),
|
Symbol(Symbol),
|
||||||
Glob(Glob),
|
Glob(Glob),
|
||||||
Regex(String, Regex),
|
Regex(String, Regex),
|
||||||
Command(Command),
|
Command(Command),
|
||||||
@ -75,10 +77,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::Symbol(val) => std::fmt::Display::fmt(val, f),
|
||||||
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\"")?;
|
||||||
@ -144,6 +143,49 @@ impl From<bool> for Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct VecReader {
|
||||||
|
vec: Vec<Value>,
|
||||||
|
types: Vec<ColumnType>,
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VecReader {
|
||||||
|
pub fn new(
|
||||||
|
vec: Vec<Value>,
|
||||||
|
column_type: ValueType,
|
||||||
|
) -> VecReader {
|
||||||
|
VecReader {
|
||||||
|
vec,
|
||||||
|
types: vec![ColumnType::new("value", column_type)],
|
||||||
|
idx: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CrushStream for VecReader {
|
||||||
|
fn read(&mut self) -> CrushResult<Row> {
|
||||||
|
self.idx += 1;
|
||||||
|
if self.idx > self.vec.len() {
|
||||||
|
return eof_error()
|
||||||
|
}
|
||||||
|
Ok(Row::new(vec![self.vec.replace(self.idx - 1, Value::Empty())]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_timeout(
|
||||||
|
&mut self,
|
||||||
|
_timeout: Duration,
|
||||||
|
) -> Result<Row, crate::lang::pipe::RecvTimeoutError> {
|
||||||
|
match self.read() {
|
||||||
|
Ok(r) => Ok(r),
|
||||||
|
Err(_) => Err(crate::lang::pipe::RecvTimeoutError::Disconnected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn types(&self) -> &[ColumnType] {
|
||||||
|
&self.types
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Value {
|
impl Value {
|
||||||
pub fn bind(self, this: Value) -> Value {
|
pub fn bind(self, this: Value) -> Value {
|
||||||
match self {
|
match self {
|
||||||
@ -218,15 +260,22 @@ impl Value {
|
|||||||
Value::String(s.into())
|
Value::String(s.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stream(&self) -> Option<Stream> {
|
pub fn stream(&self) -> CrushResult<Option<Stream>> {
|
||||||
match self {
|
Ok(match self {
|
||||||
Value::TableInputStream(s) => Some(Box::from(s.clone())),
|
Value::TableInputStream(s) => Some(Box::from(s.clone())),
|
||||||
Value::Table(r) => Some(Box::from(TableReader::new(r.clone()))),
|
Value::Table(r) => Some(Box::from(TableReader::new(r.clone()))),
|
||||||
Value::List(l) => Some(Box::from(ListReader::new(l.clone(), "value"))),
|
Value::List(l) => Some(l.stream()),
|
||||||
Value::Dict(d) => Some(Box::from(DictReader::new(d.clone()))),
|
Value::Dict(d) => Some(Box::from(DictReader::new(d.clone()))),
|
||||||
Value::Struct(s) => Some(Box::from(StructReader::new(s.clone()))),
|
Value::Struct(s) => Some(Box::from(StructReader::new(s.clone()))),
|
||||||
|
Value::Glob(l) => {
|
||||||
|
let mut paths = Vec::<PathBuf>::new();
|
||||||
|
l.glob_files(&cwd()?, &mut paths)?;
|
||||||
|
Some(Box::from(VecReader::new(
|
||||||
|
paths.iter().map(|e| { Value::File(e.to_path_buf()) }).collect(),
|
||||||
|
ValueType::File)))
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value_type(&self) -> ValueType {
|
pub fn value_type(&self) -> ValueType {
|
||||||
@ -234,7 +283,7 @@ impl Value {
|
|||||||
Value::String(_) => ValueType::String,
|
Value::String(_) => ValueType::String,
|
||||||
Value::Integer(_) => ValueType::Integer,
|
Value::Integer(_) => ValueType::Integer,
|
||||||
Value::Time(_) => ValueType::Time,
|
Value::Time(_) => ValueType::Time,
|
||||||
Value::Field(_) => ValueType::Field,
|
Value::Symbol(_) => ValueType::Symbol,
|
||||||
Value::Glob(_) => ValueType::Glob,
|
Value::Glob(_) => ValueType::Glob,
|
||||||
Value::Regex(_, _) => ValueType::Regex,
|
Value::Regex(_, _) => ValueType::Regex,
|
||||||
Value::Command(_) => ValueType::Command,
|
Value::Command(_) => ValueType::Command,
|
||||||
@ -262,7 +311,7 @@ impl Value {
|
|||||||
Value::File(p) => v.push(p.clone()),
|
Value::File(p) => v.push(p.clone()),
|
||||||
Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), v)?,
|
Value::Glob(pattern) => pattern.glob_files(&PathBuf::from("."), v)?,
|
||||||
Value::Regex(_, re) => re.match_files(&cwd()?, v, printer),
|
Value::Regex(_, re) => re.match_files(&cwd()?, v, printer),
|
||||||
val => match val.stream() {
|
val => match val.stream()? {
|
||||||
None => return error("Expected a file name"),
|
None => return error("Expected a file name"),
|
||||||
Some(mut s) => {
|
Some(mut s) => {
|
||||||
let t = s.types();
|
let t = s.types();
|
||||||
@ -329,7 +378,7 @@ impl Value {
|
|||||||
ValueType::File => Ok(Value::File(PathBuf::from(str_val.as_str()))),
|
ValueType::File => Ok(Value::File(PathBuf::from(str_val.as_str()))),
|
||||||
ValueType::Glob => Ok(Value::Glob(Glob::new(str_val.as_str()))),
|
ValueType::Glob => Ok(Value::Glob(Glob::new(str_val.as_str()))),
|
||||||
ValueType::Integer => to_crush_error(str_val.parse::<i128>()).map(Value::Integer),
|
ValueType::Integer => to_crush_error(str_val.parse::<i128>()).map(Value::Integer),
|
||||||
ValueType::Field => Ok(Value::Field(vec![str_val])),
|
ValueType::Symbol => Ok(Value::Symbol(str_val)),
|
||||||
ValueType::Regex => {
|
ValueType::Regex => {
|
||||||
to_crush_error(Regex::new(str_val.as_str()).map(|v| Value::Regex(str_val, v)))
|
to_crush_error(Regex::new(str_val.as_str()).map(|v| Value::Regex(str_val, v)))
|
||||||
}
|
}
|
||||||
@ -363,13 +412,13 @@ impl Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Format this value in a way appropriate for use in the pretty printer.
|
Format this value in a way appropriate for use in the pretty printer.
|
||||||
|
|
||||||
* Escape non-printable strings
|
* Escape non-printable strings
|
||||||
* Respect integer grouping, but use _ intead of whatever number group
|
* Respect integer grouping, but use _ intead of whatever number group
|
||||||
separator the locale prescribes, so that the number can be copied
|
separator the locale prescribes, so that the number can be copied
|
||||||
and pasted into the terminal again.
|
and pasted into the terminal again.
|
||||||
*/
|
*/
|
||||||
pub fn to_pretty_string(&self, grouping: Grouping) -> String {
|
pub fn to_pretty_string(&self, grouping: Grouping) -> String {
|
||||||
match self {
|
match self {
|
||||||
Value::String(val) =>
|
Value::String(val) =>
|
||||||
@ -442,7 +491,7 @@ impl Clone for Value {
|
|||||||
Value::String(v) => Value::String(v.clone()),
|
Value::String(v) => Value::String(v.clone()),
|
||||||
Value::Integer(v) => Value::Integer(*v),
|
Value::Integer(v) => Value::Integer(*v),
|
||||||
Value::Time(v) => Value::Time(*v),
|
Value::Time(v) => Value::Time(*v),
|
||||||
Value::Field(v) => Value::Field(v.clone()),
|
Value::Symbol(v) => Value::Symbol(v.clone()),
|
||||||
Value::Glob(v) => Value::Glob(v.clone()),
|
Value::Glob(v) => Value::Glob(v.clone()),
|
||||||
Value::Regex(v, r) => Value::Regex(v.clone(), r.clone()),
|
Value::Regex(v, r) => Value::Regex(v.clone(), r.clone()),
|
||||||
Value::Command(v) => Value::Command(v.as_ref().copy()),
|
Value::Command(v) => Value::Command(v.as_ref().copy()),
|
||||||
@ -488,7 +537,7 @@ impl std::hash::Hash for Value {
|
|||||||
Value::String(v) => v.hash(state),
|
Value::String(v) => v.hash(state),
|
||||||
Value::Integer(v) => v.hash(state),
|
Value::Integer(v) => v.hash(state),
|
||||||
Value::Time(v) => v.hash(state),
|
Value::Time(v) => v.hash(state),
|
||||||
Value::Field(v) => v.hash(state),
|
Value::Symbol(v) => v.hash(state),
|
||||||
Value::Glob(v) => v.hash(state),
|
Value::Glob(v) => v.hash(state),
|
||||||
Value::Regex(v, _) => v.hash(state),
|
Value::Regex(v, _) => v.hash(state),
|
||||||
Value::Command(_) => {}
|
Value::Command(_) => {}
|
||||||
@ -530,7 +579,7 @@ impl std::cmp::PartialEq for Value {
|
|||||||
(Value::Integer(val1), Value::Integer(val2)) => val1 == val2,
|
(Value::Integer(val1), Value::Integer(val2)) => val1 == val2,
|
||||||
(Value::Time(val1), Value::Time(val2)) => val1 == val2,
|
(Value::Time(val1), Value::Time(val2)) => val1 == val2,
|
||||||
(Value::Duration(val1), Value::Duration(val2)) => val1 == val2,
|
(Value::Duration(val1), Value::Duration(val2)) => val1 == val2,
|
||||||
(Value::Field(val1), Value::Field(val2)) => val1 == val2,
|
(Value::Symbol(val1), Value::Symbol(val2)) => val1 == val2,
|
||||||
(Value::Glob(val1), Value::Glob(val2)) => val1 == val2,
|
(Value::Glob(val1), Value::Glob(val2)) => val1 == val2,
|
||||||
(Value::Regex(val1, _), Value::Regex(val2, _)) => val1 == val2,
|
(Value::Regex(val1, _), Value::Regex(val2, _)) => val1 == val2,
|
||||||
(Value::File(val1), Value::String(val2)) => {
|
(Value::File(val1), Value::String(val2)) => {
|
||||||
@ -569,7 +618,7 @@ impl std::cmp::PartialOrd for Value {
|
|||||||
(Value::Integer(val1), Value::Integer(val2)) => Some(val1.cmp(val2)),
|
(Value::Integer(val1), Value::Integer(val2)) => Some(val1.cmp(val2)),
|
||||||
(Value::Time(val1), Value::Time(val2)) => Some(val1.cmp(val2)),
|
(Value::Time(val1), Value::Time(val2)) => Some(val1.cmp(val2)),
|
||||||
(Value::Duration(val1), Value::Duration(val2)) => Some(val1.cmp(val2)),
|
(Value::Duration(val1), Value::Duration(val2)) => Some(val1.cmp(val2)),
|
||||||
(Value::Field(val1), Value::Field(val2)) => Some(val1.cmp(val2)),
|
(Value::Symbol(val1), Value::Symbol(val2)) => Some(val1.cmp(val2)),
|
||||||
(Value::Glob(val1), Value::Glob(val2)) => Some(val1.cmp(val2)),
|
(Value::Glob(val1), Value::Glob(val2)) => Some(val1.cmp(val2)),
|
||||||
(Value::Regex(val1, _), Value::Regex(val2, _)) => Some(val1.cmp(val2)),
|
(Value::Regex(val1, _), Value::Regex(val2, _)) => Some(val1.cmp(val2)),
|
||||||
(Value::File(val1), Value::File(val2)) => Some(val1.cmp(val2)),
|
(Value::File(val1), Value::File(val2)) => Some(val1.cmp(val2)),
|
||||||
@ -631,7 +680,7 @@ mod tests {
|
|||||||
assert_eq!(Value::string("1d").convert(ValueType::File).is_err(), false);
|
assert_eq!(Value::string("1d").convert(ValueType::File).is_err(), false);
|
||||||
assert_eq!(Value::string("1d").convert(ValueType::Time).is_err(), true);
|
assert_eq!(Value::string("1d").convert(ValueType::Time).is_err(), true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Value::string("fad").convert(ValueType::Field).is_err(),
|
Value::string("fad").convert(ValueType::Symbol).is_err(),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,39 @@
|
|||||||
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::empty_channel, lang::pipe::pipe,
|
||||||
lang::value::Value,
|
lang::value::Value,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use crate::lang::ast::{Location, TrackedString};
|
use crate::lang::ast::tracked_string::TrackedString;
|
||||||
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ValueDefinition {
|
pub enum ValueDefinition {
|
||||||
Value(Value, Location),
|
Value(Value, Location),
|
||||||
ClosureDefinition(Option<TrackedString>, Option<Vec<Parameter>>, Vec<Job>, Location),
|
ClosureDefinition(Option<TrackedString>, Option<Vec<Parameter>>, Vec<Job>, Location),
|
||||||
JobDefinition(Job),
|
JobDefinition(Job),
|
||||||
Label(TrackedString),
|
Identifier(TrackedString),
|
||||||
GetAttr(Box<ValueDefinition>, TrackedString),
|
GetAttr(Box<ValueDefinition>, TrackedString),
|
||||||
Path(Box<ValueDefinition>, TrackedString),
|
Path(Box<ValueDefinition>, TrackedString),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_get(f: &str) -> Option<Value> {
|
|
||||||
let p = PathBuf::from(f);
|
|
||||||
if p.exists() {
|
|
||||||
Some(Value::File(p))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueDefinition {
|
impl ValueDefinition {
|
||||||
pub fn location(&self) -> Location {
|
pub fn location(&self) -> Location {
|
||||||
match self {
|
match self {
|
||||||
ValueDefinition::Value(_, l) => *l,
|
ValueDefinition::Value(_, l) => *l,
|
||||||
ValueDefinition::ClosureDefinition(_, _, _, l) => *l,
|
ValueDefinition::ClosureDefinition(_, _, _, l) => *l,
|
||||||
ValueDefinition::JobDefinition(j) => j.location(),
|
ValueDefinition::JobDefinition(j) => j.location(),
|
||||||
ValueDefinition::Label(l) => l.location,
|
ValueDefinition::Identifier(l) => l.location,
|
||||||
ValueDefinition::GetAttr(p, a) |
|
ValueDefinition::GetAttr(p, a) |
|
||||||
ValueDefinition::Path(p, a)=> p.location().union(a.location),
|
ValueDefinition::Path(p, a)=> p.location().union(a.location),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn can_block(&self, _arg: &[ArgumentDefinition], context: &mut CompileContext) -> bool {
|
pub fn can_block(&self, context: &mut CompileContext) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ValueDefinition::JobDefinition(j) => j.can_block(context),
|
ValueDefinition::JobDefinition(j) => j.can_block(context),
|
||||||
ValueDefinition::GetAttr(_inner1, _inner2) => true,
|
ValueDefinition::GetAttr(_inner1, _inner2) => true,
|
||||||
@ -49,33 +41,21 @@ impl ValueDefinition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_unbound(
|
pub fn eval_and_bind(&self, context: &mut CompileContext) -> CrushResult<Value> {
|
||||||
&self,
|
let (t, v) = self.eval(context)?;
|
||||||
context: &mut CompileContext,
|
|
||||||
) -> CrushResult<(Option<Value>, Value)> {
|
|
||||||
self.compile_internal(context, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compile_bound(&self, context: &mut CompileContext) -> CrushResult<Value> {
|
|
||||||
let (t, v) = self.compile_internal(context, true)?;
|
|
||||||
|
|
||||||
Ok(t.map(|tt| v.clone().bind(tt)).unwrap_or(v))
|
Ok(t.map(|tt| v.clone().bind(tt)).unwrap_or(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile_internal(
|
pub fn eval(
|
||||||
&self,
|
&self,
|
||||||
context: &mut CompileContext,
|
context: &mut CompileContext,
|
||||||
can_block: bool,
|
|
||||||
) -> CrushResult<(Option<Value>, Value)> {
|
) -> CrushResult<(Option<Value>, Value)> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
ValueDefinition::Value(v, _) => (None, v.clone()),
|
ValueDefinition::Value(v, _) => (None, v.clone()),
|
||||||
ValueDefinition::JobDefinition(def) => {
|
ValueDefinition::JobDefinition(def) => {
|
||||||
let first_input = empty_channel();
|
let first_input = empty_channel();
|
||||||
let (last_output, last_input) = pipe();
|
let (last_output, last_input) = pipe();
|
||||||
if !can_block {
|
def.eval(context.job_context(first_input, last_output))?;
|
||||||
return block_error();
|
|
||||||
}
|
|
||||||
def.invoke(context.job_context(first_input, last_output))?;
|
|
||||||
(None, last_input.recv()?)
|
(None, last_input.recv()?)
|
||||||
}
|
}
|
||||||
ValueDefinition::ClosureDefinition(name, p, c, _) => (
|
ValueDefinition::ClosureDefinition(name, p, c, _) => (
|
||||||
@ -88,23 +68,20 @@ impl ValueDefinition {
|
|||||||
vec![],
|
vec![],
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
ValueDefinition::Label(s) => (
|
ValueDefinition::Identifier(s) => (
|
||||||
None,
|
None,
|
||||||
mandate(
|
mandate(
|
||||||
context.env.get(&s.string)?.or_else(|| file_get(&s.string)),
|
context.env.get(&s.string)?,
|
||||||
&format!("Unknown variable {}", self),
|
&format!("Unknown variable {}", self),
|
||||||
)?,
|
)?,
|
||||||
),
|
),
|
||||||
|
|
||||||
ValueDefinition::GetAttr(parent_def, entry) => {
|
ValueDefinition::GetAttr(parent_def, entry) => {
|
||||||
let (grand_parent, mut parent) = parent_def.compile_internal(context, can_block)?;
|
let (grand_parent, mut parent) = parent_def.eval(context)?;
|
||||||
parent = if let Value::Command(parent_cmd) = &parent {
|
parent = if let Value::Command(parent_cmd) = &parent {
|
||||||
if !can_block {
|
|
||||||
return block_error();
|
|
||||||
}
|
|
||||||
let first_input = empty_channel();
|
let first_input = empty_channel();
|
||||||
let (last_output, last_input) = pipe();
|
let (last_output, last_input) = pipe();
|
||||||
parent_cmd.invoke(
|
parent_cmd.eval(
|
||||||
context
|
context
|
||||||
.job_context(first_input, last_output)
|
.job_context(first_input, last_output)
|
||||||
.command_context(vec![], grand_parent),
|
.command_context(vec![], grand_parent),
|
||||||
@ -125,7 +102,7 @@ impl ValueDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ValueDefinition::Path(parent_def, entry) => {
|
ValueDefinition::Path(parent_def, entry) => {
|
||||||
let parent = parent_def.compile_internal(context, can_block)?.1;
|
let parent = parent_def.eval(context)?.1;
|
||||||
let val = mandate(
|
let val = mandate(
|
||||||
parent.path(&entry.string),
|
parent.path(&entry.string),
|
||||||
&format!("Missing path entry {} in {}", entry, parent_def),
|
&format!("Missing path entry {} in {}", entry, parent_def),
|
||||||
@ -140,9 +117,9 @@ impl Display for ValueDefinition {
|
|||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match &self {
|
match &self {
|
||||||
ValueDefinition::Value(v, _location) => v.fmt(f),
|
ValueDefinition::Value(v, _location) => v.fmt(f),
|
||||||
ValueDefinition::Label(v) => v.fmt(f),
|
ValueDefinition::Identifier(v) => v.fmt(f),
|
||||||
ValueDefinition::ClosureDefinition(_, _, _, _location) => f.write_str("<closure>"),
|
ValueDefinition::ClosureDefinition(_, _, _, _location) => f.write_str("<closure>"),
|
||||||
ValueDefinition::JobDefinition(_) => f.write_str("<job>"),
|
ValueDefinition::JobDefinition(j) => j.fmt(f),
|
||||||
ValueDefinition::GetAttr(v, l) => {
|
ValueDefinition::GetAttr(v, l) => {
|
||||||
v.fmt(f)?;
|
v.fmt(f)?;
|
||||||
f.write_str(":")?;
|
f.write_str(":")?;
|
||||||
|
@ -17,7 +17,7 @@ pub enum ValueType {
|
|||||||
Integer,
|
Integer,
|
||||||
Time,
|
Time,
|
||||||
Duration,
|
Duration,
|
||||||
Field,
|
Symbol,
|
||||||
Glob,
|
Glob,
|
||||||
Regex,
|
Regex,
|
||||||
Command,
|
Command,
|
||||||
@ -82,7 +82,7 @@ impl ValueType {
|
|||||||
| ValueType::Integer
|
| ValueType::Integer
|
||||||
| ValueType::Time
|
| ValueType::Time
|
||||||
| ValueType::Duration
|
| ValueType::Duration
|
||||||
| ValueType::Field
|
| ValueType::Symbol
|
||||||
| ValueType::Glob
|
| ValueType::Glob
|
||||||
| ValueType::Regex
|
| ValueType::Regex
|
||||||
| ValueType::Command
|
| ValueType::Command
|
||||||
@ -131,7 +131,7 @@ impl ValueType {
|
|||||||
Ok(n) => Ok(Value::Integer(n)),
|
Ok(n) => Ok(Value::Integer(n)),
|
||||||
Err(e) => error(e.to_string().as_str()),
|
Err(e) => error(e.to_string().as_str()),
|
||||||
},
|
},
|
||||||
ValueType::Field => Ok(Value::Field(mandate(parse_name(s), "Invalid field name")?)),
|
ValueType::Symbol => Ok(Value::Symbol(s.to_string())),
|
||||||
ValueType::Glob => Ok(Value::Glob(Glob::new(s))),
|
ValueType::Glob => Ok(Value::Glob(Glob::new(s))),
|
||||||
ValueType::Regex => Ok(Value::Regex(s.to_string(), to_crush_error(Regex::new(s))?)),
|
ValueType::Regex => Ok(Value::Regex(s.to_string(), to_crush_error(Regex::new(s))?)),
|
||||||
ValueType::File => Ok(Value::string(s)),
|
ValueType::File => Ok(Value::string(s)),
|
||||||
@ -155,7 +155,7 @@ impl Help for ValueType {
|
|||||||
ValueType::Integer => "A numeric type representing an integer number.",
|
ValueType::Integer => "A numeric type representing an integer number.",
|
||||||
ValueType::Time => "A point in time with nanosecond precision",
|
ValueType::Time => "A point in time with nanosecond precision",
|
||||||
ValueType::Duration => "A difference between two points in time",
|
ValueType::Duration => "A difference between two points in time",
|
||||||
ValueType::Field => "A field is used to represent a path into a datastructure",
|
ValueType::Symbol => "A field is used to represent a path into a datastructure",
|
||||||
ValueType::Glob => "A pattern containing wildcards",
|
ValueType::Glob => "A pattern containing wildcards",
|
||||||
ValueType::Regex => "An advanced pattern that can be used for matching and replacing",
|
ValueType::Regex => "An advanced pattern that can be used for matching and replacing",
|
||||||
ValueType::Command => "A piece fo code that can be called",
|
ValueType::Command => "A piece fo code that can be called",
|
||||||
@ -184,8 +184,8 @@ impl Help for ValueType {
|
|||||||
let mut lines = match self {
|
let mut lines = match self {
|
||||||
ValueType::Time => {
|
ValueType::Time => {
|
||||||
vec![" All time instances use the local time zone.\n".to_string()]
|
vec![" All time instances use the local time zone.\n".to_string()]
|
||||||
},
|
}
|
||||||
_ => {Vec::new()}
|
_ => { Vec::new() }
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut keys: Vec<_> = self.fields().into_iter().collect();
|
let mut keys: Vec<_> = self.fields().into_iter().collect();
|
||||||
@ -218,7 +218,7 @@ impl Display for ValueType {
|
|||||||
ValueType::Integer => f.write_str("integer"),
|
ValueType::Integer => f.write_str("integer"),
|
||||||
ValueType::Time => f.write_str("time"),
|
ValueType::Time => f.write_str("time"),
|
||||||
ValueType::Duration => f.write_str("duration"),
|
ValueType::Duration => f.write_str("duration"),
|
||||||
ValueType::Field => f.write_str("field"),
|
ValueType::Symbol => f.write_str("field"),
|
||||||
ValueType::Glob => f.write_str("glob"),
|
ValueType::Glob => f.write_str("glob"),
|
||||||
ValueType::Regex => f.write_str("regex"),
|
ValueType::Regex => f.write_str("regex"),
|
||||||
ValueType::Command => f.write_str("command"),
|
ValueType::Command => f.write_str("command"),
|
||||||
|
@ -17,7 +17,7 @@ pub fn and(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
Value::Command(c) => {
|
Value::Command(c) => {
|
||||||
let (sender, receiver) = pipe();
|
let (sender, receiver) = pipe();
|
||||||
let cc = context.empty().with_output(sender);
|
let cc = context.empty().with_output(sender);
|
||||||
c.invoke(cc)?;
|
c.eval(cc)?;
|
||||||
match receiver.recv()? {
|
match receiver.recv()? {
|
||||||
Value::Bool(b) => {
|
Value::Bool(b) => {
|
||||||
if !b {
|
if !b {
|
||||||
@ -48,7 +48,7 @@ pub fn or(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
Value::Command(c) => {
|
Value::Command(c) => {
|
||||||
let (sender, receiver) = pipe();
|
let (sender, receiver) = pipe();
|
||||||
let cc = context.empty().with_output(sender);
|
let cc = context.empty().with_output(sender);
|
||||||
c.invoke(cc)?;
|
c.eval(cc)?;
|
||||||
match receiver.recv()? {
|
match receiver.recv()? {
|
||||||
Value::Bool(b) => {
|
Value::Bool(b) => {
|
||||||
if b {
|
if b {
|
||||||
|
@ -3,16 +3,30 @@ use crate::lang::errors::{mandate, CrushResult};
|
|||||||
use crate::lang::execution_context::{ArgumentVector, CommandContext};
|
use crate::lang::execution_context::{ArgumentVector, CommandContext};
|
||||||
use crate::lang::value::Value;
|
use crate::lang::value::Value;
|
||||||
use crate::lang::data::r#struct::Struct;
|
use crate::lang::data::r#struct::Struct;
|
||||||
|
use crate::lang::value::ValueType;
|
||||||
|
use crate::lang::data::table::ColumnType;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use crate::data::table::Row;
|
||||||
|
use crate::lang::command::OutputType::Known;
|
||||||
|
use crate::lang::pipe::pipe;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref OUTPUT_TYPE: Vec<ColumnType> = vec![
|
||||||
|
ColumnType::new("value", ValueType::Any),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
|
pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
|
||||||
context.output.send(Value::Empty())?;
|
let output = context.output.initialize(OUTPUT_TYPE.clone())?;
|
||||||
|
let (sender, receiver) = pipe();
|
||||||
|
|
||||||
context.arguments.check_len(2)?;
|
context.arguments.check_len(2)?;
|
||||||
|
|
||||||
let location = context.arguments[0].location;
|
let location = context.arguments[0].location;
|
||||||
let body = context.arguments.command(1)?;
|
let body = context.arguments.command(1)?;
|
||||||
let iter = context.arguments.remove(0);
|
let iter = context.arguments.remove(0);
|
||||||
let name = iter.argument_type;
|
let name = iter.argument_type;
|
||||||
let mut input = mandate(iter.value.stream(), "Expected a stream")?;
|
let mut input = mandate(iter.value.stream()?, "Expected a stream")?;
|
||||||
|
|
||||||
while let Ok(line) = input.read() {
|
while let Ok(line) = input.read() {
|
||||||
let env = context.scope.create_child(&context.scope, true);
|
let env = context.scope.create_child(&context.scope, true);
|
||||||
@ -38,7 +52,8 @@ pub fn r#for(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
body.invoke(context.empty())?;
|
body.eval(context.empty().with_scope(env.clone()).with_args(arguments, None).with_output(sender.clone()))?;
|
||||||
|
output.send(Row::new(vec![receiver.recv()?]))?;
|
||||||
if env.is_stopped() {
|
if env.is_stopped() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,10 @@ fn r#if(context: CommandContext) -> CrushResult<()> {
|
|||||||
let cfg: If = If::parse(context.arguments.clone(), &context.global_state.printer())?;
|
let cfg: If = If::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||||
|
|
||||||
if cfg.condition {
|
if cfg.condition {
|
||||||
cfg.true_clause.invoke(context.with_args(vec![], None))
|
cfg.true_clause.eval(context.with_args(vec![], None))
|
||||||
} else {
|
} else {
|
||||||
cfg.false_clause
|
cfg.false_clause
|
||||||
.map(|v| v.invoke(context.with_args(vec![], None)))
|
.map(|v| v.eval(context.with_args(vec![], None)))
|
||||||
.unwrap_or(Ok(()))
|
.unwrap_or(Ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
use crate::lang::command::Command;
|
use crate::lang::command::Command;
|
||||||
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::value::ValueType;
|
||||||
|
use crate::lang::data::table::ColumnType;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use crate::data::table::Row;
|
||||||
|
use crate::lang::pipe::pipe;
|
||||||
|
use crate::lang::command::OutputType::Known;
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref OUTPUT_TYPE: Vec<ColumnType> = vec![
|
||||||
|
ColumnType::new("value", ValueType::Any),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
r#loop,
|
r#loop,
|
||||||
condition = true,
|
condition = true,
|
||||||
|
output = Known(ValueType::TableInputStream(OUTPUT_TYPE.clone())),
|
||||||
short = "Repeatedly execute the body until the break command is called.",
|
short = "Repeatedly execute the body until the break command is called.",
|
||||||
example = "loop {\n if (i_am_tired) {\n break\n }\n echo \"Working\"\n }"
|
example = "loop {\n if (i_am_tired) {\n break\n }\n echo \"Working\"\n }"
|
||||||
)]
|
)]
|
||||||
@ -16,10 +29,12 @@ pub struct Loop {
|
|||||||
|
|
||||||
fn r#loop(context: CommandContext) -> CrushResult<()> {
|
fn r#loop(context: CommandContext) -> CrushResult<()> {
|
||||||
let cfg: Loop = Loop::parse(context.arguments.clone(), &context.global_state.printer())?;
|
let cfg: Loop = Loop::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||||
context.output.initialize(vec![])?;
|
let output = context.output.initialize(OUTPUT_TYPE.clone())?;
|
||||||
|
let (sender, receiver) = pipe();
|
||||||
loop {
|
loop {
|
||||||
let env = context.scope.create_child(&context.scope, true);
|
let env = context.scope.create_child(&context.scope, true);
|
||||||
cfg.body.invoke(context.empty())?;
|
cfg.body.eval(context.empty().with_scope(env.clone()).with_output(sender.clone()))?;
|
||||||
|
output.send(Row::new(vec![receiver.recv()?]))?;
|
||||||
if env.is_stopped() {
|
if env.is_stopped() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ example = "pipe := ((table_input_stream value=integer):pipe)\n _1 := (seq 100
|
|||||||
struct Fg {}
|
struct Fg {}
|
||||||
|
|
||||||
fn fg(context: CommandContext) -> CrushResult<()> {
|
fn fg(context: CommandContext) -> CrushResult<()> {
|
||||||
let mut result_stream = mandate(context.input.recv()?.stream(), "Invalid input")?;
|
let mut result_stream = mandate(context.input.recv()?.stream()?, "Invalid input")?;
|
||||||
let mut result: Vec<Value> = result_stream.read()?.into();
|
let mut result: Vec<Value> = result_stream.read()?.into();
|
||||||
if result.len() != 1 {
|
if result.len() != 1 {
|
||||||
data_error("Expected a single row, single column result")
|
data_error("Expected a single row, single column result")
|
||||||
|
@ -2,12 +2,24 @@ use crate::lang::command::Command;
|
|||||||
use crate::lang::errors::{data_error, CrushResult};
|
use crate::lang::errors::{data_error, CrushResult};
|
||||||
use crate::lang::execution_context::CommandContext;
|
use crate::lang::execution_context::CommandContext;
|
||||||
use crate::lang::value::Value;
|
use crate::lang::value::Value;
|
||||||
|
use crate::lang::value::ValueType;
|
||||||
|
use crate::lang::data::table::ColumnType;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use crate::data::table::Row;
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
|
use crate::lang::command::OutputType::Known;
|
||||||
use crate::lang::pipe::pipe;
|
use crate::lang::pipe::pipe;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref OUTPUT_TYPE: Vec<ColumnType> = vec![
|
||||||
|
ColumnType::new("value", ValueType::Any),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
r#while,
|
r#while,
|
||||||
condition = true,
|
condition = true,
|
||||||
|
output = Known(ValueType::TableInputStream(OUTPUT_TYPE.clone())),
|
||||||
short = "Repeatedly execute the body for as long the condition is met.",
|
short = "Repeatedly execute the body for as long the condition is met.",
|
||||||
long = "The loop body is optional. If not specified, the condition is executed until it returns false.\n This effectively means that the condition becomes the body, and the loop break check comes at\n the end of the loop.",
|
long = "The loop body is optional. If not specified, the condition is executed until it returns false.\n This effectively means that the condition becomes the body, and the loop break check comes at\n the end of the loop.",
|
||||||
example = "while {./some_file:exists} {echo \"hello\"}"
|
example = "while {./some_file:exists} {echo \"hello\"}"
|
||||||
@ -20,14 +32,15 @@ pub struct While {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn r#while(mut context: CommandContext) -> CrushResult<()> {
|
fn r#while(mut context: CommandContext) -> CrushResult<()> {
|
||||||
context.output.initialize(vec![])?;
|
let output = context.output.initialize(OUTPUT_TYPE.clone())?;
|
||||||
|
let (body_sender, body_receiver) = pipe();
|
||||||
let cfg: While = While::parse(context.remove_arguments(), &context.global_state.printer())?;
|
let cfg: While = While::parse(context.remove_arguments(), &context.global_state.printer())?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (sender, receiver) = pipe();
|
let (sender, receiver) = pipe();
|
||||||
|
|
||||||
let cond_env = context.scope.create_child(&context.scope, true);
|
let cond_env = context.scope.create_child(&context.scope, true);
|
||||||
cfg.condition.invoke(context.empty().with_output(sender))?;
|
cfg.condition.eval(context.empty().with_scope(cond_env.clone()).with_output(sender))?;
|
||||||
if cond_env.is_stopped() {
|
if cond_env.is_stopped() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -36,7 +49,8 @@ fn r#while(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
Value::Bool(true) => match &cfg.body {
|
Value::Bool(true) => match &cfg.body {
|
||||||
Some(body) => {
|
Some(body) => {
|
||||||
let body_env = context.scope.create_child(&context.scope, true);
|
let body_env = context.scope.create_child(&context.scope, true);
|
||||||
body.invoke(context.empty())?;
|
body.eval(context.empty().with_scope(body_env.clone()).with_output(body_sender.clone()))?;
|
||||||
|
output.send(Row::new(vec![body_receiver.recv()?]))?;
|
||||||
if body_env.is_stopped() {
|
if body_env.is_stopped() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use nix::unistd::Pid;
|
|||||||
use crate::lang::data::dict::Dict;
|
use crate::lang::data::dict::Dict;
|
||||||
use std::env;
|
use std::env;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use crate::data::list::List;
|
||||||
use crate::lang::command::Command;
|
use crate::lang::command::Command;
|
||||||
|
|
||||||
fn make_env() -> Value {
|
fn make_env() -> Value {
|
||||||
@ -19,6 +20,11 @@ fn make_env() -> Value {
|
|||||||
Value::Dict(e)
|
Value::Dict(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn make_arguments() -> Value {
|
||||||
|
Value::List(List::new(ValueType::String, env::args().map(|a| {Value::string(a)}).collect()))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref THREADS_OUTPUT_TYPE: Vec<ColumnType> = vec![
|
static ref THREADS_OUTPUT_TYPE: Vec<ColumnType> = vec![
|
||||||
ColumnType::new("created", ValueType::Time),
|
ColumnType::new("created", ValueType::Time),
|
||||||
@ -65,6 +71,33 @@ fn prompt(context: CommandContext) -> CrushResult<()> {
|
|||||||
context.output.send(Value::Empty())
|
context.output.send(Value::Empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref JOBS_OUTPUT_TYPE: Vec<ColumnType> = vec![
|
||||||
|
ColumnType::new("id", ValueType::Integer),
|
||||||
|
ColumnType::new("description", ValueType::String),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[signature(
|
||||||
|
jobs,
|
||||||
|
can_block = false,
|
||||||
|
short = "List running jobs",
|
||||||
|
output = Known(ValueType::TableInputStream(JOBS_OUTPUT_TYPE.clone())),
|
||||||
|
long = "All currently running jobs")]
|
||||||
|
struct Jobs {}
|
||||||
|
|
||||||
|
fn jobs(context: CommandContext) -> CrushResult<()> {
|
||||||
|
let output = context.output.initialize(JOBS_OUTPUT_TYPE.clone())?;
|
||||||
|
for job in context.global_state.jobs() {
|
||||||
|
output.send(Row::new(vec![
|
||||||
|
Value::Integer(usize::from(job.id) as i128),
|
||||||
|
Value::string(job.description),
|
||||||
|
]))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
mod locale {
|
mod locale {
|
||||||
use super::*;
|
use super::*;
|
||||||
use num_format::SystemLocale;
|
use num_format::SystemLocale;
|
||||||
@ -165,9 +198,11 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
|||||||
crush.declare("highlight", Value::Dict(highlight))?;
|
crush.declare("highlight", Value::Dict(highlight))?;
|
||||||
|
|
||||||
crush.declare("env", make_env())?;
|
crush.declare("env", make_env())?;
|
||||||
|
crush.declare("arguments", make_arguments())?;
|
||||||
Prompt::declare(crush)?;
|
Prompt::declare(crush)?;
|
||||||
Threads::declare(crush)?;
|
Threads::declare(crush)?;
|
||||||
Exit::declare(crush)?;
|
Exit::declare(crush)?;
|
||||||
|
Jobs::declare(crush)?;
|
||||||
|
|
||||||
crush.create_namespace(
|
crush.create_namespace(
|
||||||
"locale",
|
"locale",
|
||||||
|
@ -17,7 +17,7 @@ use std::convert::TryFrom;
|
|||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
use std::str::Chars;
|
use std::str::Chars;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
struct DBusThing {
|
struct DBusThing {
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
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, error};
|
||||||
use crate::lang::execution_context::CommandContext;
|
use crate::lang::execution_context::CommandContext;
|
||||||
use crate::lang::help::Help;
|
use crate::lang::help::Help;
|
||||||
use crate::lang::printer::Printer;
|
use crate::lang::printer::Printer;
|
||||||
@ -104,6 +104,10 @@ members of a value, write "dir <value>".
|
|||||||
}
|
}
|
||||||
Some(v) => {
|
Some(v) => {
|
||||||
match v {
|
match v {
|
||||||
|
Value::Symbol(f) => match &context.scope.get_calling_scope()?.get(&f)? {
|
||||||
|
None => error(format!("Unknown identifier {}", &f))?,
|
||||||
|
Some(v) => halp(v, &context.global_state.printer()),
|
||||||
|
},
|
||||||
Value::Command(cmd) => halp(cmd.help(), &context.global_state.printer()),
|
Value::Command(cmd) => halp(cmd.help(), &context.global_state.printer()),
|
||||||
Value::Type(t) => halp(&t, &context.global_state.printer()),
|
Value::Type(t) => halp(&t, &context.global_state.printer()),
|
||||||
v => halp(&v, &context.global_state.printer()),
|
v => halp(&v, &context.global_state.printer()),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::lang::errors::{to_crush_error, CrushResult};
|
use crate::lang::errors::{to_crush_error, CrushResult, error};
|
||||||
use crate::lang::execution_context::CommandContext;
|
use crate::lang::execution_context::CommandContext;
|
||||||
use crate::lang::data::r#struct::Struct;
|
use crate::lang::data::r#struct::Struct;
|
||||||
use crate::lang::data::scope::Scope;
|
use crate::lang::data::scope::Scope;
|
||||||
@ -12,7 +12,13 @@ use battery::State;
|
|||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use crate::lang::command::OutputType::Known;
|
use crate::lang::command::OutputType::Known;
|
||||||
|
|
||||||
#[signature(name, can_block = false, short = "name of this host")]
|
extern crate uptime_lib;
|
||||||
|
|
||||||
|
#[signature(
|
||||||
|
name,
|
||||||
|
can_block = false,
|
||||||
|
output = Known(ValueType::String),
|
||||||
|
short = "name of this host")]
|
||||||
struct Name {}
|
struct Name {}
|
||||||
|
|
||||||
fn name(context: CommandContext) -> CrushResult<()> {
|
fn name(context: CommandContext) -> CrushResult<()> {
|
||||||
@ -34,14 +40,18 @@ lazy_static! {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#[signature(uptime, can_block = false, short = "uptime of this host")]
|
#[signature(
|
||||||
|
uptime,
|
||||||
|
can_block = false,
|
||||||
|
output = Known(ValueType::Duration),
|
||||||
|
short = "uptime of this host")]
|
||||||
struct Uptime {}
|
struct Uptime {}
|
||||||
|
|
||||||
fn uptime(context: CommandContext) -> CrushResult<()> {
|
fn uptime(context: CommandContext) -> CrushResult<()> {
|
||||||
Ok(())
|
match uptime_lib::get() {
|
||||||
// context
|
Ok(d) => context.output.send(Value::Duration(Duration::nanoseconds(i64::try_from(d.as_nanos()).unwrap()))),
|
||||||
// .output
|
Err(e) => error(e),
|
||||||
// .send(Value::Duration(Duration::seconds(to_crush_error(psutil::host::uptime()))))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -86,10 +96,14 @@ fn battery(context: CommandContext) -> CrushResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[signature(mem, can_block = false, short = "memory usage of this host.")]
|
#[signature(
|
||||||
struct Mem {}
|
memory,
|
||||||
|
can_block = false,
|
||||||
|
output = Known(ValueType::Struct),
|
||||||
|
short = "memory usage of this host.")]
|
||||||
|
struct Memory {}
|
||||||
|
|
||||||
fn mem(context: CommandContext) -> CrushResult<()> {
|
fn memory(context: CommandContext) -> CrushResult<()> {
|
||||||
let mem = to_crush_error(sys_info::mem_info())?;
|
let mem = to_crush_error(sys_info::mem_info())?;
|
||||||
context.output.send(Value::Struct(Struct::new(
|
context.output.send(Value::Struct(Struct::new(
|
||||||
vec![
|
vec![
|
||||||
@ -114,7 +128,11 @@ fn mem(context: CommandContext) -> CrushResult<()> {
|
|||||||
mod os {
|
mod os {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[signature(name, can_block = false, short = "name of the operating system")]
|
#[signature(
|
||||||
|
name,
|
||||||
|
can_block = false,
|
||||||
|
output = Known(ValueType::String),
|
||||||
|
short = "name of the operating system")]
|
||||||
pub struct Name {}
|
pub struct Name {}
|
||||||
|
|
||||||
fn name(context: CommandContext) -> CrushResult<()> {
|
fn name(context: CommandContext) -> CrushResult<()> {
|
||||||
@ -124,9 +142,10 @@ mod os {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
version,
|
version,
|
||||||
can_block = false,
|
can_block = false,
|
||||||
short = "version of the operating system kernel"
|
output = Known(ValueType::String),
|
||||||
|
short = "version of the operating system kernel"
|
||||||
)]
|
)]
|
||||||
pub struct Version {}
|
pub struct Version {}
|
||||||
|
|
||||||
@ -140,7 +159,11 @@ mod os {
|
|||||||
mod cpu {
|
mod cpu {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[signature(count, can_block = false, short = "number of CPU cores")]
|
#[signature(
|
||||||
|
count,
|
||||||
|
can_block = false,
|
||||||
|
output = Known(ValueType::Integer),
|
||||||
|
short = "number of CPU cores")]
|
||||||
pub struct Count {}
|
pub struct Count {}
|
||||||
|
|
||||||
fn count(context: CommandContext) -> CrushResult<()> {
|
fn count(context: CommandContext) -> CrushResult<()> {
|
||||||
@ -149,7 +172,11 @@ mod cpu {
|
|||||||
.send(Value::Integer(to_crush_error(sys_info::cpu_num())? as i128))
|
.send(Value::Integer(to_crush_error(sys_info::cpu_num())? as i128))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[signature(load, can_block = false, short = "current CPU load")]
|
#[signature(
|
||||||
|
load,
|
||||||
|
can_block = false,
|
||||||
|
output = Known(ValueType::Struct),
|
||||||
|
short = "current CPU load")]
|
||||||
pub struct Load {}
|
pub struct Load {}
|
||||||
|
|
||||||
fn load(context: CommandContext) -> CrushResult<()> {
|
fn load(context: CommandContext) -> CrushResult<()> {
|
||||||
@ -164,7 +191,11 @@ mod cpu {
|
|||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[signature(speed, can_block = false, short = "current CPU frequency")]
|
#[signature(
|
||||||
|
speed,
|
||||||
|
can_block = false,
|
||||||
|
output = Known(ValueType::Integer),
|
||||||
|
short = "current CPU frequency")]
|
||||||
pub struct Speed {}
|
pub struct Speed {}
|
||||||
|
|
||||||
fn speed(context: CommandContext) -> CrushResult<()> {
|
fn speed(context: CommandContext) -> CrushResult<()> {
|
||||||
@ -180,7 +211,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
|||||||
"Metadata about this host",
|
"Metadata about this host",
|
||||||
Box::new(move |host| {
|
Box::new(move |host| {
|
||||||
Battery::declare(host)?;
|
Battery::declare(host)?;
|
||||||
Mem::declare(host)?;
|
Memory::declare(host)?;
|
||||||
Name::declare(host)?;
|
Name::declare(host)?;
|
||||||
Uptime::declare(host)?;
|
Uptime::declare(host)?;
|
||||||
host.create_namespace(
|
host.create_namespace(
|
||||||
|
@ -66,7 +66,7 @@ fn from(context: CommandContext) -> CrushResult<()> {
|
|||||||
skipped += 1;
|
skipped += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let line_without_newline = &line[0..line.len() - 1];
|
let line_without_newline = if line.ends_with('\n') {&line[0..line.len() - 1]} else {&line};
|
||||||
let mut split: Vec<&str> = line_without_newline
|
let mut split: Vec<&str> = line_without_newline
|
||||||
.split(separator)
|
.split(separator)
|
||||||
.map(|s| trim.map(|c| s.trim_matches(c)).unwrap_or(s))
|
.map(|s| trim.map(|c| s.trim_matches(c)).unwrap_or(s))
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
use crate::lang::execution_context::CommandContext;
|
use crate::lang::execution_context::CommandContext;
|
||||||
use crate::{
|
use crate::lang::errors::CrushError;
|
||||||
lang::errors::CrushError,
|
use crate::lang::{data::table::Row, value::Value, value::ValueType};
|
||||||
lang::{data::table::Row, value::Value, value::ValueType},
|
|
||||||
};
|
|
||||||
use std::io::{BufReader, Write};
|
use std::io::{BufReader, Write};
|
||||||
|
|
||||||
use crate::lang::command::OutputType::Unknown;
|
use crate::lang::command::OutputType::Unknown;
|
||||||
|
@ -64,7 +64,7 @@ struct To {
|
|||||||
pub fn to(context: CommandContext) -> CrushResult<()> {
|
pub fn to(context: CommandContext) -> CrushResult<()> {
|
||||||
let cfg: To = To::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: To = To::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 out = cfg.file.writer(context.output)?;
|
let mut out = cfg.file.writer(context.output)?;
|
||||||
if input.types().len() != 1 || input.types()[0].cell_type != ValueType::String {
|
if input.types().len() != 1 || input.types()[0].cell_type != ValueType::String {
|
||||||
|
@ -3,7 +3,7 @@ use crate::lang::errors::{argument_error_legacy, data_error, mandate, CrushResul
|
|||||||
use crate::lang::data::list::List;
|
use crate::lang::data::list::List;
|
||||||
use crate::lang::pretty::PrettyPrinter;
|
use crate::lang::pretty::PrettyPrinter;
|
||||||
use crate::lang::data::scope::Scope;
|
use crate::lang::data::scope::Scope;
|
||||||
use crate::lang::value::{Field, ValueType};
|
use crate::lang::value::ValueType;
|
||||||
use crate::lang::{execution_context::CommandContext, value::Value};
|
use crate::lang::{execution_context::CommandContext, value::Value};
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
use rustyline::Editor;
|
use rustyline::Editor;
|
||||||
@ -87,11 +87,11 @@ 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.")]
|
||||||
field: Field,
|
field: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn member(context: CommandContext) -> CrushResult<()> {
|
fn member(context: CommandContext) -> CrushResult<()> {
|
||||||
@ -101,8 +101,8 @@ fn member(context: CommandContext) -> CrushResult<()> {
|
|||||||
}
|
}
|
||||||
match context.input.recv()? {
|
match context.input.recv()? {
|
||||||
Value::Struct(s) => context.output.send(mandate(
|
Value::Struct(s) => context.output.send(mandate(
|
||||||
s.get(&cfg.field[0]),
|
s.get(&cfg.field),
|
||||||
format!("Unknown field \"{}\"", cfg.field[0]).as_str(),
|
format!("Unknown field \"{}\"", cfg.field).as_str(),
|
||||||
)?),
|
)?),
|
||||||
_ => data_error("Expected a struct"),
|
_ => data_error("Expected a struct"),
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ to,
|
|||||||
can_block = true,
|
can_block = true,
|
||||||
output = Unknown,
|
output = Unknown,
|
||||||
short = "Serialize to pup format",
|
short = "Serialize to pup format",
|
||||||
long = "Pup is the native crush serialization format. All pup types, including",
|
long = "Pup is the native crush serialization format. All Crush types, including",
|
||||||
long = "lambdas can be serialized to this format.",
|
long = "lambdas can be serialized to this format.",
|
||||||
example = "ls | pup:to")]
|
example = "ls | pup:to")]
|
||||||
struct To {
|
struct To {
|
||||||
|
@ -12,7 +12,7 @@ use std::io::{BufRead, BufReader};
|
|||||||
#[signature(
|
#[signature(
|
||||||
from,
|
from,
|
||||||
can_block = true,
|
can_block = true,
|
||||||
short = "Read specified files (or input) as a table, split on the specified separator"
|
short = "Read specified files (or input) as a table, split on the specified separator characters.",
|
||||||
)]
|
)]
|
||||||
struct From {
|
struct From {
|
||||||
#[unnamed()]
|
#[unnamed()]
|
||||||
@ -22,7 +22,8 @@ struct From {
|
|||||||
separator: String,
|
separator: String,
|
||||||
#[description("characters to trim from start and end of each token.")]
|
#[description("characters to trim from start and end of each token.")]
|
||||||
trim: Option<String>,
|
trim: Option<String>,
|
||||||
#[description("if false, discard empty tokens.")]
|
#[default(false)]
|
||||||
|
#[description("allow empty tokens.")]
|
||||||
allow_empty: bool,
|
allow_empty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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};
|
||||||
@ -214,8 +212,8 @@ mod macos {
|
|||||||
Value::Integer(i128::from(curr_task.ptinfo.pti_virtual_size)),
|
Value::Integer(i128::from(curr_task.ptinfo.pti_virtual_size)),
|
||||||
Value::Duration(Duration::nanoseconds(
|
Value::Duration(Duration::nanoseconds(
|
||||||
i64::try_from(curr_task.ptinfo.pti_total_user + curr_task.ptinfo.pti_total_system)? *
|
i64::try_from(curr_task.ptinfo.pti_total_user + curr_task.ptinfo.pti_total_system)? *
|
||||||
i64::try_from(info.numer)? /
|
i64::from(info.numer) /
|
||||||
i64::try_from(info.denom)?)),
|
i64::from(info.denom))),
|
||||||
Value::String(name)
|
Value::String(name)
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
@ -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() {
|
||||||
@ -349,32 +347,6 @@ fn kill(context: CommandContext) -> CrushResult<()> {
|
|||||||
context.output.send(Value::Empty())
|
context.output.send(Value::Empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref JOBS_OUTPUT_TYPE: Vec<ColumnType> = vec![
|
|
||||||
ColumnType::new("id", ValueType::Integer),
|
|
||||||
ColumnType::new("description", ValueType::String),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[signature(
|
|
||||||
jobs,
|
|
||||||
can_block = false,
|
|
||||||
short = "List running jobs",
|
|
||||||
output = Known(ValueType::TableInputStream(JOBS_OUTPUT_TYPE.clone())),
|
|
||||||
long = "All currently running jobs")]
|
|
||||||
struct Jobs {}
|
|
||||||
|
|
||||||
fn jobs(context: CommandContext) -> CrushResult<()> {
|
|
||||||
let output = context.output.initialize(JOBS_OUTPUT_TYPE.clone())?;
|
|
||||||
for job in context.global_state.jobs() {
|
|
||||||
output.send(Row::new(vec![
|
|
||||||
Value::Integer(usize::from(job.id) as i128),
|
|
||||||
Value::string(job.description),
|
|
||||||
]))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn declare(root: &Scope) -> CrushResult<()> {
|
pub fn declare(root: &Scope) -> CrushResult<()> {
|
||||||
let e = root.create_namespace(
|
let e = root.create_namespace(
|
||||||
@ -382,13 +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")]
|
||||||
macos::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)?;
|
||||||
Jobs::declare(env)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}))?;
|
}))?;
|
||||||
root.r#use(&e);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,15 @@ use crate::lang::errors::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::value::Value;
|
use crate::lang::value::Value;
|
||||||
|
use crate::lang::value::ValueType;
|
||||||
|
use crate::lang::command::OutputType::Known;
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
float,
|
float,
|
||||||
can_block = false,
|
can_block = false,
|
||||||
short = "generate a random floating point number between 0 (inclusive) and 1 (exclusive)"
|
short = "generate a random floating point number between 0 (inclusive) and 1 (exclusive)",
|
||||||
|
output = Known(ValueType::Float),
|
||||||
)]
|
)]
|
||||||
struct Float {
|
struct Float {
|
||||||
#[default(1.0)]
|
#[default(1.0)]
|
||||||
@ -26,7 +29,8 @@ fn float(context: CommandContext) -> CrushResult<()> {
|
|||||||
#[signature(
|
#[signature(
|
||||||
integer,
|
integer,
|
||||||
can_block = false,
|
can_block = false,
|
||||||
short = "generate a random integer between 0 and 1 (or some other specified number)"
|
short = "generate a random integer between 0 and 1 (or some other specified number)",
|
||||||
|
output = Known(ValueType::Integer),
|
||||||
)]
|
)]
|
||||||
struct Integer {
|
struct Integer {
|
||||||
#[default(2)]
|
#[default(2)]
|
||||||
|
@ -17,7 +17,7 @@ pub fn count(context: CommandContext) -> CrushResult<()> {
|
|||||||
Value::Table(r) => context.output.send(Value::Integer(r.rows().len() as i128)),
|
Value::Table(r) => context.output.send(Value::Integer(r.rows().len() as i128)),
|
||||||
Value::List(r) => context.output.send(Value::Integer(r.len() as i128)),
|
Value::List(r) => context.output.send(Value::Integer(r.len() as i128)),
|
||||||
Value::Dict(r) => context.output.send(Value::Integer(r.len() as i128)),
|
Value::Dict(r) => context.output.send(Value::Integer(r.len() as i128)),
|
||||||
v => match v.stream() {
|
v => match v.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let mut res: i128 = 0;
|
let mut res: i128 = 0;
|
||||||
while let Ok(_) = input.read() {
|
while let Ok(_) = input.read() {
|
||||||
|
@ -3,7 +3,7 @@ use crate::lang::execution_context::CommandContext;
|
|||||||
use crate::lang::data::table::{Row, ColumnVec};
|
use crate::lang::data::table::{Row, ColumnVec};
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
use crate::lang::command::OutputType::Unknown;
|
use crate::lang::command::OutputType::Unknown;
|
||||||
use crate::lang::value::Field;
|
use crate::lang::value::Symbol;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
@ -11,17 +11,17 @@ 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 {
|
||||||
#[unnamed()]
|
#[unnamed()]
|
||||||
drop: Vec<Field>,
|
drop: Vec<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drop(context: CommandContext) -> CrushResult<()> {
|
fn drop(context: CommandContext) -> CrushResult<()> {
|
||||||
let cfg: Drop = Drop::parse(context.arguments.clone(), &context.global_state.printer())?;
|
let cfg: Drop = Drop::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let t = input.types();
|
let t = input.types();
|
||||||
let drop = cfg.drop.iter()
|
let drop = cfg.drop.iter()
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
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;
|
||||||
use crate::lang::value::ValueType::Empty;
|
use crate::lang::value::ValueType::Empty;
|
||||||
use crate::lang::command::OutputType::Known;
|
use crate::lang::command::OutputType::Known;
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
r#each,
|
r#each,
|
||||||
@ -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,
|
||||||
@ -34,7 +33,7 @@ fn run(
|
|||||||
.map(|(c, t)| Argument::named(t.name.as_ref(), c, location))
|
.map(|(c, t)| Argument::named(t.name.as_ref(), c, location))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
condition.invoke(
|
condition.eval(
|
||||||
base_context
|
base_context
|
||||||
.clone()
|
.clone()
|
||||||
.with_args(arguments, None)
|
.with_args(arguments, None)
|
||||||
@ -46,7 +45,7 @@ pub fn each(context: CommandContext) -> CrushResult<()> {
|
|||||||
let location = context.arguments[0].location;
|
let location = context.arguments[0].location;
|
||||||
context.output.send(Value::Empty())?;
|
context.output.send(Value::Empty())?;
|
||||||
|
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let base_context = context.empty();
|
let base_context = context.empty();
|
||||||
|
|
||||||
|
@ -6,22 +6,29 @@ 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<()> {
|
||||||
match context.input.recv()?.stream() {
|
let cfg: Enumerate = Enumerate::parse(context.arguments, &context.global_state.printer())?;
|
||||||
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let mut output_type = vec![
|
let mut output_type = vec![
|
||||||
ColumnType::new("idx", ValueType::Integer)];
|
ColumnType::new("idx", ValueType::Integer)];
|
||||||
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(())
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ use crate::lang::data::scope::Scope;
|
|||||||
use crate::lang::pipe::{pipe, InputStream};
|
use crate::lang::pipe::{pipe, InputStream};
|
||||||
use crate::lang::data::table::ColumnType;
|
use crate::lang::data::table::ColumnType;
|
||||||
use crate::lang::data::table::ColumnVec;
|
use crate::lang::data::table::ColumnVec;
|
||||||
use crate::lang::value::Field;
|
use crate::lang::value::Symbol;
|
||||||
use crate::{
|
use crate::{
|
||||||
lang::errors::argument_error_legacy,
|
lang::errors::argument_error_legacy,
|
||||||
lang::pipe::{unlimited_streams, OutputStream},
|
lang::pipe::{unlimited_streams, OutputStream},
|
||||||
@ -23,12 +23,12 @@ 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()]
|
||||||
#[description("the column(s) to group by and copy into the output stream.")]
|
#[description("the column(s) to group by and copy into the output stream.")]
|
||||||
group_by: Vec<Field>,
|
group_by: Vec<Symbol>,
|
||||||
#[named()]
|
#[named()]
|
||||||
#[description("create these additional columns by aggregating the grouped rows using the supplied aggregation command.")]
|
#[description("create these additional columns by aggregating the grouped rows using the supplied aggregation command.")]
|
||||||
command: OrderedStringMap<Command>,
|
command: OrderedStringMap<Command>,
|
||||||
@ -53,7 +53,7 @@ fn aggregate(
|
|||||||
let (output_sender, output_receiver) = pipe();
|
let (output_sender, output_receiver) = pipe();
|
||||||
input_sender.send(Value::TableInputStream(rows))?;
|
input_sender.send(Value::TableInputStream(rows))?;
|
||||||
drop(input_sender);
|
drop(input_sender);
|
||||||
commands[0].invoke(
|
commands[0].eval(
|
||||||
CommandContext::new(&scope, &global_state)
|
CommandContext::new(&scope, &global_state)
|
||||||
.with_input(input_receiver)
|
.with_input(input_receiver)
|
||||||
.with_output(output_sender)
|
.with_output(output_sender)
|
||||||
@ -74,7 +74,7 @@ fn aggregate(
|
|||||||
let local_scope = scope.clone();
|
let local_scope = scope.clone();
|
||||||
let local_state = global_state.clone();
|
let local_state = global_state.clone();
|
||||||
threads.spawn("group:aggr", move ||
|
threads.spawn("group:aggr", move ||
|
||||||
local_command.invoke(
|
local_command.eval(
|
||||||
CommandContext::new(&local_scope, &local_state)
|
CommandContext::new(&local_scope, &local_state)
|
||||||
.with_input(input_receiver)
|
.with_input(input_receiver)
|
||||||
.with_output(output_sender)))?;
|
.with_output(output_sender)))?;
|
||||||
@ -141,7 +141,7 @@ fn create_worker_thread(
|
|||||||
pub fn group(context: CommandContext) -> CrushResult<()> {
|
pub fn group(context: CommandContext) -> CrushResult<()> {
|
||||||
let cfg: Group = Group::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Group = Group::parse(context.arguments, &context.global_state.printer())?;
|
||||||
let mut input = mandate(
|
let mut input = mandate(
|
||||||
context.input.recv()?.stream(),
|
context.input.recv()?.stream()?,
|
||||||
"Expected input to be a stream",
|
"Expected input to be a stream",
|
||||||
)?;
|
)?;
|
||||||
let input_type = input.types().to_vec();
|
let input_type = input.types().to_vec();
|
||||||
|
@ -17,7 +17,7 @@ pub struct Head {
|
|||||||
|
|
||||||
fn head(context: CommandContext) -> CrushResult<()> {
|
fn head(context: CommandContext) -> CrushResult<()> {
|
||||||
let cfg: Head = Head::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Head = Head::parse(context.arguments, &context.global_state.printer())?;
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let output = context.output.initialize(input.types().to_vec())?;
|
let output = context.output.initialize(input.types().to_vec())?;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
@ -72,11 +72,11 @@ pub fn join(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
context.arguments.check_len(2)?;
|
context.arguments.check_len(2)?;
|
||||||
let l = context.arguments.remove(0);
|
let l = context.arguments.remove(0);
|
||||||
let r = context.arguments.remove(0);
|
let r = context.arguments.remove(0);
|
||||||
match (l.argument_type, mandate(l.value.stream(), "Expected a stream")?,
|
match (l.argument_type, mandate(l.value.stream()?, "Expected a stream")?,
|
||||||
r.argument_type, mandate(r.value.stream(), "Expected a stream")?) {
|
r.argument_type, mandate(r.value.stream()?, "Expected a stream")?) {
|
||||||
(Some(left_name), left_stream, Some(right_name), right_stream) => {
|
(Some(left_name), left_stream, Some(right_name), right_stream) => {
|
||||||
let left_idx = left_stream.types().find_str(&left_name)?;
|
let left_idx = left_stream.types().find(&left_name)?;
|
||||||
let right_idx = right_stream.types().find_str(&right_name)?;
|
let right_idx = right_stream.types().find(&right_name)?;
|
||||||
|
|
||||||
let output_type = get_output_type(left_stream.types(), right_stream.types(), right_idx)?;
|
let output_type = get_output_type(left_stream.types(), right_stream.types(), right_idx)?;
|
||||||
let output = context.output.initialize(output_type)?;
|
let output = context.output.initialize(output_type)?;
|
||||||
|
@ -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)?;
|
||||||
|
@ -14,7 +14,7 @@ pub struct Reverse {
|
|||||||
|
|
||||||
fn reverse(context: CommandContext) -> CrushResult<()> {
|
fn reverse(context: CommandContext) -> CrushResult<()> {
|
||||||
Reverse::parse(context.arguments.clone(), &context.global_state.printer())?;
|
Reverse::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let output = context.output.initialize(input.types().to_vec())?;
|
let output = context.output.initialize(input.types().to_vec())?;
|
||||||
let mut q: Vec<Row> = Vec::new();
|
let mut q: Vec<Row> = Vec::new();
|
||||||
|
@ -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,
|
||||||
@ -10,7 +10,7 @@ use crate::{
|
|||||||
lang::{argument::Argument, data::table::Row, value::Value},
|
lang::{argument::Argument, data::table::Row, value::Value},
|
||||||
util::replace::Replace,
|
util::replace::Replace,
|
||||||
};
|
};
|
||||||
use crate::lang::ast::Location;
|
use crate::lang::ast::location::Location;
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
Replace(usize),
|
Replace(usize),
|
||||||
@ -55,7 +55,7 @@ pub fn run(config: Config, mut input: Stream, context: CommandContext) -> CrushR
|
|||||||
Argument::named(cell_type.name.as_ref(), cell.clone(), config.location)
|
Argument::named(cell_type.name.as_ref(), cell.clone(), config.location)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
closure.invoke(context.empty().with_output(sender))?;
|
closure.eval(context.empty().with_args(arguments, None).with_output(sender))?;
|
||||||
receiver.recv()?
|
receiver.recv()?
|
||||||
}
|
}
|
||||||
Source::Argument(idx) => row.cells()[*idx].clone(),
|
Source::Argument(idx) => row.cells()[*idx].clone(),
|
||||||
@ -98,7 +98,7 @@ pub fn run(config: Config, mut input: Stream, context: CommandContext) -> CrushR
|
|||||||
.map(|(cell, cell_type)| Argument::named(&cell_type.name, cell.clone(), config.location))
|
.map(|(cell, cell_type)| Argument::named(&cell_type.name, cell.clone(), config.location))
|
||||||
.collect();
|
.collect();
|
||||||
let (sender, receiver) = pipe();
|
let (sender, receiver) = pipe();
|
||||||
closure.invoke(context.empty().with_output(sender))?;
|
closure.eval(context.empty().with_args(arguments, None).with_output(sender))?;
|
||||||
receiver.recv()?
|
receiver.recv()?
|
||||||
}
|
}
|
||||||
Source::Argument(idx) => row.cells()[*idx].clone(),
|
Source::Argument(idx) => row.cells()[*idx].clone(),
|
||||||
@ -118,7 +118,7 @@ pub fn run(config: Config, mut input: Stream, context: CommandContext) -> CrushR
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn select(mut context: CommandContext) -> CrushResult<()> {
|
pub fn select(mut context: CommandContext) -> CrushResult<()> {
|
||||||
match context.input.clone().recv()?.stream() {
|
match context.input.clone().recv()?.stream()? {
|
||||||
Some(input) => {
|
Some(input) => {
|
||||||
let mut copy = false;
|
let mut copy = false;
|
||||||
let mut columns = Vec::new();
|
let mut columns = Vec::new();
|
||||||
@ -142,7 +142,7 @@ pub fn select(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
location = location.union(a.location);
|
location = location.union(a.location);
|
||||||
match (a.argument_type.as_deref(), a.value.clone()) {
|
match (a.argument_type.as_deref(), a.value.clone()) {
|
||||||
(Some(name), Value::Command(closure)) => {
|
(Some(name), Value::Command(closure)) => {
|
||||||
match (copy, input_type.find_str(name)) {
|
match (copy, input_type.find(name)) {
|
||||||
(true, Ok(idx)) => {
|
(true, Ok(idx)) => {
|
||||||
columns.push((Action::Replace(idx), Source::Closure(closure)))
|
columns.push((Action::Replace(idx), Source::Closure(closure)))
|
||||||
}
|
}
|
||||||
@ -152,16 +152,16 @@ pub fn select(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, Value::Field(name)) => {
|
(None, Value::Symbol(name)) => {
|
||||||
if name.len() != 1 {
|
if name.len() != 1 {
|
||||||
return argument_error_legacy("Invalid field");
|
return argument_error_legacy("Invalid field");
|
||||||
}
|
}
|
||||||
match (copy, input_type.find_str(name[0].as_ref())) {
|
match (copy, input_type.find(name.as_ref())) {
|
||||||
(false, Ok(idx)) => columns
|
(false, Ok(idx)) => columns
|
||||||
.push((Action::Append(name[0].clone()), Source::Argument(idx))),
|
.push((Action::Append(name.clone()), Source::Argument(idx))),
|
||||||
_ => {
|
_ => {
|
||||||
return argument_error_legacy(
|
return argument_error_legacy(
|
||||||
format!("Unknown field {}", name[0]).as_str(),
|
format!("Unknown field {}", name).as_str(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -3,7 +3,7 @@ use crate::lang::errors::{error, CrushResult};
|
|||||||
use crate::lang::execution_context::CommandContext;
|
use crate::lang::execution_context::CommandContext;
|
||||||
use crate::lang::data::table::ColumnVec;
|
use crate::lang::data::table::ColumnVec;
|
||||||
use crate::lang::data::table::Row;
|
use crate::lang::data::table::Row;
|
||||||
use crate::lang::value::Field;
|
use crate::lang::value::Symbol;
|
||||||
use crate::lang::errors::argument_error_legacy;
|
use crate::lang::errors::argument_error_legacy;
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
@ -11,19 +11,19 @@ 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()]
|
||||||
#[description("the columns to sort on. Optional if input only has one column.")]
|
#[description("the columns to sort on. Optional if input only has one column.")]
|
||||||
field: Vec<Field>,
|
field: Vec<Symbol>,
|
||||||
#[description("reverse the sort order.")]
|
#[description("reverse the sort order.")]
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
reverse: bool,
|
reverse: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sort(context: CommandContext) -> CrushResult<()> {
|
fn sort(context: CommandContext) -> CrushResult<()> {
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let output = context.output.initialize(input.types().to_vec())?;
|
let output = context.output.initialize(input.types().to_vec())?;
|
||||||
let cfg: Sort = Sort::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Sort = Sort::parse(context.arguments, &context.global_state.printer())?;
|
||||||
|
@ -7,9 +7,9 @@ use crate::lang::{value::Value, value::ValueType};
|
|||||||
use chrono::Duration;
|
use chrono::Duration;
|
||||||
use float_ord::FloatOrd;
|
use float_ord::FloatOrd;
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
use crate::lang::value::Field;
|
use crate::lang::value::Symbol;
|
||||||
|
|
||||||
fn parse(input_type: &[ColumnType], field: Option<Field>) -> CrushResult<usize> {
|
fn parse(input_type: &[ColumnType], field: Option<String>) -> CrushResult<usize> {
|
||||||
field.map(|f| input_type.find(&f))
|
field.map(|f| input_type.find(&f))
|
||||||
.unwrap_or_else(||
|
.unwrap_or_else(||
|
||||||
if input_type.len() == 1 {
|
if input_type.len() == 1 {
|
||||||
@ -42,13 +42,13 @@ 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<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sum(context: CommandContext) -> CrushResult<()> {
|
fn sum(context: CommandContext) -> CrushResult<()> {
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(input) => {
|
Some(input) => {
|
||||||
let cfg: Sum = Sum::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Sum = Sum::parse(context.arguments, &context.global_state.printer())?;
|
||||||
let column = parse(input.types(), cfg.field)?;
|
let column = parse(input.types(), cfg.field)?;
|
||||||
@ -94,13 +94,13 @@ 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<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn avg(context: CommandContext) -> CrushResult<()> {
|
fn avg(context: CommandContext) -> CrushResult<()> {
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(input) => {
|
Some(input) => {
|
||||||
let cfg: Avg = Avg::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Avg = Avg::parse(context.arguments, &context.global_state.printer())?;
|
||||||
let column = parse(input.types(), cfg.field)?;
|
let column = parse(input.types(), cfg.field)?;
|
||||||
@ -159,13 +159,13 @@ 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<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn min(context: CommandContext) -> CrushResult<()> {
|
fn min(context: CommandContext) -> CrushResult<()> {
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(input) => {
|
Some(input) => {
|
||||||
let cfg: Min = Min::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Min = Min::parse(context.arguments, &context.global_state.printer())?;
|
||||||
let column = parse(input.types(), cfg.field)?;
|
let column = parse(input.types(), cfg.field)?;
|
||||||
@ -186,13 +186,13 @@ 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<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max(context: CommandContext) -> CrushResult<()> {
|
fn max(context: CommandContext) -> CrushResult<()> {
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(input) => {
|
Some(input) => {
|
||||||
let cfg: Max = Max::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Max = Max::parse(context.arguments, &context.global_state.printer())?;
|
||||||
let column = parse(input.types(), cfg.field)?;
|
let column = parse(input.types(), cfg.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<Symbol>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -19,7 +19,7 @@ pub struct Tail {
|
|||||||
|
|
||||||
fn tail(context: CommandContext) -> CrushResult<()> {
|
fn tail(context: CommandContext) -> CrushResult<()> {
|
||||||
let cfg: Tail = Tail::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Tail = Tail::parse(context.arguments, &context.global_state.printer())?;
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let output = context.output.initialize(input.types().to_vec())?;
|
let output = context.output.initialize(input.types().to_vec())?;
|
||||||
let mut q: VecDeque<Row> = VecDeque::new();
|
let mut q: VecDeque<Row> = VecDeque::new();
|
||||||
|
@ -2,23 +2,22 @@ use crate::lang::errors::{error, CrushResult};
|
|||||||
use crate::lang::execution_context::CommandContext;
|
use crate::lang::execution_context::CommandContext;
|
||||||
use crate::lang::data::table::ColumnVec;
|
use crate::lang::data::table::ColumnVec;
|
||||||
use crate::lang::data::table::Row;
|
use crate::lang::data::table::Row;
|
||||||
use crate::lang::value::Value;
|
use crate::lang::value::{Symbol, Value};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
use crate::lang::command::OutputType::Passthrough;
|
use crate::lang::command::OutputType::Passthrough;
|
||||||
use crate::lang::value::Field;
|
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
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<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uniq(context: CommandContext) -> CrushResult<()> {
|
pub fn uniq(context: CommandContext) -> CrushResult<()> {
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let cfg: Uniq = Uniq::parse(context.arguments, &context.global_state.printer())?;
|
let cfg: Uniq = Uniq::parse(context.arguments, &context.global_state.printer())?;
|
||||||
let output = context.output.initialize(input.types().to_vec())?;
|
let output = context.output.initialize(input.types().to_vec())?;
|
||||||
|
@ -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::Location;
|
||||||
|
use crate::lang::pipe::pipe;
|
||||||
|
|
||||||
#[signature(
|
#[signature(
|
||||||
r#where,
|
r#where,
|
||||||
@ -35,7 +35,7 @@ fn evaluate(
|
|||||||
|
|
||||||
let (sender, reciever) = pipe();
|
let (sender, reciever) = pipe();
|
||||||
|
|
||||||
condition.invoke(
|
condition.eval(
|
||||||
base_context
|
base_context
|
||||||
.clone()
|
.clone()
|
||||||
.with_args(arguments, None)
|
.with_args(arguments, None)
|
||||||
@ -52,7 +52,7 @@ pub fn r#where(context: CommandContext) -> CrushResult<()> {
|
|||||||
let cfg: Where = Where::parse(context.arguments.clone(), &context.global_state.printer())?;
|
let cfg: Where = Where::parse(context.arguments.clone(), &context.global_state.printer())?;
|
||||||
let location = context.arguments[0].location;
|
let location = context.arguments[0].location;
|
||||||
|
|
||||||
match context.input.recv()?.stream() {
|
match context.input.recv()?.stream()? {
|
||||||
Some(mut input) => {
|
Some(mut input) => {
|
||||||
let base_context = context.empty();
|
let base_context = context.empty();
|
||||||
|
|
||||||
|
@ -3,7 +3,12 @@ use crate::lang::execution_context::CommandContext;
|
|||||||
use crate::lang::pipe::Stream;
|
use crate::lang::pipe::Stream;
|
||||||
use signature::signature;
|
use signature::signature;
|
||||||
|
|
||||||
#[signature(zip, can_block = true, short = "Combine two streams of data into one")]
|
#[signature(
|
||||||
|
zip,
|
||||||
|
can_block = true,
|
||||||
|
short = "Combine two streams of data into one containing one row of each input stream in each row of output.",
|
||||||
|
long = "If the two streams have different numbers of rows, the longer stream will be truncated to the length\nof the shorter one."
|
||||||
|
)]
|
||||||
pub struct Zip {
|
pub struct Zip {
|
||||||
#[description("the first stream.")]
|
#[description("the first stream.")]
|
||||||
first: Stream,
|
first: Stream,
|
||||||
|
@ -85,6 +85,46 @@ lazy_static! {
|
|||||||
Known(ValueType::Bool),
|
Known(ValueType::Bool),
|
||||||
vec![],
|
vec![],
|
||||||
);
|
);
|
||||||
|
res.declare(
|
||||||
|
full("max"),
|
||||||
|
max,
|
||||||
|
false,
|
||||||
|
"float:max",
|
||||||
|
"Largest finite float value",
|
||||||
|
None,
|
||||||
|
Known(ValueType::Float),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
res.declare(
|
||||||
|
full("min"),
|
||||||
|
min,
|
||||||
|
false,
|
||||||
|
"float:min",
|
||||||
|
"Smallest finite float value",
|
||||||
|
None,
|
||||||
|
Known(ValueType::Float),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
res.declare(
|
||||||
|
full("nan"),
|
||||||
|
nan,
|
||||||
|
false,
|
||||||
|
"float:nan",
|
||||||
|
"Not a Number",
|
||||||
|
None,
|
||||||
|
Known(ValueType::Float),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
|
res.declare(
|
||||||
|
full("infinity"),
|
||||||
|
infinity,
|
||||||
|
false,
|
||||||
|
"float:infinity",
|
||||||
|
"Infinity",
|
||||||
|
None,
|
||||||
|
Known(ValueType::Float),
|
||||||
|
vec![],
|
||||||
|
);
|
||||||
res
|
res
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -148,3 +188,31 @@ fn is_infinite(context: CommandContext) -> CrushResult<()> {
|
|||||||
.output
|
.output
|
||||||
.send(Value::Bool(context.this.float()?.is_infinite()))
|
.send(Value::Bool(context.this.float()?.is_infinite()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn max(context: CommandContext) -> CrushResult<()> {
|
||||||
|
context.arguments.check_len(0)?;
|
||||||
|
context
|
||||||
|
.output
|
||||||
|
.send(Value::Float(f64::MAX))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min(context: CommandContext) -> CrushResult<()> {
|
||||||
|
context.arguments.check_len(0)?;
|
||||||
|
context
|
||||||
|
.output
|
||||||
|
.send(Value::Float(f64::MIN))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nan(context: CommandContext) -> CrushResult<()> {
|
||||||
|
context.arguments.check_len(0)?;
|
||||||
|
context
|
||||||
|
.output
|
||||||
|
.send(Value::Float(f64::NAN))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infinity(context: CommandContext) -> CrushResult<()> {
|
||||||
|
context.arguments.check_len(0)?;
|
||||||
|
context
|
||||||
|
.output
|
||||||
|
.send(Value::Float(f64::INFINITY))
|
||||||
|
}
|
||||||
|
@ -207,7 +207,7 @@ fn of(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
match context.arguments.len() {
|
match context.arguments.len() {
|
||||||
0 => {
|
0 => {
|
||||||
let mut lst = Vec::new();
|
let mut lst = Vec::new();
|
||||||
let mut input = mandate(context.input.recv()?.stream(), "Expected a stream")?;
|
let mut input = mandate(context.input.recv()?.stream()?, "Expected a stream")?;
|
||||||
if input.types().len() != 1 {
|
if input.types().len() != 1 {
|
||||||
return data_error("Expected input with exactly one column");
|
return data_error("Expected input with exactly one column");
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ fn new(mut context: CommandContext) -> CrushResult<()> {
|
|||||||
if let Some(Value::Command(c)) = res.get("__init__") {
|
if let Some(Value::Command(c)) = res.get("__init__") {
|
||||||
context.output = black_hole();
|
context.output = black_hole();
|
||||||
context.this = Some(Value::Struct(res.clone()));
|
context.this = Some(Value::Struct(res.clone()));
|
||||||
c.invoke(context)?;
|
c.eval(context)?;
|
||||||
}
|
}
|
||||||
o.send(Value::Struct(res))
|
o.send(Value::Struct(res))
|
||||||
}
|
}
|
||||||
@ -219,7 +219,7 @@ pub fn declare(root: &Scope) -> CrushResult<()> {
|
|||||||
env.declare("scope", Value::Type(ValueType::Scope))?;
|
env.declare("scope", Value::Type(ValueType::Scope))?;
|
||||||
env.declare("binary", Value::Type(ValueType::Binary))?;
|
env.declare("binary", Value::Type(ValueType::Binary))?;
|
||||||
env.declare("binary_stream", Value::Type(ValueType::BinaryInputStream))?;
|
env.declare("binary_stream", Value::Type(ValueType::BinaryInputStream))?;
|
||||||
env.declare("field", Value::Type(ValueType::Field))?;
|
env.declare("field", Value::Type(ValueType::Symbol))?;
|
||||||
env.declare("empty", Value::Type(ValueType::Empty))?;
|
env.declare("empty", Value::Type(ValueType::Empty))?;
|
||||||
env.declare("float", Value::Type(ValueType::Float))?;
|
env.declare("float", Value::Type(ValueType::Float))?;
|
||||||
env.declare("integer", Value::Type(ValueType::Integer))?;
|
env.declare("integer", Value::Type(ValueType::Integer))?;
|
||||||
|
@ -62,7 +62,7 @@ struct Write {}
|
|||||||
fn write(context: CommandContext) -> CrushResult<()> {
|
fn write(context: CommandContext) -> CrushResult<()> {
|
||||||
context.output.send(Value::Empty())?;
|
context.output.send(Value::Empty())?;
|
||||||
let real_output = context.this.table_output_stream()?;
|
let real_output = context.this.table_output_stream()?;
|
||||||
let mut stream = mandate(context.input.recv()?.stream(), "Expected a stream")?;
|
let mut stream = mandate(context.input.recv()?.stream()?, "Expected a stream")?;
|
||||||
|
|
||||||
while let Ok(row) = stream.read() {
|
while let Ok(row) = stream.read() {
|
||||||
real_output.send(row)?;
|
real_output.send(row)?;
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
touch ./foo
|
touch ./foo
|
||||||
foo:chmod "a=" "o+xr" "u+w" "g-r"
|
./foo:chmod "a=" "o+xr" "u+w" "g-r"
|
||||||
find ./foo | select ^permissions
|
find ./foo | select permissions
|
||||||
rm ./foo
|
rm ./foo
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
echo "Integer comparisons"
|
"Integer comparisons"
|
||||||
|
|
||||||
1>2
|
1>2
|
||||||
1<2
|
1<2
|
||||||
@ -21,7 +21,7 @@ echo "Integer comparisons"
|
|||||||
2!=1
|
2!=1
|
||||||
2==1
|
2==1
|
||||||
|
|
||||||
echo "Float comparisons"
|
"Float comparisons"
|
||||||
|
|
||||||
1.0>2.0
|
1.0>2.0
|
||||||
1.0<2.0
|
1.0<2.0
|
||||||
@ -30,7 +30,7 @@ echo "Float comparisons"
|
|||||||
1.0!=2.0
|
1.0!=2.0
|
||||||
1.0==2.0
|
1.0==2.0
|
||||||
|
|
||||||
echo "Duration comparisons"
|
"Duration comparisons"
|
||||||
|
|
||||||
(duration:of seconds=1) > (duration:of seconds=2)
|
(duration:of seconds=1) > (duration:of seconds=2)
|
||||||
(duration:of seconds=1) < (duration:of seconds=2)
|
(duration:of seconds=1) < (duration:of seconds=2)
|
||||||
@ -39,18 +39,18 @@ echo "Duration comparisons"
|
|||||||
(duration:of seconds=1) != (duration:of seconds=2)
|
(duration:of seconds=1) != (duration:of seconds=2)
|
||||||
(duration:of seconds=1) == (duration:of seconds=2)
|
(duration:of seconds=1) == (duration:of seconds=2)
|
||||||
|
|
||||||
echo "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
|
||||||
|
|
||||||
echo "String comparisons"
|
"String comparisons"
|
||||||
"a" > "b"
|
"a" > "b"
|
||||||
"a" < "b"
|
"a" < "b"
|
||||||
"a" >= "b"
|
"a" >= "b"
|
||||||
|
@ -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)
|
||||||
|
@ -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) {
|
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
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
seq 1000 | select num={(value*5):mod 7} | group ^num | sort ^num
|
seq 10 | 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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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 | 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.
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user