mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 15:11:52 +02:00
Compare commits
186 Commits
Author | SHA1 | Date | |
---|---|---|---|
370639d7d7 | |||
d326f6def6 | |||
d42cfab6ef | |||
2b7c811402 | |||
c6cb491e77 | |||
e2a4632159 | |||
65f0edd14b | |||
b2c466bca6 | |||
6b4e577032 | |||
b12a3dd0e5 | |||
d856ac92f4 | |||
f5856b0914 | |||
8c675a0d31 | |||
86a0e77065 | |||
72c27bd095 | |||
e4e27b6e11 | |||
475d32045f | |||
3643ee6dfd | |||
32e4535f24 | |||
daa2148136 | |||
9097e865ca | |||
894d3e7452 | |||
5a5c65ee4b | |||
8b35239bce | |||
87e2fa137a | |||
46f64c6fdc | |||
10536f70f3 | |||
0812a08bfb | |||
5706eddee3 | |||
0b429fde24 | |||
388ff78a26 | |||
7d46177cf3 | |||
8a0bd20e84 | |||
a1a5a3646b | |||
453c11b4b5 | |||
c66b97126f | |||
0646f1118c | |||
0bcfa12e0d | |||
b2ec32fdf0 | |||
8f00848ff9 | |||
604025fe34 | |||
98126e2981 | |||
db9b88089e | |||
a35a71fd82 | |||
558cd58d09 | |||
410f3ef0f0 | |||
ae765c71fd | |||
e5684bc34c | |||
b4a7e7e6e9 | |||
41669e60c8 | |||
eeaca50dee | |||
d8d88cd395 | |||
9aabafeb41 | |||
9ced5915ff | |||
9d0be7d96f | |||
57a6465ba0 | |||
5cc6505512 | |||
3d45f77692 | |||
e01974b7ab | |||
1f01677b7b | |||
58ee2bf06a | |||
7bf09559a6 | |||
8dea08929a | |||
26f31da711 | |||
d95a065e3d | |||
ed50210832 | |||
ceafe434b5 | |||
89b374cb16 | |||
47c1f475bf | |||
61e027b227 | |||
58ab5aa887 | |||
2b2117173c | |||
f2a79cf381 | |||
ad9449bf00 | |||
c2f8f4bd9b | |||
8b6232ac87 | |||
93a965e3e2 | |||
217c2bae99 | |||
b9bbf0c10f | |||
a54f9719e5 | |||
a5470b2362 | |||
c1bf9fd897 | |||
f3036b8cfd | |||
9b6b817276 | |||
9e3c64aa84 | |||
920e0acb85 | |||
b7d3623e53 | |||
3676a8a48d | |||
f85a1d003c | |||
121e8678b6 | |||
e4c512e33d | |||
81df42d63b | |||
6802a4ee21 | |||
c0ce78f892 | |||
221f36ca65 | |||
125e60d06a | |||
83458510a9 | |||
eac5f62959 | |||
b19cc799aa | |||
efa56d0147 | |||
47f6d20131 | |||
e0b4ab09eb | |||
8abf28093a | |||
d1687df067 | |||
e77219a59f | |||
22edb37162 | |||
1ac87715ff | |||
de162c9aea | |||
390d06d4e7 | |||
89acbda877 | |||
0d40d0438f | |||
1e8212a938 | |||
2da8310b11 | |||
c16d8f0d5f | |||
2ac5b0480a | |||
4e90b478b7 | |||
3a38fb94f0 | |||
c6f6dcb57c | |||
b80299eba7 | |||
a48616697a | |||
b82dccf0bd | |||
84caf8859f | |||
be7f35246e | |||
3917fda7ed | |||
3b357e5402 | |||
79da470239 | |||
37949e70e0 | |||
5d00ecef56 | |||
6dde231dde | |||
58fa2e51a2 | |||
cf0877bf72 | |||
a0db4ce747 | |||
6ee13126f7 | |||
1c15a4ed3a | |||
7aabc381a3 | |||
8c9dced71b | |||
06d5a31301 | |||
ffbc0b0180 | |||
c0901ef707 | |||
d3e84daa49 | |||
228ede18cf | |||
c5a69271a2 | |||
dc9d939c83 | |||
32f0f94b46 | |||
a142d1a192 | |||
173d60d59d | |||
f2989bf704 | |||
575ddbd4ef | |||
ef9b72d360 | |||
25349a1eac | |||
99e4c44862 | |||
1345f97202 | |||
f02076daa8 | |||
533e04a60a | |||
13c152b00f | |||
f231a6df4a | |||
3c0bccb900 | |||
f43a65d7a7 | |||
0827ed143d | |||
4b84825dbf | |||
82ae06865c | |||
128ce6f9b7 | |||
44cbd88b55 | |||
7164929c61 | |||
848ff8453b | |||
f94ca6cfde | |||
fab3f8fd40 | |||
dbcfcdae89 | |||
08aa248c42 | |||
9f07bcc66f | |||
2caa44cea8 | |||
28c21121cf | |||
a17d46f200 | |||
6cc8402127 | |||
5f0ad1d6ad | |||
8d7bb9147e | |||
bc48b4553c | |||
28c07a5072 | |||
30c8dabeb4 | |||
8b368b6a4e | |||
8c0d60d0fb | |||
8b0a4ccf4c | |||
cfe4eff566 | |||
38f3957edf | |||
cb66d2bcad | |||
ff73623873 |
6
.github/pull_request_template.md
vendored
6
.github/pull_request_template.md
vendored
@ -4,6 +4,12 @@
|
||||
|
||||
# Tests
|
||||
|
||||
Make sure you've done the following:
|
||||
|
||||
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
|
||||
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
|
||||
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
|
||||
|
||||
Make sure you've run and fixed any issues with these commands:
|
||||
|
||||
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
|
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -135,10 +135,7 @@ jobs:
|
||||
- run: python -m pip install tox
|
||||
|
||||
- name: Install virtualenv
|
||||
run: |
|
||||
git clone https://github.com/kubouch/virtualenv.git && \
|
||||
cd virtualenv && \
|
||||
git checkout engine-q-update
|
||||
run: git clone https://github.com/pypa/virtualenv.git
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
|
4
.github/workflows/release-pkg.nu
vendored
4
.github/workflows/release-pkg.nu
vendored
@ -102,8 +102,8 @@ if $os in ['ubuntu-latest', 'macos-latest'] {
|
||||
let releaseStem = $'($bin)-($version)-($target)'
|
||||
|
||||
$'(char nl)Download less related stuffs...'; hr-line
|
||||
curl https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o $'($dist)\less.exe'
|
||||
curl https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o $'($dist)\LICENSE-for-less.txt'
|
||||
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
|
||||
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
|
||||
|
||||
# Create Windows msi release package
|
||||
if (get-env _EXTRA_) == 'msi' {
|
||||
|
2
.github/workflows/winget-submission.yml
vendored
2
.github/workflows/winget-submission.yml
vendored
@ -15,5 +15,5 @@ jobs:
|
||||
run: |
|
||||
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
|
||||
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
|
||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
|
||||
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}
|
||||
|
1216
Cargo.lock
generated
1216
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
41
Cargo.toml
41
Cargo.toml
@ -11,7 +11,7 @@ name = "nu"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.64.0"
|
||||
version = "0.66.1"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -28,6 +28,7 @@ members = [
|
||||
"crates/nu_plugin_gstat",
|
||||
"crates/nu_plugin_example",
|
||||
"crates/nu_plugin_query",
|
||||
"crates/nu_plugin_custom_values",
|
||||
"crates/nu-utils",
|
||||
]
|
||||
|
||||
@ -36,23 +37,23 @@ chrono = { version = "0.4.19", features = ["serde"] }
|
||||
crossterm = "0.23.0"
|
||||
ctrlc = "3.2.1"
|
||||
log = "0.4"
|
||||
miette = "4.5.0"
|
||||
miette = "5.1.0"
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.64.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.64.0" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.64.0" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.64.0" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.64.0" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.64.0" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.64.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.64.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.64.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.64.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.64.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.64.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.64.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.64.0" }
|
||||
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
|
||||
nu-cli = { path="./crates/nu-cli", version = "0.66.1" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.66.1" }
|
||||
nu-command = { path="./crates/nu-command", version = "0.66.1" }
|
||||
nu-engine = { path="./crates/nu-engine", version = "0.66.1" }
|
||||
nu-json = { path="./crates/nu-json", version = "0.66.1" }
|
||||
nu-parser = { path="./crates/nu-parser", version = "0.66.1" }
|
||||
nu-path = { path="./crates/nu-path", version = "0.66.1" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.66.1" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.66.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.66.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.66.1" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.66.1" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.66.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.66.1" }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
pretty_env_logger = "0.4.0"
|
||||
rayon = "1.5.1"
|
||||
is_executable = "1.0.1"
|
||||
@ -63,13 +64,13 @@ openssl = { version = "0.10.38", features = ["vendored"], optional = true }
|
||||
signal-hook = { version = "0.3.14", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.64.0" }
|
||||
nu-test-support = { path="./crates/nu-test-support", version = "0.66.1" }
|
||||
tempfile = "3.2.0"
|
||||
assert_cmd = "2.0.2"
|
||||
pretty_assertions = "1.0.0"
|
||||
serial_test = "0.5.1"
|
||||
serial_test = "0.8.0"
|
||||
hamcrest2 = "0.3.0"
|
||||
rstest = "0.12.0"
|
||||
rstest = "0.15.0"
|
||||
itertools = "0.10.3"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 7.3 KiB |
@ -4,29 +4,32 @@ description = "CLI-related functionality for Nushell"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.64.0"
|
||||
version = "0.66.1"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.64.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.64.0" }
|
||||
nu-test-support = { path="../nu-test-support", version = "0.66.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.66.1" }
|
||||
rstest = "0.15.0"
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.64.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.64.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.64.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.64.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.66.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.66.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.66.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.66.1" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.64.0" }
|
||||
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.66.1" }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
crossterm = "0.23.0"
|
||||
miette = { version = "4.5.0", features = ["fancy"] }
|
||||
thiserror = "1.0.29"
|
||||
miette = { version = "5.1.0", features = ["fancy"] }
|
||||
thiserror = "1.0.31"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
|
||||
log = "0.4"
|
||||
is_executable = "1.0.1"
|
||||
chrono = "0.4.19"
|
||||
is_executable = "1.0.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
regex = "1.5.4"
|
||||
sysinfo = "0.24.1"
|
||||
|
||||
[features]
|
||||
|
@ -5,21 +5,27 @@ use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::engine::Stack;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, StateDelta, StateWorkingSet},
|
||||
engine::{EngineState, StateWorkingSet},
|
||||
PipelineData, Spanned, Value,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
/// Run a command (or commands) given to us by the user
|
||||
pub fn evaluate_commands(
|
||||
commands: &Spanned<String>,
|
||||
init_cwd: &Path,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
input: PipelineData,
|
||||
is_perf_true: bool,
|
||||
table_mode: Option<Value>,
|
||||
) -> Result<()> {
|
||||
// Run a command (or commands) given to us by the user
|
||||
) -> Result<Option<i64>> {
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// Parse the source code
|
||||
let (block, delta) = {
|
||||
if let Some(ref t_mode) = table_mode {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
@ -39,43 +45,19 @@ pub fn evaluate_commands(
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
|
||||
// Update permanent state
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
// Run the block
|
||||
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
let mut config = engine_state.get_config().clone();
|
||||
if let Some(t_mode) = table_mode {
|
||||
config.table_mode = t_mode.as_string()?;
|
||||
}
|
||||
|
||||
// Merge the delta in case env vars changed in the config
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) =
|
||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
||||
{
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = convert_env_values(engine_state, stack) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(pipeline_data) => {
|
||||
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
|
||||
}
|
||||
Err(err) => {
|
||||
@ -84,11 +66,11 @@ pub fn evaluate_commands(
|
||||
report_error(&working_set, &err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if is_perf_true {
|
||||
info!("evaluate {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(exit_code)
|
||||
}
|
||||
|
@ -43,19 +43,21 @@ impl CommandCompletion {
|
||||
|
||||
if let Ok(mut contents) = std::fs::read_dir(path) {
|
||||
while let Some(Ok(item)) = contents.next() {
|
||||
if !executables.contains(
|
||||
if self.engine_state.config.max_external_completion_results
|
||||
> executables.len() as i64
|
||||
&& !executables.contains(
|
||||
&item
|
||||
.path()
|
||||
.file_name()
|
||||
.map(|x| x.to_string_lossy().to_string())
|
||||
.unwrap_or_default(),
|
||||
) && matches!(
|
||||
item.path()
|
||||
.file_name()
|
||||
.map(|x| match_algorithm
|
||||
)
|
||||
&& matches!(
|
||||
item.path().file_name().map(|x| match_algorithm
|
||||
.matches_str(&x.to_string_lossy(), prefix)),
|
||||
Some(true)
|
||||
) && is_executable::is_executable(&item.path())
|
||||
)
|
||||
&& is_executable::is_executable(&item.path())
|
||||
{
|
||||
if let Ok(name) = item.file_name().into_string() {
|
||||
executables.push(name);
|
||||
|
@ -68,14 +68,10 @@ impl NuCompleter {
|
||||
for pipeline in output.pipelines.into_iter() {
|
||||
for expr in pipeline.expressions {
|
||||
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
|
||||
let span_offset: usize = alias_offset.iter().sum();
|
||||
|
||||
for (flat_idx, flat) in flattened.iter().enumerate() {
|
||||
let alias = if alias_offset.is_empty() {
|
||||
0
|
||||
} else {
|
||||
alias_offset[flat_idx]
|
||||
};
|
||||
if pos >= flat.0.start - alias && pos < flat.0.end - alias {
|
||||
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
|
||||
// Context variables
|
||||
let most_left_var =
|
||||
most_left_variable(flat_idx, &working_set, flattened.clone());
|
||||
@ -84,42 +80,18 @@ impl NuCompleter {
|
||||
let new_span = if flat_idx == 0 {
|
||||
Span {
|
||||
start: flat.0.start,
|
||||
end: flat.0.end - 1 - alias,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
} else {
|
||||
Span {
|
||||
start: flat.0.start - alias,
|
||||
end: flat.0.end - 1 - alias,
|
||||
start: flat.0.start - span_offset,
|
||||
end: flat.0.end - 1 - span_offset,
|
||||
}
|
||||
};
|
||||
|
||||
// Parses the prefix
|
||||
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
|
||||
prefix.remove(pos - (flat.0.start - alias));
|
||||
|
||||
// Completions that depends on the previous expression (e.g: use, source)
|
||||
if flat_idx > 0 {
|
||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||
// Read the content for the previous expression
|
||||
let prev_expr_str =
|
||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||
|
||||
// Completion for .nu files
|
||||
if prev_expr_str == b"use" || prev_expr_str == b"source" {
|
||||
let mut completer =
|
||||
DotNuCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
prefix.remove(pos - (flat.0.start - span_offset));
|
||||
|
||||
// Variables completion
|
||||
if prefix.starts_with(b"$") || most_left_var.is_some() {
|
||||
@ -153,6 +125,42 @@ impl NuCompleter {
|
||||
);
|
||||
}
|
||||
|
||||
// Completions that depends on the previous expression (e.g: use, source)
|
||||
if flat_idx > 0 {
|
||||
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
|
||||
// Read the content for the previous expression
|
||||
let prev_expr_str =
|
||||
working_set.get_span_contents(previous_expr.0).to_vec();
|
||||
|
||||
// Completion for .nu files
|
||||
if prev_expr_str == b"use" || prev_expr_str == b"source" {
|
||||
let mut completer =
|
||||
DotNuCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
} else if prev_expr_str == b"ls" {
|
||||
let mut completer =
|
||||
FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match other types
|
||||
match &flat.1 {
|
||||
FlatShape::Custom(decl_id) => {
|
||||
@ -185,6 +193,18 @@ impl NuCompleter {
|
||||
pos,
|
||||
);
|
||||
}
|
||||
FlatShape::Filepath | FlatShape::GlobPattern => {
|
||||
let mut completer = FileCompletion::new(self.engine_state.clone());
|
||||
|
||||
return self.process_completion(
|
||||
&mut completer,
|
||||
&working_set,
|
||||
prefix,
|
||||
new_span,
|
||||
offset,
|
||||
pos,
|
||||
);
|
||||
}
|
||||
flat_shape => {
|
||||
let mut completer = CommandCompletion::new(
|
||||
self.engine_state.clone(),
|
||||
@ -235,7 +255,7 @@ impl ReedlineCompleter for NuCompleter {
|
||||
}
|
||||
}
|
||||
|
||||
type MatchedAlias<'a> = Vec<(&'a [u8], &'a [u8])>;
|
||||
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
|
||||
|
||||
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
|
||||
// that `g` is an alias of `git`
|
||||
@ -254,6 +274,13 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
|
||||
lens -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !line.is_empty() {
|
||||
let last = line.last().expect("input is empty");
|
||||
if last == &b' ' {
|
||||
output.push(b' ');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
output = line.to_vec();
|
||||
}
|
||||
@ -261,7 +288,7 @@ fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<u
|
||||
(output, alias_offset)
|
||||
}
|
||||
|
||||
fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option<MatchedAlias<'a>> {
|
||||
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
|
||||
let mut vec_names = vec![];
|
||||
let mut vec_alias = vec![];
|
||||
let mut pos = 0;
|
||||
@ -269,27 +296,31 @@ fn search_alias<'a>(input: &'a [u8], working_set: &'a StateWorkingSet) -> Option
|
||||
for (index, character) in input.iter().enumerate() {
|
||||
if *character == b' ' {
|
||||
let range = &input[pos..index];
|
||||
vec_names.push(range);
|
||||
vec_names.push(range.to_owned());
|
||||
pos = index + 1;
|
||||
}
|
||||
}
|
||||
// Push the rest to names vector.
|
||||
if pos < input.len() {
|
||||
vec_names.push(&input[pos..]);
|
||||
vec_names.push((&input[pos..]).to_owned());
|
||||
}
|
||||
|
||||
for name in &vec_names {
|
||||
if let Some(alias_id) = working_set.find_alias(name) {
|
||||
if let Some(alias_id) = working_set.find_alias(&name[..]) {
|
||||
let alias_span = working_set.get_alias(alias_id);
|
||||
let mut span_vec = vec![];
|
||||
is_alias = true;
|
||||
for alias in alias_span {
|
||||
let name = working_set.get_span_contents(*alias);
|
||||
if !name.is_empty() {
|
||||
vec_alias.push(name);
|
||||
span_vec.push(name);
|
||||
}
|
||||
}
|
||||
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
|
||||
let full_aliases = span_vec.join(&[b' '][..]);
|
||||
vec_alias.push(full_aliases);
|
||||
} else {
|
||||
vec_alias.push(name);
|
||||
vec_alias.push(name.to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ impl Completer for VariableCompletion {
|
||||
options: &CompletionOptions,
|
||||
) -> Vec<Suggestion> {
|
||||
let mut output = vec![];
|
||||
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
|
||||
let builtins = ["$nu", "$in", "$env", "$nothing"];
|
||||
let var_str = std::str::from_utf8(&self.var_context.0)
|
||||
.unwrap_or("")
|
||||
.to_lowercase();
|
||||
|
@ -1,7 +1,13 @@
|
||||
use crate::util::{eval_source, report_error};
|
||||
#[cfg(feature = "plugin")]
|
||||
use log::info;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_parser::ParseError;
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_path::canonicalize_with;
|
||||
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
|
||||
#[cfg(feature = "plugin")]
|
||||
use nu_protocol::Spanned;
|
||||
use nu_protocol::{HistoryFileFormat, PipelineData, Span};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -15,12 +21,13 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
|
||||
pub fn read_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
is_perf_true: bool,
|
||||
) {
|
||||
// Reading signatures from signature file
|
||||
// The plugin.nu file stores the parsed signature collected from each registered plugin
|
||||
add_plugin_file(engine_state, storage_path);
|
||||
add_plugin_file(engine_state, plugin_file, storage_path);
|
||||
|
||||
let plugin_path = engine_state.plugin_signatures.clone();
|
||||
if let Some(plugin_path) = plugin_path {
|
||||
@ -43,8 +50,23 @@ pub fn read_plugin_file(
|
||||
}
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
|
||||
if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
pub fn add_plugin_file(
|
||||
engine_state: &mut EngineState,
|
||||
plugin_file: Option<Spanned<String>>,
|
||||
storage_path: &str,
|
||||
) {
|
||||
if let Some(plugin_file) = plugin_file {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
let cwd = working_set.get_cwd();
|
||||
|
||||
match canonicalize_with(&plugin_file.item, cwd) {
|
||||
Ok(path) => engine_state.plugin_signatures = Some(path),
|
||||
Err(_) => {
|
||||
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
}
|
||||
} else if let Some(mut plugin_path) = nu_path::config_dir() {
|
||||
// Path to store plugins signatures
|
||||
plugin_path.push(storage_path);
|
||||
plugin_path.push(PLUGIN_FILE);
|
||||
@ -69,12 +91,10 @@ pub fn eval_config_contents(
|
||||
PipelineData::new(Span::new(0, 0)),
|
||||
);
|
||||
|
||||
// Merge the delta in case env vars changed in the config
|
||||
// Merge the environment in case env vars changed in the config
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(cwd) => {
|
||||
if let Err(e) =
|
||||
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
|
||||
{
|
||||
if let Err(e) = engine_state.merge_env(stack, cwd) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ pub fn print_table_or_error(
|
||||
stack: &mut Stack,
|
||||
mut pipeline_data: PipelineData,
|
||||
config: &mut Config,
|
||||
) {
|
||||
) -> Option<i64> {
|
||||
let exit_code = match &mut pipeline_data {
|
||||
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
|
||||
_ => None,
|
||||
@ -77,7 +77,11 @@ pub fn print_table_or_error(
|
||||
|
||||
match engine_state.find_decl("table".as_bytes(), &[]) {
|
||||
Some(decl_id) => {
|
||||
let table = engine_state.get_decl(decl_id).run(
|
||||
let command = engine_state.get_decl(decl_id);
|
||||
if command.get_block_id().is_some() {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
} else {
|
||||
let table = command.run(
|
||||
engine_state,
|
||||
stack,
|
||||
&Call::new(Span::new(0, 0)),
|
||||
@ -86,20 +90,7 @@ pub fn print_table_or_error(
|
||||
|
||||
match table {
|
||||
Ok(table) => {
|
||||
for item in table {
|
||||
if let Value::Error { error } = item {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &error);
|
||||
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut out = item.into_string("\n", config);
|
||||
out.push('\n');
|
||||
|
||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||
}
|
||||
print_or_exit(table, engine_state, config);
|
||||
}
|
||||
Err(error) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
@ -110,7 +101,27 @@ pub fn print_table_or_error(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
print_or_exit(pipeline_data, engine_state, config);
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
|
||||
exit_code
|
||||
.pop()
|
||||
.and_then(|last_exit_code| match last_exit_code {
|
||||
Value::Int { val: code, .. } => Some(code),
|
||||
_ => None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
|
||||
for item in pipeline_data {
|
||||
if let Value::Error { error } = item {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
@ -126,10 +137,3 @@ pub fn print_table_or_error(
|
||||
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Make sure everything has finished
|
||||
if let Some(exit_code) = exit_code {
|
||||
let _: Vec<_> = exit_code.into_iter().collect();
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,9 @@ pub use nu_highlight::NuHighlight;
|
||||
pub use print::Print;
|
||||
pub use prompt::NushellPrompt;
|
||||
pub use repl::evaluate_repl;
|
||||
pub use repl::{eval_env_change_hook, eval_hook};
|
||||
pub use syntax_highlight::NuHighlighter;
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
|
||||
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
|
||||
pub use validation::NuValidator;
|
||||
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -21,11 +21,19 @@ impl Command for Print {
|
||||
"print without inserting a newline for the line ending",
|
||||
Some('n'),
|
||||
)
|
||||
.switch("stderr", "print to stderr instead of stdout", Some('e'))
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Prints the values given"
|
||||
"Print the given values to stdout"
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
|
||||
Since this command has no output, there is no point in piping it with other commands.
|
||||
|
||||
`print` may be used inside blocks of code (e.g.: hooks) to display text during execution without interfering with the pipeline."#
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -41,11 +49,12 @@ impl Command for Print {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let no_newline = call.has_flag("no-newline");
|
||||
let to_stderr = call.has_flag("stderr");
|
||||
let head = call.head;
|
||||
|
||||
for arg in args {
|
||||
arg.into_pipeline_data()
|
||||
.print(engine_state, stack, no_newline)?;
|
||||
.print(engine_state, stack, no_newline, to_stderr)?;
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(head))
|
||||
|
@ -1,5 +1,6 @@
|
||||
#[cfg(windows)]
|
||||
use nu_utils::enable_vt_processing;
|
||||
use reedline::DefaultPrompt;
|
||||
|
||||
use {
|
||||
reedline::{
|
||||
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
|
||||
@ -86,6 +87,11 @@ impl NushellPrompt {
|
||||
|
||||
impl Prompt for NushellPrompt {
|
||||
fn render_prompt_left(&self) -> Cow<str> {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let _ = enable_vt_processing();
|
||||
}
|
||||
|
||||
if let Some(prompt_string) = &self.left_prompt_string {
|
||||
prompt_string.replace('\n', "\r\n").into()
|
||||
} else {
|
||||
|
@ -15,6 +15,10 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
||||
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
||||
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
||||
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
|
||||
fn get_prompt_string(
|
||||
prompt: &str,
|
||||
@ -98,6 +102,20 @@ pub(crate) fn update_prompt<'prompt>(
|
||||
is_perf_true,
|
||||
);
|
||||
|
||||
// Now that we have the prompt string lets ansify it.
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
let left_prompt_string = if config.shell_integration {
|
||||
match left_prompt_string {
|
||||
Some(prompt_string) => Some(format!(
|
||||
"{}{}{}",
|
||||
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
|
||||
)),
|
||||
None => left_prompt_string,
|
||||
}
|
||||
} else {
|
||||
left_prompt_string
|
||||
};
|
||||
|
||||
let right_prompt_string = get_prompt_string(
|
||||
PROMPT_COMMAND_RIGHT,
|
||||
config,
|
||||
|
@ -875,7 +875,16 @@ fn edit_from_record(
|
||||
"moveleft" => EditCommand::MoveLeft,
|
||||
"moveright" => EditCommand::MoveRight,
|
||||
"movewordleft" => EditCommand::MoveWordLeft,
|
||||
"movebigwordleft" => EditCommand::MoveBigWordLeft,
|
||||
"movewordright" => EditCommand::MoveWordRight,
|
||||
"movewordrightend" => EditCommand::MoveWordRightEnd,
|
||||
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
|
||||
"movewordrightstart" => EditCommand::MoveWordRightStart,
|
||||
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
|
||||
"movetoposition" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
EditCommand::MoveToPosition(value.as_integer()? as usize)
|
||||
}
|
||||
"insertchar" => {
|
||||
let value = extract_value("value", cols, vals, span)?;
|
||||
let char = extract_char(value, config)?;
|
||||
@ -888,6 +897,7 @@ fn edit_from_record(
|
||||
"insertnewline" => EditCommand::InsertNewline,
|
||||
"backspace" => EditCommand::Backspace,
|
||||
"delete" => EditCommand::Delete,
|
||||
"cutchar" => EditCommand::CutChar,
|
||||
"backspaceword" => EditCommand::BackspaceWord,
|
||||
"deleteword" => EditCommand::DeleteWord,
|
||||
"clear" => EditCommand::Clear,
|
||||
@ -898,7 +908,11 @@ fn edit_from_record(
|
||||
"cuttoend" => EditCommand::CutToEnd,
|
||||
"cuttolineend" => EditCommand::CutToLineEnd,
|
||||
"cutwordleft" => EditCommand::CutWordLeft,
|
||||
"cutbigwordleft" => EditCommand::CutBigWordLeft,
|
||||
"cutwordright" => EditCommand::CutWordRight,
|
||||
"cutbigwordright" => EditCommand::CutBigWordRight,
|
||||
"cutwordrighttonext" => EditCommand::CutWordRightToNext,
|
||||
"cutbigwordrighttonext" => EditCommand::CutBigWordRightToNext,
|
||||
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
|
||||
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
|
||||
"uppercaseword" => EditCommand::UppercaseWord,
|
||||
|
@ -2,26 +2,34 @@ use crate::{
|
||||
completions::NuCompleter,
|
||||
prompt_update,
|
||||
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
|
||||
util::{eval_source, report_error},
|
||||
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
|
||||
NuHighlighter, NuValidator, NushellPrompt,
|
||||
};
|
||||
use log::{info, trace};
|
||||
use lazy_static::lazy_static;
|
||||
use log::{info, trace, warn};
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{convert_env_values, eval_block};
|
||||
use nu_parser::lex;
|
||||
use nu_parser::{lex, parse};
|
||||
use nu_protocol::{
|
||||
ast::PathMember,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Value,
|
||||
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
|
||||
};
|
||||
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
|
||||
use regex::Regex;
|
||||
use std::io::{self, Write};
|
||||
use std::{sync::atomic::Ordering, time::Instant};
|
||||
use sysinfo::SystemExt;
|
||||
|
||||
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
// According to Daniel Imms @Tyriar, we need to do these this way:
|
||||
// <133 A><prompt><133 B><command><133 C><command output>
|
||||
// These first two have been moved to prompt_update to get as close as possible to the prompt.
|
||||
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
||||
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
||||
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
|
||||
const CMD_FINISHED_MARKER: &str = "\x1b]133;D\x1b\\";
|
||||
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
|
||||
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
|
||||
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
|
||||
|
||||
pub fn evaluate_repl(
|
||||
@ -79,7 +87,7 @@ pub fn evaluate_repl(
|
||||
|
||||
// Get the config once for the history `max_history_size`
|
||||
// Updating that will not be possible in one session
|
||||
let mut config = engine_state.get_config();
|
||||
let config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
|
||||
@ -121,6 +129,14 @@ pub fn evaluate_repl(
|
||||
);
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
|
||||
// Before doing anything, merge the environment from the previous REPL iteration into the
|
||||
// permanent state.
|
||||
if let Err(err) = engine_state.merge_env(stack, cwd) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
|
||||
//Reset the ctrl-c handler
|
||||
if let Some(ctrlc) = &mut engine_state.ctrlc {
|
||||
ctrlc.store(false, Ordering::SeqCst);
|
||||
@ -130,7 +146,7 @@ pub fn evaluate_repl(
|
||||
sig_quit.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
config = engine_state.get_config();
|
||||
let config = engine_state.get_config();
|
||||
|
||||
if is_perf_true {
|
||||
info!("setup colors {}:{}:{}", file!(), line!(), column!());
|
||||
@ -147,7 +163,6 @@ pub fn evaluate_repl(
|
||||
engine_state: engine_state.clone(),
|
||||
config: config.clone(),
|
||||
}))
|
||||
.with_animation(config.animate_prompt)
|
||||
.with_validator(Box::new(NuValidator {
|
||||
engine_state: engine_state.clone(),
|
||||
}))
|
||||
@ -201,7 +216,10 @@ pub fn evaluate_repl(
|
||||
if is_perf_true {
|
||||
info!("sync history {}:{}:{}", file!(), line!(), column!());
|
||||
}
|
||||
line_editor.sync_history().into_diagnostic()?;
|
||||
|
||||
if let Err(e) = line_editor.sync_history() {
|
||||
warn!("Failed to sync history: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
if is_perf_true {
|
||||
@ -236,64 +254,22 @@ pub fn evaluate_repl(
|
||||
|
||||
// Right before we start our prompt and take input from the user,
|
||||
// fire the "pre_prompt" hook
|
||||
if let Some(hook) = &config.hooks.pre_prompt {
|
||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
if let Some(hook) = config.hooks.pre_prompt.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
// Next, check all the environment variables they ask for
|
||||
// fire the "env_change" hook
|
||||
if let Some(hook) = config.hooks.env_change.clone() {
|
||||
match hook {
|
||||
Value::Record {
|
||||
cols, vals: blocks, ..
|
||||
} => {
|
||||
for (idx, env_var) in cols.iter().enumerate() {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_var)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let after = stack.get_env_var(engine_state, env_var).unwrap_or_default();
|
||||
if before != after {
|
||||
if let Err(err) = run_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
vec![before, after.clone()],
|
||||
&blocks[idx],
|
||||
) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
}
|
||||
|
||||
engine_state
|
||||
.previous_env_vars
|
||||
.insert(env_var.to_string(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(
|
||||
&working_set,
|
||||
&ShellError::TypeMismatch(
|
||||
"record for 'env_change' hook".to_string(),
|
||||
x.span().unwrap_or_else(|_| Span::new(0, 0)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config = engine_state.get_config();
|
||||
|
||||
let shell_integration = config.shell_integration;
|
||||
if shell_integration {
|
||||
run_ansi_sequence(PRE_PROMPT_MARKER)?;
|
||||
let config = engine_state.get_config();
|
||||
if let Err(error) =
|
||||
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
|
||||
{
|
||||
report_error_new(engine_state, &error)
|
||||
}
|
||||
|
||||
let config = engine_state.get_config();
|
||||
let prompt =
|
||||
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
|
||||
|
||||
@ -309,6 +285,7 @@ pub fn evaluate_repl(
|
||||
}
|
||||
|
||||
let input = line_editor.read_line(prompt);
|
||||
let shell_integration = config.shell_integration;
|
||||
|
||||
match input {
|
||||
Ok(Signal::Success(s)) => {
|
||||
@ -328,32 +305,31 @@ pub fn evaluate_repl(
|
||||
|
||||
// Right before we start running the code the user gave us,
|
||||
// fire the "pre_execution" hook
|
||||
if let Some(hook) = &config.hooks.pre_execution {
|
||||
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &err);
|
||||
if let Some(hook) = config.hooks.pre_execution.clone() {
|
||||
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
run_ansi_sequence(RESET_APPLICATION_MODE)?;
|
||||
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
// if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
// let path = cwd.as_string()?;
|
||||
// // Try to abbreviate string for windows title
|
||||
// let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
// path.replace(&p.as_path().display().to_string(), "~")
|
||||
// } else {
|
||||
// path
|
||||
// };
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
}
|
||||
// // Set window title too
|
||||
// // https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// // ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// // ESC]1;stringBEL -- Set icon name to string
|
||||
// // ESC]2;stringBEL -- Set window title to string
|
||||
// run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
// }
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
@ -364,13 +340,7 @@ pub fn evaluate_repl(
|
||||
|
||||
let orig = s.clone();
|
||||
|
||||
if (orig.starts_with('.')
|
||||
|| orig.starts_with('~')
|
||||
|| orig.starts_with('/')
|
||||
|| orig.starts_with('\\'))
|
||||
&& path.is_dir()
|
||||
&& tokens.0.len() == 1
|
||||
{
|
||||
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
|
||||
// We have an auto-cd
|
||||
let (path, span) = {
|
||||
if !path.exists() {
|
||||
@ -386,6 +356,14 @@ pub fn evaluate_repl(
|
||||
(path.to_string_lossy().to_string(), tokens.0[0].span)
|
||||
};
|
||||
|
||||
stack.add_env_var(
|
||||
"OLDPWD".into(),
|
||||
Value::String {
|
||||
val: cwd.clone(),
|
||||
span: Span { start: 0, end: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var(
|
||||
@ -437,14 +415,6 @@ pub fn evaluate_repl(
|
||||
},
|
||||
);
|
||||
|
||||
// FIXME: permanent state changes like this hopefully in time can be removed
|
||||
// and be replaced by just passing the cwd in where needed
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
let _ = std::env::set_current_dir(path);
|
||||
engine_state.add_env_var("PWD".into(), cwd);
|
||||
}
|
||||
|
||||
if history_supports_meta && !s.is_empty() {
|
||||
line_editor
|
||||
.update_last_command_context(&|mut c| {
|
||||
@ -458,20 +428,35 @@ pub fn evaluate_repl(
|
||||
}
|
||||
|
||||
if shell_integration {
|
||||
// FIXME: use variant with exit code, if apropriate
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
|
||||
let path = cwd.as_string()?;
|
||||
// Try to abbreviate string for windows title
|
||||
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
|
||||
path.replace(&p.as_path().display().to_string(), "~")
|
||||
} else {
|
||||
path
|
||||
};
|
||||
|
||||
// Set window title too
|
||||
// https://tldp.org/HOWTO/Xterm-Title-3.html
|
||||
// ESC]0;stringBEL -- Set icon name and window title to string
|
||||
// ESC]1;stringBEL -- Set icon name to string
|
||||
// ESC]2;stringBEL -- Set window title to string
|
||||
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlC) => {
|
||||
// `Reedline` clears the line content. New prompt is shown
|
||||
if shell_integration {
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
Ok(Signal::CtrlD) => {
|
||||
// When exiting clear to a new line
|
||||
if shell_integration {
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
println!();
|
||||
break;
|
||||
@ -482,7 +467,7 @@ pub fn evaluate_repl(
|
||||
println!("Error: {:?}", err);
|
||||
}
|
||||
if shell_integration {
|
||||
run_ansi_sequence(CMD_FINISHED_MARKER)?;
|
||||
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -491,6 +476,285 @@ pub fn evaluate_repl(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
|
||||
let exit_code = stack
|
||||
.get_env_var(engine_state, "LAST_EXIT_CODE")
|
||||
.and_then(|e| e.as_i64().ok());
|
||||
|
||||
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
|
||||
}
|
||||
|
||||
pub fn eval_env_change_hook(
|
||||
env_change_hook: Option<Value>,
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
) -> Result<(), ShellError> {
|
||||
if let Some(hook) = env_change_hook {
|
||||
match hook {
|
||||
Value::Record {
|
||||
cols: env_names,
|
||||
vals: hook_values,
|
||||
..
|
||||
} => {
|
||||
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
|
||||
let before = engine_state
|
||||
.previous_env_vars
|
||||
.get(env_name)
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
|
||||
let after = stack
|
||||
.get_env_var(engine_state, env_name)
|
||||
.unwrap_or_default();
|
||||
|
||||
if before != after {
|
||||
eval_hook(
|
||||
engine_state,
|
||||
stack,
|
||||
vec![("$before".into(), before), ("$after".into(), after.clone())],
|
||||
hook_value,
|
||||
)?;
|
||||
|
||||
engine_state
|
||||
.previous_env_vars
|
||||
.insert(env_name.to_string(), after);
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
return Err(ShellError::TypeMismatch(
|
||||
"record for the 'env_change' hook".to_string(),
|
||||
x.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn eval_hook(
|
||||
engine_state: &mut EngineState,
|
||||
stack: &mut Stack,
|
||||
arguments: Vec<(String, Value)>,
|
||||
value: &Value,
|
||||
) -> Result<(), ShellError> {
|
||||
let value_span = value.span()?;
|
||||
|
||||
let condition_path = PathMember::String {
|
||||
val: "condition".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
|
||||
let code_path = PathMember::String {
|
||||
val: "code".to_string(),
|
||||
span: value_span,
|
||||
};
|
||||
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
eval_hook(engine_state, stack, arguments.clone(), val)?
|
||||
}
|
||||
}
|
||||
Value::Record { .. } => {
|
||||
let do_run_hook =
|
||||
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
|
||||
match condition {
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
match run_hook_block(
|
||||
engine_state,
|
||||
stack,
|
||||
block_id,
|
||||
arguments.clone(),
|
||||
block_span,
|
||||
) {
|
||||
Ok(value) => match value {
|
||||
Value::Bool { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"boolean output".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// always run the hook
|
||||
true
|
||||
};
|
||||
|
||||
if do_run_hook {
|
||||
match value.clone().follow_cell_path(&[code_path], false)? {
|
||||
Value::String {
|
||||
val,
|
||||
span: source_span,
|
||||
} => {
|
||||
let (block, delta, vars) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
let mut vars: Vec<(VarId, Value)> = vec![];
|
||||
|
||||
for (name, val) in arguments {
|
||||
let var_id = working_set.add_variable(
|
||||
name.as_bytes().to_vec(),
|
||||
val.span()?,
|
||||
Type::Any,
|
||||
);
|
||||
|
||||
vars.push((var_id, val));
|
||||
}
|
||||
|
||||
let (output, err) =
|
||||
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"valid source code".into(),
|
||||
"source code with syntax errors".into(),
|
||||
source_span,
|
||||
));
|
||||
}
|
||||
|
||||
(output, working_set.render(), vars)
|
||||
};
|
||||
|
||||
engine_state.merge_delta(delta)?;
|
||||
let input = PipelineData::new(value_span);
|
||||
|
||||
let var_ids: Vec<VarId> = vars
|
||||
.into_iter()
|
||||
.map(|(var_id, val)| {
|
||||
stack.add_var(var_id, val);
|
||||
var_id
|
||||
})
|
||||
.collect();
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
report_error_new(engine_state, &err);
|
||||
}
|
||||
}
|
||||
|
||||
for var_id in var_ids.iter() {
|
||||
stack.vars.remove(var_id);
|
||||
}
|
||||
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
|
||||
let cwd = get_guaranteed_cwd(engine_state, stack);
|
||||
engine_state.merge_env(stack, cwd)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block or string".to_string(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span: block_span,
|
||||
..
|
||||
} => {
|
||||
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"block, record, or list of records".into(),
|
||||
format!("{}", other.get_type()),
|
||||
other.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_hook_block(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: BlockId,
|
||||
arguments: Vec<(String, Value)>,
|
||||
span: Span,
|
||||
) -> Result<Value, ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let input = PipelineData::new(span);
|
||||
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
for (idx, PositionalArg { var_id, .. }) in
|
||||
block.signature.required_positional.iter().enumerate()
|
||||
{
|
||||
if let Some(var_id) = var_id {
|
||||
if let Some(arg) = arguments.get(idx) {
|
||||
callee_stack.add_var(*var_id, arg.1.clone())
|
||||
} else {
|
||||
return Err(ShellError::IncompatibleParametersSingle(
|
||||
"This hook block has too many parameters".into(),
|
||||
span,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
||||
Value::Error { error } => Err(error),
|
||||
val => {
|
||||
// If all went fine, preserve the environment of the called block
|
||||
let caller_env_vars = stack.get_env_var_names(engine_state);
|
||||
|
||||
// remove env vars that are present in the caller but not in the callee
|
||||
// (the callee hid them)
|
||||
for var in caller_env_vars.iter() {
|
||||
if !callee_stack.has_env_var(engine_state, var) {
|
||||
stack.remove_env_var(engine_state, var);
|
||||
}
|
||||
}
|
||||
|
||||
// add new env vars from callee to caller
|
||||
for (var, value) in callee_stack.get_stack_env_vars() {
|
||||
stack.add_env_var(var, value);
|
||||
}
|
||||
|
||||
Ok(val)
|
||||
}
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||
match io::stdout().write_all(seq.as_bytes()) {
|
||||
Ok(it) => it,
|
||||
@ -515,62 +779,33 @@ fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_hook(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
arguments: Vec<Value>,
|
||||
value: &Value,
|
||||
) -> Result<(), ShellError> {
|
||||
match value {
|
||||
Value::List { vals, .. } => {
|
||||
for val in vals {
|
||||
run_hook(engine_state, stack, arguments.clone(), val)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Value::Block {
|
||||
val: block_id,
|
||||
span,
|
||||
..
|
||||
} => run_hook_block(engine_state, stack, *block_id, arguments, *span),
|
||||
x => match x.span() {
|
||||
Ok(span) => Err(ShellError::MissingConfigValue(
|
||||
"block for hook in config".into(),
|
||||
span,
|
||||
)),
|
||||
_ => Err(ShellError::MissingConfigValue(
|
||||
"block for hook in config".into(),
|
||||
Span { start: 0, end: 0 },
|
||||
)),
|
||||
},
|
||||
}
|
||||
lazy_static! {
|
||||
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
|
||||
static ref DRIVE_PATH_REGEX: Regex =
|
||||
Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
|
||||
}
|
||||
|
||||
pub fn run_hook_block(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
block_id: BlockId,
|
||||
arguments: Vec<Value>,
|
||||
span: Span,
|
||||
) -> Result<(), ShellError> {
|
||||
let block = engine_state.get_block(block_id);
|
||||
let input = PipelineData::new(span);
|
||||
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
|
||||
for (idx, PositionalArg { var_id, .. }) in
|
||||
block.signature.required_positional.iter().enumerate()
|
||||
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
|
||||
fn looks_like_path(orig: &str) -> bool {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
if let Some(var_id) = var_id {
|
||||
callee_stack.add_var(*var_id, arguments[idx].clone())
|
||||
if DRIVE_PATH_REGEX.is_match(orig) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
|
||||
Ok(pipeline_data) => match pipeline_data.into_value(span) {
|
||||
Value::Error { error } => Err(error),
|
||||
_ => Ok(()),
|
||||
},
|
||||
Err(err) => Err(err),
|
||||
orig.starts_with('.')
|
||||
|| orig.starts_with('~')
|
||||
|| orig.starts_with('/')
|
||||
|| orig.starts_with('\\')
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn looks_like_path_windows_drive_path_works() {
|
||||
let on_windows = cfg!(windows);
|
||||
assert_eq!(looks_like_path("C:"), on_windows);
|
||||
assert_eq!(looks_like_path("D:\\"), on_windows);
|
||||
assert_eq!(looks_like_path("E:/"), on_windows);
|
||||
assert_eq!(looks_like_path("F:\\some_dir"), on_windows);
|
||||
assert_eq!(looks_like_path("G:/some_dir"), on_windows);
|
||||
}
|
||||
|
@ -224,16 +224,11 @@ pub fn eval_source(
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
let cwd = match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
get_init_cwd()
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
set_last_exit_code(stack, 1);
|
||||
report_error_new(engine_state, &err);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
|
||||
|
||||
match eval_block(engine_state, stack, &block, input, false, false) {
|
||||
Ok(mut pipeline_data) => {
|
||||
@ -247,7 +242,7 @@ pub fn eval_source(
|
||||
set_last_exit_code(stack, 0);
|
||||
}
|
||||
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false) {
|
||||
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
@ -297,6 +292,15 @@ pub fn report_error(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_error_new(
|
||||
engine_state: &EngineState,
|
||||
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
|
||||
) {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, error);
|
||||
}
|
||||
|
||||
pub fn get_init_cwd() -> PathBuf {
|
||||
match std::env::current_dir() {
|
||||
Ok(cwd) => cwd,
|
||||
@ -310,6 +314,17 @@ pub fn get_init_cwd() -> PathBuf {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
|
||||
match nu_engine::env::current_dir(engine_state, stack) {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
get_init_cwd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
65
crates/nu-cli/tests/alias.rs
Normal file
65
crates/nu-cli/tests/alias.rs
Normal file
@ -0,0 +1,65 @@
|
||||
pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn alias_of_command_and_flags() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -l"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_basic_command() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls "#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("ll t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_of_another_alias() {
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Create an alias
|
||||
let alias = r#"alias ll = ls -la"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
|
||||
// Create the second alias
|
||||
let alias = r#"alias lf = ll -f"#;
|
||||
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let suggestions = completer.complete("lf t", 4);
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
@ -2,10 +2,24 @@ pub mod support;
|
||||
|
||||
use nu_cli::NuCompleter;
|
||||
use reedline::Completer;
|
||||
use rstest::{fixture, rstest};
|
||||
use support::{match_suggestions, new_engine};
|
||||
|
||||
#[test]
|
||||
fn variables_completions() {
|
||||
#[fixture]
|
||||
fn completer() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
// Add record value as example
|
||||
let record = "def tst [--mod -s] {}";
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
#[fixture]
|
||||
fn completer_strings() -> NuCompleter {
|
||||
// Create a new engine
|
||||
let (dir, _, mut engine, mut stack) = new_engine();
|
||||
|
||||
@ -14,16 +28,42 @@ fn variables_completions() {
|
||||
def my-command [animal: string@animals] { print $animal }"#;
|
||||
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
|
||||
|
||||
// Instatiate a new completer
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
// Instantiate a new completer
|
||||
NuCompleter::new(std::sync::Arc::new(engine), stack)
|
||||
}
|
||||
|
||||
// Test completions for $nu
|
||||
let suggestions = completer.complete("my-command ", 11);
|
||||
|
||||
assert_eq!(3, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
|
||||
// Match results
|
||||
#[rstest]
|
||||
fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst --", 6);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
|
||||
// dbg!(&expected, &suggestions);
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_single_dash_argument(mut completer: NuCompleter) {
|
||||
let suggestions = completer.complete("tst -", 5);
|
||||
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_command(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 9);
|
||||
let expected: Vec<String> = vec!["my-command".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_subcommands(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn variables_completions_subcommands_2(mut completer_strings: NuCompleter) {
|
||||
let suggestions = completer_strings.complete("my-command ", 11);
|
||||
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
|
||||
match_suggestions(expected, suggestions);
|
||||
}
|
||||
|
@ -30,8 +30,8 @@ fn file_completions() {
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
|
||||
// Test completions for the completions/another folder
|
||||
let target_dir = format!("cd {}", folder(dir.join("another")));
|
||||
// Test completions for a file
|
||||
let target_dir = format!("cp {}", folder(dir.join("another")));
|
||||
let suggestions = completer.complete(&target_dir, target_dir.len());
|
||||
|
||||
// Create the expected values
|
||||
@ -40,3 +40,233 @@ fn file_completions() {
|
||||
// Match the results
|
||||
match_suggestions(expected_paths, suggestions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_ls_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "ls ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
#[test]
|
||||
fn command_open_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "open ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_rm_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "rm ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_cp_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "cp ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_save_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "save ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_touch_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "touch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_watch_completion() {
|
||||
let (_, _, engine, stack) = new_engine();
|
||||
|
||||
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
|
||||
|
||||
let target_dir = "watch ";
|
||||
let suggestions = completer.complete(target_dir, target_dir.len());
|
||||
|
||||
#[cfg(windows)]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a\\".to_string(),
|
||||
"test_b\\".to_string(),
|
||||
"another\\".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder\\".to_string(),
|
||||
];
|
||||
#[cfg(not(windows))]
|
||||
let expected_paths: Vec<String> = vec![
|
||||
"nushell".to_string(),
|
||||
"test_a/".to_string(),
|
||||
"test_b/".to_string(),
|
||||
"another/".to_string(),
|
||||
"custom_completion.nu".to_string(),
|
||||
".hidden_file".to_string(),
|
||||
".hidden_folder/".to_string(),
|
||||
];
|
||||
|
||||
match_suggestions(expected_paths, suggestions)
|
||||
}
|
||||
|
@ -14,15 +14,17 @@ fn flag_completions() {
|
||||
// Test completions for the 'ls' flags
|
||||
let suggestions = completer.complete("ls -", 4);
|
||||
|
||||
assert_eq!(12, suggestions.len());
|
||||
assert_eq!(14, suggestions.len());
|
||||
|
||||
let expected: Vec<String> = vec![
|
||||
"--all".into(),
|
||||
"--directory".into(),
|
||||
"--du".into(),
|
||||
"--full-paths".into(),
|
||||
"--help".into(),
|
||||
"--long".into(),
|
||||
"--short-names".into(),
|
||||
"-D".into(),
|
||||
"-a".into(),
|
||||
"-d".into(),
|
||||
"-f".into(),
|
||||
|
@ -4,7 +4,7 @@ use nu_command::create_default_context;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, ShellError, Span, Value,
|
||||
};
|
||||
use nu_test_support::fs;
|
||||
@ -23,14 +23,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
dir_str.push(SEP);
|
||||
|
||||
// Create a new engine with default context
|
||||
let mut engine_state = create_default_context(&dir);
|
||||
let mut engine_state = create_default_context();
|
||||
|
||||
// New stack
|
||||
let mut stack = Stack::new();
|
||||
|
||||
// New delta state
|
||||
let delta = StateDelta::new(&engine_state);
|
||||
|
||||
// Add pwd as env var
|
||||
stack.add_env_var(
|
||||
"PWD".to_string(),
|
||||
@ -53,8 +50,8 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
},
|
||||
);
|
||||
|
||||
// Merge delta
|
||||
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
|
||||
// Merge environment into the permanent state
|
||||
let merge_result = engine_state.merge_env(&mut stack, &dir);
|
||||
assert!(merge_result.is_ok());
|
||||
|
||||
(dir, dir_str, engine_state, stack)
|
||||
@ -62,6 +59,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
|
||||
|
||||
// match a list of suggestions with the expected values
|
||||
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
|
||||
let expected_len = expected.len();
|
||||
let suggestions_len = suggestions.len();
|
||||
if expected_len != suggestions_len {
|
||||
panic!(
|
||||
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
|
||||
Suggestions: {suggestions:#?} \n\
|
||||
Expected: {expected:#?}\n"
|
||||
)
|
||||
}
|
||||
expected.iter().zip(suggestions).for_each(|it| {
|
||||
assert_eq!(it.0, &it.1.value);
|
||||
});
|
||||
@ -97,6 +103,11 @@ pub fn merge_input(
|
||||
|
||||
(block, working_set.render())
|
||||
};
|
||||
|
||||
if let Err(err) = engine_state.merge_delta(delta) {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
assert!(eval_block(
|
||||
engine_state,
|
||||
stack,
|
||||
@ -112,6 +123,6 @@ pub fn merge_input(
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
// Merge delta
|
||||
engine_state.merge_delta(delta, Some(stack), &dir)
|
||||
// Merge environment into the permanent state
|
||||
engine_state.merge_env(stack, &dir)
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ description = "Color configuration code used by Nushell"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.64.0"
|
||||
version = "0.66.1"
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.1" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
nu-json = { path = "../nu-json", version = "0.64.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.64.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.66.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.66.1" }
|
||||
serde = { version="1.0.123", features=["derive"] }
|
||||
|
@ -4,30 +4,31 @@ description = "Nushell's built-in commands"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
version = "0.64.0"
|
||||
version = "0.66.1"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.64.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.64.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.64.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.64.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.64.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.64.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.64.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.64.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.64.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.64.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.64.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.64.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.64.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.66.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.66.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.66.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.66.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.66.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.66.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.66.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.66.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.66.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.66.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.66.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.66.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.66.1" }
|
||||
nu-ansi-term = "0.46.0"
|
||||
|
||||
# Potential dependencies for extras
|
||||
alphanumeric-sort = "1.4.4"
|
||||
base64 = "0.13.0"
|
||||
byteorder = "1.4.3"
|
||||
bytesize = "1.1.0"
|
||||
calamine = "0.18.0"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
@ -51,15 +52,15 @@ is-root = "0.1.2"
|
||||
itertools = "0.10.0"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
lscolors = { version = "0.9.0", features = ["crossterm"]}
|
||||
lscolors = { version = "0.10.0", features = ["crossterm"]}
|
||||
md5 = { package = "md-5", version = "0.10.0" }
|
||||
meval = "0.2.0"
|
||||
mime = "0.3.16"
|
||||
notify = "4.0.17"
|
||||
num = { version = "0.4.0", optional = true }
|
||||
pathdiff = "0.2.1"
|
||||
powierza-coefficient = "1.0"
|
||||
quick-xml = "0.22"
|
||||
powierza-coefficient = "1.0.1"
|
||||
quick-xml = "0.23.0"
|
||||
rand = "0.8"
|
||||
rayon = "1.5.1"
|
||||
regex = "1.5.4"
|
||||
@ -72,20 +73,20 @@ serde_urlencoded = "0.7.0"
|
||||
serde_yaml = "0.8.16"
|
||||
sha2 = "0.10.0"
|
||||
# Disable default features b/c the default features build Git (very slow to compile)
|
||||
shadow-rs = { version = "0.11.0", default-features = false }
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
sysinfo = "0.23.5"
|
||||
terminal_size = "0.1.17"
|
||||
thiserror = "1.0.29"
|
||||
titlecase = "1.1.0"
|
||||
sysinfo = "0.24.6"
|
||||
terminal_size = "0.2.1"
|
||||
thiserror = "1.0.31"
|
||||
titlecase = "2.0.0"
|
||||
toml = "0.5.8"
|
||||
unicode-segmentation = "1.8.0"
|
||||
url = "2.2.1"
|
||||
uuid = { version = "0.8.2", features = ["v4"] }
|
||||
uuid = { version = "1.1.2", features = ["v4"] }
|
||||
which = { version = "4.2.2", optional = true }
|
||||
reedline = { version = "0.7.0", features = ["bashisms", "sqlite"]}
|
||||
wax = { version = "0.4.0", features = ["diagnostics"] }
|
||||
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
|
||||
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
|
||||
wax = { version = "0.5.0", features = ["diagnostics"] }
|
||||
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
|
||||
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
@ -97,7 +98,7 @@ version = "2.1.3"
|
||||
optional = true
|
||||
|
||||
[dependencies.polars]
|
||||
version = "0.21.1"
|
||||
version = "0.22.8"
|
||||
# path = "../../../../polars/polars"
|
||||
optional = true
|
||||
features = [
|
||||
@ -105,7 +106,7 @@ features = [
|
||||
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
|
||||
"rolling_window", "strings", "rows", "random",
|
||||
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
|
||||
"dynamic_groupby"
|
||||
"dynamic_groupby", "dtype-categorical", "concat_str"
|
||||
]
|
||||
|
||||
[target.'cfg(windows)'.dependencies.windows]
|
||||
@ -125,10 +126,11 @@ dataframe = ["polars", "num"]
|
||||
database = ["sqlparser", "rusqlite"]
|
||||
|
||||
[build-dependencies]
|
||||
shadow-rs = { version = "0.11.0", default-features = false }
|
||||
shadow-rs = { version = "0.16.1", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hamcrest2 = "0.3.0"
|
||||
dirs-next = "2.0.0"
|
||||
quickcheck = "1.0.3"
|
||||
quickcheck_macros = "1.0.0"
|
||||
rstest = "0.15.0"
|
||||
|
167
crates/nu-command/src/bytes/add.rs
Normal file
167
crates/nu-command/src/bytes/add.rs
Normal file
@ -0,0 +1,167 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
added_data: Vec<u8>,
|
||||
index: Option<usize>,
|
||||
end: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesAdd;
|
||||
|
||||
impl Command for BytesAdd {
|
||||
fn name(&self) -> &str {
|
||||
"bytes add"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes add")
|
||||
.required("data", SyntaxShape::Binary, "the binary to add")
|
||||
.named(
|
||||
"index",
|
||||
SyntaxShape::Int,
|
||||
"index to insert binary data",
|
||||
Some('i'),
|
||||
)
|
||||
.switch("end", "add to the end of binary", Some('e'))
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"add specified bytes to the input"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["append", "truncate", "padding"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
|
||||
let end = call.has_flag("end");
|
||||
|
||||
let arg = Arguments {
|
||||
added_data,
|
||||
index,
|
||||
end,
|
||||
column_paths,
|
||||
};
|
||||
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Add bytes `0x[AA]` to `0x[1F FF AA AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes add 0x[AA]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0x1F, 0xFF, 0xAA, 0xAA],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Add bytes `0x[AA BB]` to `0x[1F FF AA AA]` at index 1",
|
||||
example: "0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x1F, 0xAA, 0xBB, 0xFF, 0xAA, 0xAA],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Add bytes `0x[11]` to `0x[FF AA AA]` at the end",
|
||||
example: "0x[FF AA AA] | bytes add 0x[11] -e",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xAA, 0xAA, 0x11],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Add bytes `0x[11 22 33]` to `0x[FF AA AA]` at the end, at index 1(the index is start from end)",
|
||||
example: "0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xAA, 0x11, 0x22, 0x33, 0xBB],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn add(input: &[u8], args: &Arguments, span: Span) -> Value {
|
||||
match args.index {
|
||||
None => {
|
||||
if args.end {
|
||||
let mut added_data = args.added_data.clone();
|
||||
let mut result = input.to_vec();
|
||||
result.append(&mut added_data);
|
||||
Value::Binary { val: result, span }
|
||||
} else {
|
||||
let mut result = args.added_data.clone();
|
||||
let mut input = input.to_vec();
|
||||
result.append(&mut input);
|
||||
Value::Binary { val: result, span }
|
||||
}
|
||||
}
|
||||
Some(mut indx) => {
|
||||
let inserted_index = if args.end {
|
||||
input.len().saturating_sub(indx)
|
||||
} else {
|
||||
if indx > input.len() {
|
||||
indx = input.len()
|
||||
}
|
||||
indx
|
||||
};
|
||||
let mut result = vec![];
|
||||
let mut prev_data = input[..inserted_index].to_vec();
|
||||
result.append(&mut prev_data);
|
||||
let mut added_data = args.added_data.clone();
|
||||
result.append(&mut added_data);
|
||||
let mut after_data = input[inserted_index..].to_vec();
|
||||
result.append(&mut after_data);
|
||||
Value::Binary { val: result, span }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesAdd {})
|
||||
}
|
||||
}
|
281
crates/nu-command/src/bytes/at.rs
Normal file
281
crates/nu-command/src/bytes/at.rs
Normal file
@ -0,0 +1,281 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesAt;
|
||||
|
||||
struct Arguments {
|
||||
start: isize,
|
||||
end: isize,
|
||||
arg_span: Span,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// ensure given `range` is valid, and returns [start, end, val_span] pair.
|
||||
fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellError> {
|
||||
let (start, end, span) = match range {
|
||||
Value::List { mut vals, span } => {
|
||||
if vals.len() != 2 {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"More than two indices given".to_string(),
|
||||
span,
|
||||
));
|
||||
} else {
|
||||
let end = vals.pop().expect("Already check has size 2");
|
||||
let end = match end {
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
let start = vals.pop().expect("Already check has size 1");
|
||||
let start = match start {
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => val,
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes. Expecting a string or int".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
(start, end, span)
|
||||
}
|
||||
}
|
||||
Value::String { val, span } => {
|
||||
let splitted_result = val.split_once(',');
|
||||
match splitted_result {
|
||||
Some((start, end)) => (start.to_string(), end.to_string(), span),
|
||||
None => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
other.span().unwrap_or(head),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let start: isize = if start.is_empty() || start == "_" {
|
||||
0
|
||||
} else {
|
||||
match start.trim().parse() {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
let end: isize = if end.is_empty() || end == "_" {
|
||||
isize::max_value()
|
||||
} else {
|
||||
match end.trim().parse() {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"could not perform subbytes".to_string(),
|
||||
span,
|
||||
))
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok((start, end, span))
|
||||
}
|
||||
|
||||
impl Command for BytesAt {
|
||||
fn name(&self) -> &str {
|
||||
"bytes at"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes at")
|
||||
.required("range", SyntaxShape::Any, "the indexes to get bytes")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally get bytes by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get bytes defined by a range. Note that the start is included but the end is excluded, and that the first byte is index 0."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["slice"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let range: Value = call.req(engine_state, stack, 0)?;
|
||||
let (start, end, arg_span) = parse_range(range, call.head)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
start,
|
||||
end,
|
||||
arg_span,
|
||||
column_paths,
|
||||
};
|
||||
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at [3 4]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Alternatively, you can use the form",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at '3,4'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Drop the last `n` characters from the string",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at ',-3'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x33, 0x44, 0x55],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Get the remaining characters from a starting index",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at '3,'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10, 0x01, 0x13],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Get the characters from the beginning until ending index",
|
||||
example: " 0x[33 44 55 10 01 13] | bytes at ',4'",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x33, 0x44, 0x55, 0x10],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Or the characters from the beginning until ending index inside a table",
|
||||
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at "1," ColB ColC"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::Binary {
|
||||
val: vec![0x11, 0x12, 0x13],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x18, 0x19],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn at(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
let len: isize = input.len() as isize;
|
||||
|
||||
let start: isize = if arg.start < 0 {
|
||||
arg.start + len
|
||||
} else {
|
||||
arg.start
|
||||
};
|
||||
let end: isize = if arg.end < 0 {
|
||||
std::cmp::max(len + arg.end, 0)
|
||||
} else {
|
||||
arg.end
|
||||
};
|
||||
|
||||
if start < len && end >= 0 {
|
||||
match start.cmp(&end) {
|
||||
Ordering::Equal => Value::Binary { val: vec![], span },
|
||||
Ordering::Greater => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"End must be greater than or equal to Start".to_string(),
|
||||
arg.arg_span,
|
||||
),
|
||||
},
|
||||
Ordering::Less => Value::Binary {
|
||||
val: {
|
||||
let input_iter = input.iter().copied().skip(start as usize);
|
||||
if end == isize::max_value() {
|
||||
input_iter.collect()
|
||||
} else {
|
||||
input_iter.take((end - start) as usize).collect()
|
||||
}
|
||||
},
|
||||
span,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
Value::Binary { val: vec![], span }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesAt {})
|
||||
}
|
||||
}
|
81
crates/nu-command/src/bytes/build_.rs
Normal file
81
crates/nu-command/src/bytes/build_.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use nu_engine::eval_expression;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesBuild;
|
||||
|
||||
impl Command for BytesBuild {
|
||||
fn name(&self) -> &str {
|
||||
"bytes build"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create bytes from the arguments."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["concatenate", "join"]
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("bytes build")
|
||||
.rest("rest", SyntaxShape::Any, "list of bytes")
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
example: "bytes build 0x[01 02] 0x[03] 0x[04]",
|
||||
description: "Builds binary data from 0x[01 02], 0x[03], 0x[04]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x01, 0x02, 0x03, 0x04],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let mut output = vec![];
|
||||
for expr in call.positional_iter() {
|
||||
let val = eval_expression(engine_state, stack, expr)?;
|
||||
match val {
|
||||
Value::Binary { mut val, .. } => output.append(&mut val),
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"only support expression which yields to binary data".to_string(),
|
||||
other.span().unwrap_or(call.head),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Binary {
|
||||
val: output,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesBuild {})
|
||||
}
|
||||
}
|
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
49
crates/nu-command/src/bytes/bytes_.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoPipelineData, PipelineData, Signature, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Bytes;
|
||||
|
||||
impl Command for Bytes {
|
||||
fn name(&self) -> &str {
|
||||
"bytes"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes").category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Various commands for working with byte data"
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(&Bytes.signature(), &Bytes.examples(), engine_state, stack),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Bytes;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Bytes {})
|
||||
}
|
||||
}
|
127
crates/nu-command/src/bytes/collect.rs
Normal file
127
crates/nu-command/src/bytes/collect.rs
Normal file
@ -0,0 +1,127 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BytesCollect;
|
||||
|
||||
impl Command for BytesCollect {
|
||||
fn name(&self) -> &str {
|
||||
"bytes collect"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes collect")
|
||||
.optional(
|
||||
"separator",
|
||||
SyntaxShape::Binary,
|
||||
"optional separator to use when creating binary",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Concatenate multiple binary into a single binary, with an optional separator between each"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["join", "concatenate"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let separator: Option<Vec<u8>> = call.opt(engine_state, stack, 0)?;
|
||||
// input should be a list of binary data.
|
||||
let mut output_binary = vec![];
|
||||
for value in input {
|
||||
match value {
|
||||
Value::Binary { mut val, .. } => {
|
||||
output_binary.append(&mut val);
|
||||
// manually concat
|
||||
// TODO: make use of std::slice::Join when it's available in stable.
|
||||
if let Some(sep) = &separator {
|
||||
let mut work_sep = sep.clone();
|
||||
output_binary.append(&mut work_sep)
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"The element type is {}, this command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
other.span().unwrap_or(call.head),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match separator {
|
||||
None => Ok(Value::Binary {
|
||||
val: output_binary,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data()),
|
||||
Some(sep) => {
|
||||
if output_binary.is_empty() {
|
||||
Ok(Value::Binary {
|
||||
val: output_binary,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
// have push one extra separator in previous step, pop them out.
|
||||
for _ in sep {
|
||||
let _ = output_binary.pop();
|
||||
}
|
||||
Ok(Value::Binary {
|
||||
val: output_binary,
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create a byte array from input",
|
||||
example: "[0x[11] 0x[13 15]] | bytes collect",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x11, 0x13, 0x15],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Create a byte array from input with a separator",
|
||||
example: "[0x[11] 0x[33] 0x[44]] | bytes collect 0x[01]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x11, 0x01, 0x33, 0x01, 0x44],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesCollect {})
|
||||
}
|
||||
}
|
116
crates/nu-command/src/bytes/ends_with.rs
Normal file
116
crates/nu-command/src/bytes/ends_with.rs
Normal file
@ -0,0 +1,116 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesEndsWith;
|
||||
|
||||
impl Command for BytesEndsWith {
|
||||
fn name(&self) -> &str {
|
||||
"bytes ends-with"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes ends-with")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Check if bytes ends with a pattern"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["pattern", "match", "find", "search"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
column_paths,
|
||||
};
|
||||
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Checks if binary ends with `0x[AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary ends with `0x[FF AA AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary ends with `0x[11]`",
|
||||
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
|
||||
result: Some(Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
||||
Value::Bool {
|
||||
val: input.ends_with(pattern),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesEndsWith {})
|
||||
}
|
||||
}
|
210
crates/nu-command/src/bytes/index_of.rs
Normal file
210
crates/nu-command/src/bytes/index_of.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
end: bool,
|
||||
all: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesIndexOf;
|
||||
|
||||
impl Command for BytesIndexOf {
|
||||
fn name(&self) -> &str {
|
||||
"bytes index-of"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes index-of")
|
||||
.required(
|
||||
"pattern",
|
||||
SyntaxShape::Binary,
|
||||
"the pattern to find index of",
|
||||
)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally returns index of pattern in string by column paths",
|
||||
)
|
||||
.switch("all", "returns all matched index", Some('a'))
|
||||
.switch("end", "search from the end of the binary", Some('e'))
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Returns start index of first occurrence of pattern in bytes, or -1 if no match"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["pattern", "match", "find", "search", "index"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
end: call.has_flag("end"),
|
||||
all: call.has_flag("all"),
|
||||
column_paths,
|
||||
};
|
||||
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Returns index of pattern in bytes",
|
||||
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
|
||||
result: Some(Value::test_int(1)),
|
||||
},
|
||||
Example {
|
||||
description: "Returns index of pattern, search from end",
|
||||
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55]",
|
||||
result: Some(Value::test_int(6)),
|
||||
},
|
||||
Example {
|
||||
description: "Returns all matched index",
|
||||
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44]",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(0), Value::test_int(5), Value::test_int(7)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Returns all matched index, searching from end",
|
||||
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44]",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(7), Value::test_int(5), Value::test_int(0)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Returns index of pattern for specific column",
|
||||
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::test_int(0),
|
||||
Value::Binary {
|
||||
val: vec![0x14, 0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::test_int(-1),
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn index_of(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
if arg.all {
|
||||
search_all_index(input, &arg.pattern, arg.end, span)
|
||||
} else {
|
||||
let mut iter = input.windows(arg.pattern.len());
|
||||
|
||||
if arg.end {
|
||||
Value::Int {
|
||||
val: iter
|
||||
.rev()
|
||||
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||
.map(|x| (input.len() - arg.pattern.len() - x) as i64)
|
||||
.unwrap_or(-1),
|
||||
span,
|
||||
}
|
||||
} else {
|
||||
Value::Int {
|
||||
val: iter
|
||||
.position(|sub_bytes| sub_bytes == arg.pattern)
|
||||
.map(|x| x as i64)
|
||||
.unwrap_or(-1),
|
||||
span,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
|
||||
let mut result = vec![];
|
||||
if from_end {
|
||||
let (mut left, mut right) = (
|
||||
input.len() as isize - pattern.len() as isize,
|
||||
input.len() as isize,
|
||||
);
|
||||
while left >= 0 {
|
||||
if &input[left as usize..right as usize] == pattern {
|
||||
result.push(Value::Int {
|
||||
val: left as i64,
|
||||
span,
|
||||
});
|
||||
left -= pattern.len() as isize;
|
||||
right -= pattern.len() as isize;
|
||||
} else {
|
||||
left -= 1;
|
||||
right -= 1;
|
||||
}
|
||||
}
|
||||
Value::List { vals: result, span }
|
||||
} else {
|
||||
// doing find stuff.
|
||||
let (mut left, mut right) = (0, pattern.len());
|
||||
let input_len = input.len();
|
||||
let pattern_len = pattern.len();
|
||||
while right <= input_len {
|
||||
if &input[left..right] == pattern {
|
||||
result.push(Value::Int {
|
||||
val: left as i64,
|
||||
span,
|
||||
});
|
||||
left += pattern_len;
|
||||
right += pattern_len;
|
||||
} else {
|
||||
left += 1;
|
||||
right += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Value::List { vals: result, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesIndexOf {})
|
||||
}
|
||||
}
|
98
crates/nu-command/src/bytes/length.rs
Normal file
98
crates/nu-command/src/bytes/length.rs
Normal file
@ -0,0 +1,98 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesLen;
|
||||
|
||||
struct Arguments {
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
impl Command for BytesLen {
|
||||
fn name(&self) -> &str {
|
||||
"bytes length"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes length")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally find length of binary by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output the length of any bytes in the pipeline"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["len", "size", "count"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments { column_paths };
|
||||
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "0x[1F FF AA AB] | bytes length",
|
||||
result: Some(Value::test_int(4)),
|
||||
},
|
||||
Example {
|
||||
description: "Return the lengths of multiple strings",
|
||||
example: "[0x[1F FF AA AB] 0x[1F]] | bytes length",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::test_int(4), Value::test_int(1)],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn length(input: &[u8], _arg: &Arguments, span: Span) -> Value {
|
||||
Value::Int {
|
||||
val: input.len() as i64,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesLen {})
|
||||
}
|
||||
}
|
98
crates/nu-command/src/bytes/mod.rs
Normal file
98
crates/nu-command/src/bytes/mod.rs
Normal file
@ -0,0 +1,98 @@
|
||||
mod add;
|
||||
mod at;
|
||||
mod build_;
|
||||
mod bytes_;
|
||||
mod collect;
|
||||
mod ends_with;
|
||||
mod index_of;
|
||||
mod length;
|
||||
mod remove;
|
||||
mod replace;
|
||||
mod reverse;
|
||||
mod starts_with;
|
||||
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use add::BytesAdd;
|
||||
pub use at::BytesAt;
|
||||
pub use build_::BytesBuild;
|
||||
pub use bytes_::Bytes;
|
||||
pub use collect::BytesCollect;
|
||||
pub use ends_with::BytesEndsWith;
|
||||
pub use index_of::BytesIndexOf;
|
||||
pub use length::BytesLen;
|
||||
pub use remove::BytesRemove;
|
||||
pub use replace::BytesReplace;
|
||||
pub use reverse::BytesReverse;
|
||||
pub use starts_with::BytesStartsWith;
|
||||
|
||||
trait BytesArgument {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>>;
|
||||
}
|
||||
|
||||
/// map input pipeline data, for each elements, if it's Binary, invoke relative `cmd` with `arg`.
|
||||
fn operate<C, A>(
|
||||
cmd: C,
|
||||
mut arg: A,
|
||||
input: PipelineData,
|
||||
span: Span,
|
||||
ctrlc: Option<Arc<AtomicBool>>,
|
||||
) -> Result<PipelineData, ShellError>
|
||||
where
|
||||
A: BytesArgument + Send + Sync + 'static,
|
||||
C: Fn(&[u8], &A, Span) -> Value + Send + Sync + 'static + Clone + Copy,
|
||||
{
|
||||
match arg.take_column_paths() {
|
||||
None => input.map(
|
||||
move |v| match v {
|
||||
Value::Binary {
|
||||
val,
|
||||
span: val_span,
|
||||
} => cmd(&val, &arg, val_span),
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Input's type is {}. This command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
span,
|
||||
),
|
||||
},
|
||||
},
|
||||
ctrlc,
|
||||
),
|
||||
Some(column_paths) => {
|
||||
let arg = Arc::new(arg);
|
||||
input.map(
|
||||
move |mut v| {
|
||||
for path in &column_paths {
|
||||
let opt = arg.clone();
|
||||
let r = v.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| {
|
||||
match old {
|
||||
Value::Binary {val, span: val_span} => cmd(val, &opt, *val_span),
|
||||
other => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!(
|
||||
"Input's type is {}. This command only works with bytes.",
|
||||
other.get_type()
|
||||
),
|
||||
span,
|
||||
),
|
||||
}}}),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
}
|
||||
}
|
||||
v
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
196
crates/nu-command/src/bytes/remove.rs
Normal file
196
crates/nu-command/src/bytes/remove.rs
Normal file
@ -0,0 +1,196 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
end: bool,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesRemove;
|
||||
|
||||
impl Command for BytesRemove {
|
||||
fn name(&self) -> &str {
|
||||
"bytes remove"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes remove")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to find")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally remove bytes by column paths",
|
||||
)
|
||||
.switch("end", "remove from end of binary", Some('e'))
|
||||
.switch("all", "remove occurrences of finding binary", Some('a'))
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"remove bytes"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["search", "shift", "switch"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||
if pattern_to_remove.item.is_empty() {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"the pattern to remove cannot be empty".to_string(),
|
||||
pattern_to_remove.span,
|
||||
));
|
||||
}
|
||||
|
||||
let pattern_to_remove: Vec<u8> = pattern_to_remove.item;
|
||||
let arg = Arguments {
|
||||
pattern: pattern_to_remove,
|
||||
end: call.has_flag("end"),
|
||||
column_paths,
|
||||
all: call.has_flag("all"),
|
||||
};
|
||||
|
||||
operate(remove, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Remove contents",
|
||||
example: "0x[10 AA FF AA FF] | bytes remove 0x[10 AA]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xAA, 0xFF],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove all occurrences of find binary",
|
||||
example: "0x[10 AA 10 BB 10] | bytes remove -a 0x[10]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0xBB],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove occurrences of find binary from end",
|
||||
example: "0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Remove all occurrences of find binary in table",
|
||||
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::Binary {
|
||||
val: vec![0x12, 0x13],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x14, 0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x17, 0x18, 0x19],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
let mut result = vec![];
|
||||
let remove_all = arg.all;
|
||||
let input_len = input.len();
|
||||
let pattern_len = arg.pattern.len();
|
||||
|
||||
// Note:
|
||||
// remove_all from start and end will generate the same result.
|
||||
// so we'll put `remove_all` relative logic into else clouse.
|
||||
if arg.end && !remove_all {
|
||||
let (mut left, mut right) = (
|
||||
input.len() as isize - arg.pattern.len() as isize,
|
||||
input.len() as isize,
|
||||
);
|
||||
while left >= 0 && input[left as usize..right as usize] != arg.pattern {
|
||||
result.push(input[right as usize - 1]);
|
||||
left -= 1;
|
||||
right -= 1;
|
||||
}
|
||||
// append the remaining thing to result, this can be happeneed when
|
||||
// we have something to remove and remove_all is False.
|
||||
let mut remain = input[..left as usize].iter().copied().rev().collect();
|
||||
result.append(&mut remain);
|
||||
result = result.into_iter().rev().collect();
|
||||
Value::Binary { val: result, span }
|
||||
} else {
|
||||
let (mut left, mut right) = (0, arg.pattern.len());
|
||||
while right <= input_len {
|
||||
if input[left..right] == arg.pattern {
|
||||
left += pattern_len;
|
||||
right += pattern_len;
|
||||
if !remove_all {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
result.push(input[left]);
|
||||
left += 1;
|
||||
right += 1;
|
||||
}
|
||||
}
|
||||
// append the remaing thing to result, this can happened when
|
||||
// we have something to remove and remove_all is False.
|
||||
let mut remain = input[left..].to_vec();
|
||||
result.append(&mut remain);
|
||||
Value::Binary { val: result, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesRemove {})
|
||||
}
|
||||
}
|
171
crates/nu-command/src/bytes/replace.rs
Normal file
171
crates/nu-command/src/bytes/replace.rs
Normal file
@ -0,0 +1,171 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
struct Arguments {
|
||||
find: Vec<u8>,
|
||||
replace: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
all: bool,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytesReplace;
|
||||
|
||||
impl Command for BytesReplace {
|
||||
fn name(&self) -> &str {
|
||||
"bytes replace"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes replace")
|
||||
.required("find", SyntaxShape::Binary, "the pattern to find")
|
||||
.required("replace", SyntaxShape::Binary, "the replacement pattern")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally find and replace text by column paths",
|
||||
)
|
||||
.switch("all", "replace all occurrences of find binary", Some('a'))
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Find and replace binary"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["search", "shift", "switch"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
|
||||
if find.item.is_empty() {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"the pattern to find cannot be empty".to_string(),
|
||||
find.span,
|
||||
));
|
||||
}
|
||||
|
||||
let arg = Arguments {
|
||||
find: find.item,
|
||||
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
|
||||
column_paths,
|
||||
all: call.has_flag("all"),
|
||||
};
|
||||
|
||||
operate(replace, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Find and replace contents",
|
||||
example: "0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xFF, 0xFF, 0xAA, 0xFF],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find binary",
|
||||
example: "0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0]",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xA0, 0xAA, 0xA0, 0xBB, 0xA0],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Find and replace all occurrences of find binary in table",
|
||||
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
|
||||
vals: vec![
|
||||
Value::Binary {
|
||||
val: vec![0x13, 0x12, 0x13],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x14, 0x15, 0x16],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Binary {
|
||||
val: vec![0x17, 0x18, 0x19],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn replace(input: &[u8], arg: &Arguments, span: Span) -> Value {
|
||||
let mut replaced = vec![];
|
||||
let replace_all = arg.all;
|
||||
|
||||
// doing find-and-replace stuff.
|
||||
let (mut left, mut right) = (0, arg.find.len());
|
||||
let input_len = input.len();
|
||||
let pattern_len = arg.find.len();
|
||||
while right <= input_len {
|
||||
if input[left..right] == arg.find {
|
||||
let mut to_replace = arg.replace.clone();
|
||||
replaced.append(&mut to_replace);
|
||||
left += pattern_len;
|
||||
right += pattern_len;
|
||||
if !replace_all {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
replaced.push(input[left]);
|
||||
left += 1;
|
||||
right += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let mut remain = input[left..].to_vec();
|
||||
replaced.append(&mut remain);
|
||||
Value::Binary {
|
||||
val: replaced,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesReplace {})
|
||||
}
|
||||
}
|
104
crates/nu-command/src/bytes/reverse.rs
Normal file
104
crates/nu-command/src/bytes/reverse.rs
Normal file
@ -0,0 +1,104 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesReverse;
|
||||
|
||||
impl Command for BytesReverse {
|
||||
fn name(&self) -> &str {
|
||||
"bytes reverse"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes reverse")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Reverse every bytes in the pipeline"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "inverse", "flip"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments { column_paths };
|
||||
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Reverse bytes `0x[1F FF AA AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes reverse",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0xAA, 0xFF, 0x1F],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Reverse bytes `0x[FF AA AA]`",
|
||||
example: "0x[FF AA AA] | bytes reverse",
|
||||
result: Some(Value::Binary {
|
||||
val: vec![0xAA, 0xAA, 0xFF],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn reverse(input: &[u8], _args: &Arguments, span: Span) -> Value {
|
||||
let mut reversed_input = input.to_vec();
|
||||
reversed_input.reverse();
|
||||
Value::Binary {
|
||||
val: reversed_input,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesReverse {})
|
||||
}
|
||||
}
|
122
crates/nu-command/src/bytes/starts_with.rs
Normal file
122
crates/nu-command/src/bytes/starts_with.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use super::{operate, BytesArgument};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::ast::CellPath;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::Category;
|
||||
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
|
||||
|
||||
struct Arguments {
|
||||
pattern: Vec<u8>,
|
||||
column_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl BytesArgument for Arguments {
|
||||
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.column_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
pub struct BytesStartsWith;
|
||||
|
||||
impl Command for BytesStartsWith {
|
||||
fn name(&self) -> &str {
|
||||
"bytes starts-with"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("bytes starts-with")
|
||||
.required("pattern", SyntaxShape::Binary, "the pattern to match")
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
"optionally matches prefix of text by column paths",
|
||||
)
|
||||
.category(Category::Bytes)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Check if bytes starts with a pattern"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["pattern", "match", "find", "search"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
|
||||
let column_paths = if column_paths.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(column_paths)
|
||||
};
|
||||
let arg = Arguments {
|
||||
pattern,
|
||||
column_paths,
|
||||
};
|
||||
operate(
|
||||
starts_with,
|
||||
arg,
|
||||
input,
|
||||
call.head,
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F FF AA]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
|
||||
result: Some(Value::Bool {
|
||||
val: true,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Checks if binary starts with `0x[1F]`",
|
||||
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
|
||||
result: Some(Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
|
||||
Value::Bool {
|
||||
val: input.starts_with(pattern),
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(BytesStartsWith {})
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
use crate::{generate_strftime_list, parse_date_from_string};
|
||||
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
@ -7,9 +8,6 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
use crate::generate_strftime_list;
|
||||
use crate::parse_date_from_string;
|
||||
|
||||
struct Arguments {
|
||||
timezone: Option<Spanned<String>>,
|
||||
offset: Option<Spanned<i64>>,
|
||||
@ -148,6 +146,15 @@ impl Command for SubCommand {
|
||||
example: "1614434140 | into datetime -o +9",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description:
|
||||
"Convert timestamps like the sqlite history t",
|
||||
example: "1656165681720 | into datetime",
|
||||
result: Some(Value::Date {
|
||||
val: Utc.timestamp_millis(1656165681720).into(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -251,10 +258,19 @@ fn action(
|
||||
|
||||
return match timezone {
|
||||
// default to UTC
|
||||
None => Value::Date {
|
||||
val: Utc.timestamp(ts, 0).into(),
|
||||
None => {
|
||||
// be able to convert chrono::Utc::now()
|
||||
let dt = match ts.to_string().len() {
|
||||
x if x > 13 => Utc.timestamp_nanos(ts).into(),
|
||||
x if x > 10 => Utc.timestamp_millis(ts).into(),
|
||||
_ => Utc.timestamp(ts, 0).into(),
|
||||
};
|
||||
|
||||
Value::Date {
|
||||
val: dt,
|
||||
span: head,
|
||||
},
|
||||
}
|
||||
}
|
||||
Some(Spanned { item, span }) => match item {
|
||||
Zone::Utc => Value::Date {
|
||||
val: Utc.timestamp(ts, 0).into(),
|
||||
|
@ -28,6 +28,10 @@ impl Command for SubCommand {
|
||||
"Convert value to duration"
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"into duration does not take leap years into account and every month is calculated with 30 days"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["convert", "time", "period"]
|
||||
}
|
||||
@ -136,7 +140,7 @@ fn into_duration(
|
||||
)
|
||||
}
|
||||
|
||||
fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
||||
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
|
||||
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
|
||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||
if let Expr::Int(x) = value.expr {
|
||||
@ -149,16 +153,21 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
||||
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
|
||||
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(ShellError::CantConvert(
|
||||
Err(ShellError::CantConvertWithValue(
|
||||
"duration".to_string(),
|
||||
"string".to_string(),
|
||||
s.to_string(),
|
||||
span,
|
||||
value_span,
|
||||
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
|
||||
))
|
||||
}
|
||||
@ -166,7 +175,10 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
||||
fn action(input: &Value, span: Span) -> Value {
|
||||
match input {
|
||||
Value::Duration { .. } => input.clone(),
|
||||
Value::String { val, .. } => match string_to_duration(val, span) {
|
||||
Value::String {
|
||||
val,
|
||||
span: value_span,
|
||||
} => match string_to_duration(val, span, *value_span) {
|
||||
Ok(val) => Value::Duration { val, span },
|
||||
Err(error) => Value::Error { error },
|
||||
},
|
||||
|
@ -8,6 +8,7 @@ use nu_protocol::{
|
||||
struct Arguments {
|
||||
radix: Option<Value>,
|
||||
column_paths: Vec<CellPath>,
|
||||
little_endian: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -21,6 +22,7 @@ impl Command for SubCommand {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into int")
|
||||
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
|
||||
.switch("little-endian", "use little-endian byte decoding", None)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
@ -100,6 +102,21 @@ impl Command for SubCommand {
|
||||
example: "'FF' | into int -r 16",
|
||||
result: Some(Value::test_int(255)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert octal string to integer",
|
||||
example: "'0o10132' | into int",
|
||||
result: Some(Value::test_int(4186)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert 0 padded string to integer",
|
||||
example: "'0010132' | into int",
|
||||
result: Some(Value::test_int(10132)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert 0 padded string to integer with radix",
|
||||
example: "'0010132' | into int -r 8",
|
||||
result: Some(Value::test_int(4186)),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -114,6 +131,7 @@ fn into_int(
|
||||
|
||||
let options = Arguments {
|
||||
radix: call.get_flag(engine_state, stack, "radix")?,
|
||||
little_endian: call.has_flag("little-endian"),
|
||||
column_paths: call.rest(engine_state, stack, 0)?,
|
||||
};
|
||||
|
||||
@ -135,13 +153,13 @@ fn into_int(
|
||||
input.map(
|
||||
move |v| {
|
||||
if options.column_paths.is_empty() {
|
||||
action(&v, head, radix)
|
||||
action(&v, head, radix, options.little_endian)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &options.column_paths {
|
||||
let r = ret.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| action(old, head, radix)),
|
||||
Box::new(move |old| action(old, head, radix, options.little_endian)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
@ -155,7 +173,7 @@ fn into_int(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
||||
pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Value {
|
||||
match input {
|
||||
Value::Int { val: _, .. } => {
|
||||
if radix == 10 {
|
||||
@ -166,9 +184,34 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
||||
}
|
||||
Value::Filesize { val, .. } => Value::Int { val: *val, span },
|
||||
Value::Float { val, .. } => Value::Int {
|
||||
val: {
|
||||
if radix == 10 {
|
||||
*val as i64
|
||||
} else {
|
||||
match convert_int(
|
||||
&Value::Int {
|
||||
val: *val as i64,
|
||||
span,
|
||||
},
|
||||
span,
|
||||
radix,
|
||||
)
|
||||
.as_i64()
|
||||
{
|
||||
Ok(v) => v,
|
||||
_ => {
|
||||
return Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
"Could not convert float to integer".to_string(),
|
||||
span,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
span,
|
||||
},
|
||||
Value::String { val, .. } => {
|
||||
if radix == 10 {
|
||||
match int_from_string(val, span) {
|
||||
@ -190,6 +233,33 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
|
||||
val: val.timestamp(),
|
||||
span,
|
||||
},
|
||||
Value::Binary { val, span } => {
|
||||
use byteorder::{BigEndian, ByteOrder, LittleEndian};
|
||||
|
||||
let mut val = val.to_vec();
|
||||
|
||||
if little_endian {
|
||||
while val.len() < 8 {
|
||||
val.push(0);
|
||||
}
|
||||
val.resize(8, 0);
|
||||
|
||||
Value::Int {
|
||||
val: LittleEndian::read_i64(&val),
|
||||
span: *span,
|
||||
}
|
||||
} else {
|
||||
while val.len() < 8 {
|
||||
val.insert(0, 0);
|
||||
}
|
||||
val.resize(8, 0);
|
||||
|
||||
Value::Int {
|
||||
val: BigEndian::read_i64(&val),
|
||||
span: *span,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Value::Error {
|
||||
error: ShellError::UnsupportedInput(
|
||||
format!("'into int' for unsupported type '{}'", input.get_type()),
|
||||
@ -203,11 +273,31 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
||||
let i = match input {
|
||||
Value::Int { val, .. } => val.to_string(),
|
||||
Value::String { val, .. } => {
|
||||
if val.starts_with("0x") || val.starts_with("0b") {
|
||||
let val = val.trim();
|
||||
if val.starts_with("0x") // hex
|
||||
|| val.starts_with("0b") // binary
|
||||
|| val.starts_with("0o")
|
||||
// octal
|
||||
{
|
||||
match int_from_string(val, head) {
|
||||
Ok(x) => return Value::Int { val: x, span: head },
|
||||
Err(e) => return Value::Error { error: e },
|
||||
}
|
||||
} else if val.starts_with("00") {
|
||||
// It's a padded string
|
||||
match i64::from_str_radix(val, radix) {
|
||||
Ok(n) => return Value::Int { val: n, span: head },
|
||||
Err(e) => {
|
||||
return Value::Error {
|
||||
error: ShellError::CantConvert(
|
||||
"string".to_string(),
|
||||
"int".to_string(),
|
||||
head,
|
||||
Some(e.to_string()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val.to_string()
|
||||
}
|
||||
@ -220,10 +310,10 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
||||
}
|
||||
}
|
||||
};
|
||||
match i64::from_str_radix(&i, radix) {
|
||||
match i64::from_str_radix(i.trim(), radix) {
|
||||
Ok(n) => Value::Int { val: n, span: head },
|
||||
Err(_reason) => Value::Error {
|
||||
error: ShellError::CantConvert("int".to_string(), "string".to_string(), head, None),
|
||||
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -261,7 +351,21 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||
};
|
||||
Ok(num)
|
||||
}
|
||||
_ => match a_string.parse::<i64>() {
|
||||
o if o.starts_with("0o") => {
|
||||
let num = match i64::from_str_radix(o.trim_start_matches("0o"), 8) {
|
||||
Ok(n) => n,
|
||||
Err(_reason) => {
|
||||
return Err(ShellError::CantConvert(
|
||||
"int".to_string(),
|
||||
"string".to_string(),
|
||||
span,
|
||||
Some(r#"octal digits following "0o" should be in 0-7"#.to_string()),
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(num)
|
||||
}
|
||||
_ => match trimmed.parse::<i64>() {
|
||||
Ok(n) => Ok(n),
|
||||
Err(_) => match a_string.parse::<f64>() {
|
||||
Ok(f) => Ok(f as i64),
|
||||
@ -269,7 +373,10 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
|
||||
"int".to_string(),
|
||||
"string".to_string(),
|
||||
span,
|
||||
None,
|
||||
Some(format!(
|
||||
r#"string "{}" does not represent a valid integer"#,
|
||||
trimmed
|
||||
)),
|
||||
)),
|
||||
},
|
||||
},
|
||||
@ -294,21 +401,21 @@ mod test {
|
||||
let word = Value::test_string("10");
|
||||
let expected = Value::test_int(10);
|
||||
|
||||
let actual = action(&word, Span::test_data(), 10);
|
||||
let actual = action(&word, Span::test_data(), 10, false);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_binary_to_integer() {
|
||||
let s = Value::test_string("0b101");
|
||||
let actual = action(&s, Span::test_data(), 10);
|
||||
let actual = action(&s, Span::test_data(), 10, false);
|
||||
assert_eq!(actual, Value::test_int(5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_hex_to_integer() {
|
||||
let s = Value::test_string("0xFF");
|
||||
let actual = action(&s, Span::test_data(), 16);
|
||||
let actual = action(&s, Span::test_data(), 16, false);
|
||||
assert_eq!(actual, Value::test_int(255));
|
||||
}
|
||||
|
||||
@ -316,7 +423,7 @@ mod test {
|
||||
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
|
||||
let integer_str = Value::test_string("36anra");
|
||||
|
||||
let actual = action(&integer_str, Span::test_data(), 10);
|
||||
let actual = action(&integer_str, Span::test_data(), 10, false);
|
||||
|
||||
assert_eq!(actual.get_type(), Error)
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
|
||||
SyntaxShape, Value,
|
||||
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||
Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
|
||||
@ -53,6 +53,14 @@ impl Command for SubCommand {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "convert integer to string and append three decimal places",
|
||||
example: "5 | into string -d 3",
|
||||
result: Some(Value::String {
|
||||
val: "5.000".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "convert decimal to string and round to nearest integer",
|
||||
example: "1.7 | into string -d 0",
|
||||
@ -210,6 +218,15 @@ pub fn action(
|
||||
Value::Int { val, .. } => {
|
||||
let res = if group_digits {
|
||||
format_int(*val) // int.to_formatted_string(*locale)
|
||||
} else if let Some(dig) = digits {
|
||||
let mut val_with_trailing_zeroes = val.to_string();
|
||||
if dig != 0 {
|
||||
val_with_trailing_zeroes.push('.');
|
||||
}
|
||||
for _ in 0..dig {
|
||||
val_with_trailing_zeroes.push('0');
|
||||
}
|
||||
val_with_trailing_zeroes
|
||||
} else {
|
||||
val.to_string()
|
||||
};
|
||||
@ -247,6 +264,15 @@ pub fn action(
|
||||
val: input.into_string(", ", config),
|
||||
span,
|
||||
},
|
||||
Value::Error { error } => Value::String {
|
||||
val: {
|
||||
match into_code(error) {
|
||||
Some(code) => code,
|
||||
None => "".to_string(),
|
||||
}
|
||||
},
|
||||
span,
|
||||
},
|
||||
Value::Nothing { .. } => Value::String {
|
||||
val: "".to_string(),
|
||||
span,
|
||||
|
@ -28,7 +28,33 @@ impl Command for DefEnv {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html
|
||||
|
||||
=== EXTRA NOTE ===
|
||||
All blocks are scoped, including variable definition and environment variable changes.
|
||||
|
||||
Because of this, the following doesn't work:
|
||||
|
||||
def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
if $arg != "" {
|
||||
cd $arg
|
||||
} else {
|
||||
cd $fall_back_path
|
||||
}
|
||||
}
|
||||
|
||||
Instead, you have to use cd in the top level scope:
|
||||
|
||||
def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
let path = if $arg != "" {
|
||||
$arg
|
||||
} else {
|
||||
$fall_back_path
|
||||
}
|
||||
cd $path
|
||||
}"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -1,7 +1,9 @@
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Do;
|
||||
@ -23,6 +25,11 @@ impl Command for Do {
|
||||
"ignore errors as the block runs",
|
||||
Some('i'),
|
||||
)
|
||||
.switch(
|
||||
"capture-errors",
|
||||
"capture errors as the block runs and return it",
|
||||
Some('c'),
|
||||
)
|
||||
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
|
||||
.category(Category::Core)
|
||||
}
|
||||
@ -37,6 +44,7 @@ impl Command for Do {
|
||||
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
|
||||
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
let ignore_errors = call.has_flag("ignore-errors");
|
||||
let capture_errors = call.has_flag("capture-errors");
|
||||
|
||||
let mut stack = stack.captures_to_stack(&block.captures);
|
||||
let block = engine_state.get_block(block.block_id);
|
||||
@ -85,7 +93,7 @@ impl Command for Do {
|
||||
block,
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
ignore_errors,
|
||||
ignore_errors || capture_errors,
|
||||
);
|
||||
|
||||
if ignore_errors {
|
||||
@ -93,6 +101,11 @@ impl Command for Do {
|
||||
Ok(x) => Ok(x),
|
||||
Err(_) => Ok(PipelineData::new(call.head)),
|
||||
}
|
||||
} else if capture_errors {
|
||||
match result {
|
||||
Ok(x) => Ok(x),
|
||||
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
|
||||
}
|
||||
} else {
|
||||
result
|
||||
}
|
||||
|
@ -23,6 +23,10 @@ impl Command for Echo {
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"Unlike `print`, this command returns an actual value that will be passed to the next command of the pipeline."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -2,8 +2,7 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -16,7 +15,12 @@ impl Command for ErrorMake {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("error make")
|
||||
.optional("error_struct", SyntaxShape::Record, "the error to create")
|
||||
.required("error_struct", SyntaxShape::Record, "the error to create")
|
||||
.switch(
|
||||
"unspanned",
|
||||
"remove the origin label from the error",
|
||||
Some('u'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
@ -33,42 +37,32 @@ impl Command for ErrorMake {
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
let arg: Option<Value> = call.opt(engine_state, stack, 0)?;
|
||||
let arg: Value = call.req(engine_state, stack, 0)?;
|
||||
let unspanned = call.has_flag("unspanned");
|
||||
|
||||
if let Some(arg) = arg {
|
||||
Ok(make_error(&arg, span)
|
||||
.map(|err| Value::Error { error: err })
|
||||
.unwrap_or_else(|| Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
if unspanned {
|
||||
Err(make_error(&arg, None).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
})
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
input.map(
|
||||
move |value| {
|
||||
make_error(&value, span)
|
||||
.map(|err| Value::Error { error: err })
|
||||
.unwrap_or_else(|| Value::Error {
|
||||
error: ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
})
|
||||
},
|
||||
ctrlc,
|
||||
)
|
||||
}))
|
||||
} else {
|
||||
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
|
||||
ShellError::GenericError(
|
||||
"Creating error value not supported.".into(),
|
||||
"unsupported error format".into(),
|
||||
Some(span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,7 +87,7 @@ impl Command for ErrorMake {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
||||
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
|
||||
if let Value::Record { .. } = &value {
|
||||
let msg = value.get_data_by_key("msg");
|
||||
let label = value.get_data_by_key("label");
|
||||
@ -130,7 +124,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
||||
) => Some(ShellError::GenericError(
|
||||
message,
|
||||
label_text,
|
||||
Some(throw_span),
|
||||
throw_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
@ -140,7 +134,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
|
||||
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
|
||||
message,
|
||||
"originates from here".to_string(),
|
||||
Some(throw_span),
|
||||
throw_span,
|
||||
None,
|
||||
Vec::new(),
|
||||
)),
|
||||
|
@ -28,7 +28,33 @@ impl Command for ExportDefEnv {
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html"#
|
||||
https://www.nushell.sh/book/thinking_in_nushell.html
|
||||
|
||||
=== EXTRA NOTE ===
|
||||
All blocks are scoped, including variable definition and environment variable changes.
|
||||
|
||||
Because of this, the following doesn't work:
|
||||
|
||||
export def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
if $arg != "" {
|
||||
cd $arg
|
||||
} else {
|
||||
cd $fall_back_path
|
||||
}
|
||||
}
|
||||
|
||||
Instead, you have to use cd in the top level scope:
|
||||
|
||||
export def-env cd_with_fallback [arg = ""] {
|
||||
let fall_back_path = "/tmp"
|
||||
let path = if $arg != "" {
|
||||
$arg
|
||||
} else {
|
||||
$fall_back_path
|
||||
}
|
||||
cd $path
|
||||
}"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
|
@ -1,12 +1,15 @@
|
||||
use nu_ansi_term::{
|
||||
Color::{Default, Red, White},
|
||||
Style,
|
||||
};
|
||||
use nu_color_config::get_color_config;
|
||||
use nu_engine::{get_full_help, CallExt};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
|
||||
ShellError, Signature, Spanned, SyntaxShape, Value,
|
||||
ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
use nu_engine::{get_full_help, CallExt};
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -81,23 +84,29 @@ fn help(
|
||||
let head = call.head;
|
||||
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
|
||||
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
|
||||
|
||||
let commands = engine_state.get_decl_ids_sorted(false);
|
||||
let config = engine_state.get_config();
|
||||
let color_hm = get_color_config(config);
|
||||
let default_style = Style::new().fg(Default).on(Default);
|
||||
let string_style = match color_hm.get("string") {
|
||||
Some(style) => style,
|
||||
None => &default_style,
|
||||
};
|
||||
|
||||
if let Some(f) = find {
|
||||
let org_search_string = f.item.clone();
|
||||
let search_string = f.item.to_lowercase();
|
||||
let mut found_cmds_vec = Vec::new();
|
||||
|
||||
for decl_id in commands {
|
||||
let mut cols = vec![];
|
||||
let mut vals = vec![];
|
||||
|
||||
let decl = engine_state.get_decl(decl_id);
|
||||
let sig = decl.signature().update_from_command(decl.borrow());
|
||||
|
||||
let key = sig.name;
|
||||
let usage = sig.usage;
|
||||
let search_terms = sig.search_terms;
|
||||
|
||||
let matches_term = if !search_terms.is_empty() {
|
||||
search_terms
|
||||
.iter()
|
||||
@ -106,13 +115,16 @@ fn help(
|
||||
false
|
||||
};
|
||||
|
||||
if key.to_lowercase().contains(&search_string)
|
||||
|| usage.to_lowercase().contains(&search_string)
|
||||
|| matches_term
|
||||
{
|
||||
let key_match = key.to_lowercase().contains(&search_string);
|
||||
let usage_match = usage.to_lowercase().contains(&search_string);
|
||||
if key_match || usage_match || matches_term {
|
||||
cols.push("name".into());
|
||||
vals.push(Value::String {
|
||||
val: key,
|
||||
val: if key_match {
|
||||
highlight_search_string(&key, &org_search_string, string_style)?
|
||||
} else {
|
||||
key
|
||||
},
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -142,7 +154,11 @@ fn help(
|
||||
|
||||
cols.push("usage".into());
|
||||
vals.push(Value::String {
|
||||
val: usage,
|
||||
val: if usage_match {
|
||||
highlight_search_string(&usage, &org_search_string, string_style)?
|
||||
} else {
|
||||
usage
|
||||
},
|
||||
span: head,
|
||||
});
|
||||
|
||||
@ -151,7 +167,30 @@ fn help(
|
||||
Value::nothing(head)
|
||||
} else {
|
||||
Value::String {
|
||||
val: search_terms.join(", "),
|
||||
val: if matches_term {
|
||||
search_terms
|
||||
.iter()
|
||||
.map(|term| {
|
||||
if term.to_lowercase().contains(&search_string) {
|
||||
match highlight_search_string(
|
||||
term,
|
||||
&org_search_string,
|
||||
string_style,
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
string_style.paint(term.to_string()).to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
string_style.paint(term.to_string()).to_string()
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
} else {
|
||||
search_terms.join(", ")
|
||||
},
|
||||
span: head,
|
||||
}
|
||||
});
|
||||
@ -303,3 +342,48 @@ You can also learn more at https://www.nushell.sh/book/"#;
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the search string using ANSI escape sequences and regular expressions.
|
||||
pub fn highlight_search_string(
|
||||
haystack: &str,
|
||||
needle: &str,
|
||||
string_style: &Style,
|
||||
) -> Result<String, ShellError> {
|
||||
let regex_string = format!("(?i){}", needle);
|
||||
let regex = match regex::Regex::new(®ex_string) {
|
||||
Ok(regex) => regex,
|
||||
Err(err) => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Could not compile regex".into(),
|
||||
err.to_string(),
|
||||
Some(Span::test_data()),
|
||||
None,
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let mut last_match_end = 0;
|
||||
let style = Style::new().fg(White).on(Red);
|
||||
let mut highlighted = String::new();
|
||||
|
||||
for cap in regex.captures_iter(haystack) {
|
||||
let start = match cap.get(0) {
|
||||
Some(cap) => cap.start(),
|
||||
None => 0,
|
||||
};
|
||||
let end = match cap.get(0) {
|
||||
Some(cap) => cap.end(),
|
||||
None => 0,
|
||||
};
|
||||
highlighted.push_str(
|
||||
&string_style
|
||||
.paint(&haystack[last_match_end..start])
|
||||
.to_string(),
|
||||
);
|
||||
highlighted.push_str(&style.paint(&haystack[start..end]).to_string());
|
||||
last_match_end = end;
|
||||
}
|
||||
|
||||
highlighted.push_str(&string_style.paint(&haystack[last_match_end..]).to_string());
|
||||
Ok(highlighted)
|
||||
}
|
||||
|
@ -1,131 +0,0 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, HistoryFileFormat, IntoInterruptiblePipelineData, PipelineData, ShellError,
|
||||
Signature, Value,
|
||||
};
|
||||
use reedline::{
|
||||
FileBackedHistory, History as ReedlineHistory, SearchDirection, SearchQuery,
|
||||
SqliteBackedHistory,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct History;
|
||||
|
||||
impl Command for History {
|
||||
fn name(&self) -> &str {
|
||||
"history"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Get the command history"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("history")
|
||||
.switch("clear", "Clears out the history entries", Some('c'))
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let head = call.head;
|
||||
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history`
|
||||
if let Some(config_path) = nu_path::config_dir() {
|
||||
let clear = call.has_flag("clear");
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut history_path = config_path;
|
||||
history_path.push("nushell");
|
||||
match engine_state.config.history_file_format {
|
||||
HistoryFileFormat::Sqlite => {
|
||||
history_path.push("history.sqlite3");
|
||||
}
|
||||
HistoryFileFormat::PlainText => {
|
||||
history_path.push("history.txt");
|
||||
}
|
||||
}
|
||||
|
||||
if clear {
|
||||
let _ = std::fs::remove_file(history_path);
|
||||
// TODO: FIXME also clear the auxiliary files when using sqlite
|
||||
Ok(PipelineData::new(head))
|
||||
} else {
|
||||
let history_reader: Option<Box<dyn ReedlineHistory>> =
|
||||
match engine_state.config.history_file_format {
|
||||
HistoryFileFormat::Sqlite => SqliteBackedHistory::with_file(history_path)
|
||||
.map(|inner| {
|
||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||
boxed
|
||||
})
|
||||
.ok(),
|
||||
|
||||
HistoryFileFormat::PlainText => FileBackedHistory::with_file(
|
||||
engine_state.config.max_history_size as usize,
|
||||
history_path,
|
||||
)
|
||||
.map(|inner| {
|
||||
let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
|
||||
boxed
|
||||
})
|
||||
.ok(),
|
||||
};
|
||||
|
||||
let data = history_reader
|
||||
.and_then(|h| {
|
||||
h.search(SearchQuery::everything(SearchDirection::Forward))
|
||||
.ok()
|
||||
})
|
||||
.map(move |entries| {
|
||||
entries
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(move |(idx, entry)| Value::Record {
|
||||
cols: vec!["command".to_string(), "index".to_string()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: entry.command_line,
|
||||
span: head,
|
||||
},
|
||||
Value::Int {
|
||||
val: idx as i64,
|
||||
span: head,
|
||||
},
|
||||
],
|
||||
span: head,
|
||||
})
|
||||
})
|
||||
.ok_or(ShellError::FileNotFound(head))?
|
||||
.into_pipeline_data(ctrlc);
|
||||
Ok(data)
|
||||
}
|
||||
} else {
|
||||
Err(ShellError::FileNotFound(head))
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
example: "history | length",
|
||||
description: "Get current history length",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "history | last 5",
|
||||
description: "Show last 5 commands you have ran",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
example: "history | wrap cmd | where cmd =~ cargo",
|
||||
description: "Search all the commands from history that contains 'cargo'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
@ -92,6 +92,7 @@ impl Command for If {
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|res| res.0)
|
||||
}
|
||||
} else {
|
||||
eval_expression_with_input(
|
||||
@ -102,6 +103,7 @@ impl Command for If {
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)
|
||||
.map(|res| res.0)
|
||||
}
|
||||
} else {
|
||||
Ok(PipelineData::new(call.head))
|
||||
|
@ -35,6 +35,10 @@ impl Command for Let {
|
||||
true
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["set", "const"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
@ -61,7 +65,8 @@ impl Command for Let {
|
||||
input,
|
||||
call.redirect_stdout,
|
||||
call.redirect_stderr,
|
||||
)?;
|
||||
)?
|
||||
.0;
|
||||
|
||||
//println!("Adding: {:?} to {}", rhs, var_id);
|
||||
|
||||
|
@ -14,9 +14,8 @@ mod export_env;
|
||||
mod export_extern;
|
||||
mod extern_;
|
||||
mod for_;
|
||||
mod help;
|
||||
pub mod help;
|
||||
mod hide;
|
||||
mod history;
|
||||
mod if_;
|
||||
mod ignore;
|
||||
mod let_;
|
||||
@ -24,7 +23,6 @@ mod metadata;
|
||||
mod module;
|
||||
pub(crate) mod overlay;
|
||||
mod source;
|
||||
mod tutor;
|
||||
mod use_;
|
||||
mod version;
|
||||
|
||||
@ -46,7 +44,6 @@ pub use extern_::Extern;
|
||||
pub use for_::For;
|
||||
pub use help::Help;
|
||||
pub use hide::Hide;
|
||||
pub use history::History;
|
||||
pub use if_::If;
|
||||
pub use ignore::Ignore;
|
||||
pub use let_::Let;
|
||||
@ -54,7 +51,6 @@ pub use metadata::Metadata;
|
||||
pub use module::Module;
|
||||
pub use overlay::*;
|
||||
pub use source::Source;
|
||||
pub use tutor::Tutor;
|
||||
pub use use_::Use;
|
||||
pub use version::Version;
|
||||
#[cfg(feature = "plugin")]
|
||||
|
@ -1,9 +1,7 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
|
||||
};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverlayRemove;
|
||||
@ -22,9 +20,15 @@ impl Command for OverlayRemove {
|
||||
.optional("name", SyntaxShape::String, "Overlay to remove")
|
||||
.switch(
|
||||
"keep-custom",
|
||||
"Keep newly added symbols within the next activated overlay",
|
||||
"Keep all newly added symbols within the next activated overlay",
|
||||
Some('k'),
|
||||
)
|
||||
.named(
|
||||
"keep-env",
|
||||
SyntaxShape::List(Box::new(SyntaxShape::String)),
|
||||
"List of environment variables to keep from the removed overlay",
|
||||
Some('e'),
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
@ -60,30 +64,44 @@ impl Command for OverlayRemove {
|
||||
));
|
||||
}
|
||||
|
||||
if call.has_flag("keep-custom") {
|
||||
let keep_env: Option<Vec<Spanned<String>>> =
|
||||
call.get_flag(engine_state, stack, "keep-env")?;
|
||||
|
||||
let env_vars_to_keep = if call.has_flag("keep-custom") {
|
||||
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.item.as_bytes()) {
|
||||
let overlay_frame = engine_state.get_overlay(overlay_id);
|
||||
let origin_module = engine_state.get_module(overlay_frame.origin);
|
||||
|
||||
let env_vars_to_keep: Vec<(String, Value)> = stack
|
||||
stack
|
||||
.get_overlay_env_vars(engine_state, &overlay_name.item)
|
||||
.into_iter()
|
||||
.filter(|(name, _)| !origin_module.has_env_var(name.as_bytes()))
|
||||
.collect();
|
||||
|
||||
stack.remove_overlay(&overlay_name.item);
|
||||
|
||||
for (name, val) in env_vars_to_keep {
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
.collect()
|
||||
} else {
|
||||
return Err(ShellError::OverlayNotFoundAtRuntime(
|
||||
overlay_name.item,
|
||||
overlay_name.span,
|
||||
));
|
||||
}
|
||||
} else if let Some(env_var_names_to_keep) = keep_env {
|
||||
let mut env_vars_to_keep = vec![];
|
||||
|
||||
for name in env_var_names_to_keep.into_iter() {
|
||||
match stack.get_env_var(engine_state, &name.item) {
|
||||
Some(val) => env_vars_to_keep.push((name.item, val.clone())),
|
||||
None => return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span)),
|
||||
}
|
||||
}
|
||||
|
||||
env_vars_to_keep
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
stack.remove_overlay(&overlay_name.item);
|
||||
|
||||
for (name, val) in env_vars_to_keep {
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
|
||||
Ok(PipelineData::new(call.head))
|
||||
@ -112,6 +130,13 @@ impl Command for OverlayRemove {
|
||||
overlay remove"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Keep the current working directory when removing an overlay",
|
||||
example: r#"overlay new spam
|
||||
cd some-dir
|
||||
overlay remove --keep-env [ PWD ] spam"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -69,11 +69,6 @@ impl Command for Source {
|
||||
example: r#"source ./foo.nu; say-hi"#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Runs foo.nu in current context and call the `main` command automatically, suppose foo.nu has content: `def main [] { echo 'Hi!' }`",
|
||||
example: r#"source ./foo.nu"#,
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,26 @@
|
||||
use crate::{
|
||||
database::values::dsl::{ExprDb, SelectDb},
|
||||
SQLiteDatabase,
|
||||
};
|
||||
use crate::SQLiteDatabase;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Ident, SelectItem, SetExpr, Statement, TableAlias, TableFactor};
|
||||
use sqlparser::ast::{Ident, SetExpr, Statement, TableAlias, TableFactor};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AliasExpr;
|
||||
pub struct AliasDb;
|
||||
|
||||
impl Command for AliasExpr {
|
||||
impl Command for AliasDb {
|
||||
fn name(&self) -> &str {
|
||||
"db as"
|
||||
"as"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("alias", SyntaxShape::String, "alias name")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -29,11 +29,59 @@ impl Command for AliasExpr {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an alias for a column selection",
|
||||
example: "db col name_a | db as new_a",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Creates an alias for a selected table",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| as t1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 AS t1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Creates an alias for a derived table",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from (
|
||||
open db.mysql
|
||||
| into db
|
||||
| select a b
|
||||
| from table_a
|
||||
)
|
||||
| as t1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM (SELECT a, b FROM table_a) AS t1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -48,51 +96,11 @@ impl Command for AliasExpr {
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let alias: String = call.req(engine_state, stack, 0)?;
|
||||
let value = input.into_value(call.head);
|
||||
|
||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
||||
alias_selection(expr.into_native().into(), alias, call)
|
||||
} else if let Ok(select) = SelectDb::try_from_value(&value) {
|
||||
alias_selection(select, alias, call)
|
||||
} else if let Ok(db) = SQLiteDatabase::try_from_value(value.clone()) {
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
alias_db(db, alias, call)
|
||||
} else {
|
||||
Err(ShellError::CantConvert(
|
||||
"expression or query".into(),
|
||||
value.get_type().to_string(),
|
||||
value.span()?,
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn alias_selection(
|
||||
select: SelectDb,
|
||||
alias: String,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let select = match select.into_native() {
|
||||
SelectItem::UnnamedExpr(expr) => SelectItem::ExprWithAlias {
|
||||
expr,
|
||||
alias: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
},
|
||||
SelectItem::ExprWithAlias { expr, .. } => SelectItem::ExprWithAlias {
|
||||
expr,
|
||||
alias: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
},
|
||||
select => select,
|
||||
};
|
||||
|
||||
let select: SelectDb = select.into();
|
||||
Ok(select.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
fn alias_db(
|
||||
mut db: SQLiteDatabase,
|
||||
@ -142,7 +150,7 @@ fn alias_db(
|
||||
},
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
"Connection doesn't define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
@ -152,3 +160,19 @@ fn alias_db(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(AliasDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
@ -15,16 +15,18 @@ pub struct AndDb;
|
||||
|
||||
impl Command for AndDb {
|
||||
fn name(&self) -> &str {
|
||||
"db and"
|
||||
"and"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an AND clause for a query or expression"
|
||||
"Includes an AND clause for a query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("where", SyntaxShape::Any, "Where expression on the table")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -35,23 +37,52 @@ impl Command for AndDb {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database with a where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1)
|
||||
| db and ((db col b) == 1)
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "Selects a column from a database with an AND clause",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1)
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE a > 1 AND b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a nested where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1 | db and ((db col a) < 10))
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "Creates a AND clause combined with an expression AND",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1 | and ((field a) < 10))
|
||||
| and ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE (a > 1 AND a < 10) AND b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -66,24 +97,13 @@ impl Command for AndDb {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expression) = ExprDb::try_from_value(&value) {
|
||||
let expression = Expr::BinaryOp {
|
||||
left: Box::new(expression.into_native()),
|
||||
op: BinaryOperator::And,
|
||||
right: Box::new(expr),
|
||||
};
|
||||
|
||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
} else if let Ok(mut db) = SQLiteDatabase::try_from_value(value.clone()) {
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement.as_mut() {
|
||||
Some(statement) => match statement {
|
||||
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Connection doesnt define a query".into(),
|
||||
"Connection doesn't define a query".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
@ -103,14 +123,6 @@ impl Command for AndDb {
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::CantConvert(
|
||||
"expression or query".into(),
|
||||
value.get_type().to_string(),
|
||||
value.span()?,
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,3 +162,23 @@ fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Resu
|
||||
select.as_mut().selection = Some(new_expression);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{AndExpr, FieldExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(AndDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(AndExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -1,51 +0,0 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ColExpr;
|
||||
|
||||
impl Command for ColExpr {
|
||||
fn name(&self) -> &str {
|
||||
"db col"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("name", SyntaxShape::String, "column name")
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates column expression for database"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates a named column expression",
|
||||
example: "db col name_1",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "column", "expression"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expression = ExprDb::try_from_value(&value)?;
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
@ -11,21 +11,24 @@ pub struct CollectDb;
|
||||
|
||||
impl Command for CollectDb {
|
||||
fn name(&self) -> &str {
|
||||
"db collect"
|
||||
"collect"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Custom("database".into()))
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Query a database using SQL."
|
||||
"Collects a query from a database database connection"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Collect from a select query",
|
||||
example: "open foo.db | db select a | db from table_1 | db collect",
|
||||
example: "open foo.db | into db | select a | from table_1 | collect",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoPipelineData, PipelineData, ShellError, Signature, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Database;
|
||||
|
||||
impl Command for Database {
|
||||
fn name(&self) -> &str {
|
||||
"db"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Database commands"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(
|
||||
&Database.signature(),
|
||||
&Database.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
@ -11,11 +11,14 @@ pub struct DescribeDb;
|
||||
|
||||
impl Command for DescribeDb {
|
||||
fn name(&self) -> &str {
|
||||
"db describe"
|
||||
"describe"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Custom("database".into()))
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -25,13 +28,26 @@ impl Command for DescribeDb {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Describe SQLite database constructed query",
|
||||
example: "db open foo.db | db select table_1 | db describe",
|
||||
result: None,
|
||||
example: "open foo.db | into db | select col_1 | from table_1 | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "foo.db".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT col_1 FROM table_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "SQLite"]
|
||||
vec!["database", "SQLite", "describe"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -45,3 +61,19 @@ impl Command for DescribeDb {
|
||||
Ok(db.describe(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(DescribeDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Ident, Query, Select, SetExpr, Statement, TableAlias, TableWithJoins};
|
||||
|
||||
@ -14,7 +15,7 @@ pub struct FromDb;
|
||||
|
||||
impl Command for FromDb {
|
||||
fn name(&self) -> &str {
|
||||
"db from"
|
||||
"from"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -34,6 +35,8 @@ impl Command for FromDb {
|
||||
"Alias for the selected table",
|
||||
Some('a'),
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -43,9 +46,22 @@ impl Command for FromDb {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Selects table from database",
|
||||
example: "db open db.mysql | db from table_a",
|
||||
result: None,
|
||||
description: "Selects a table from database",
|
||||
example: "open db.mysql | into db | from table_a | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT FROM table_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
@ -180,3 +196,14 @@ fn create_table(
|
||||
|
||||
Ok(table)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(FromDb {})])
|
||||
}
|
||||
}
|
||||
|
@ -1,85 +0,0 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, Ident, ObjectName};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FunctionExpr;
|
||||
|
||||
impl Command for FunctionExpr {
|
||||
fn name(&self) -> &str {
|
||||
"db fn"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("name", SyntaxShape::String, "function name")
|
||||
.switch("distinct", "distict values", Some('d'))
|
||||
.rest("arguments", SyntaxShape::Any, "function arguments")
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates function expression for a select operation"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates a function expression",
|
||||
example: "db fn count name_1",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "function", "expression"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let name: String = call.req(engine_state, stack, 0)?;
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let expressions = ExprDb::extract_exprs(value)?;
|
||||
|
||||
let name: Vec<Ident> = name
|
||||
.split('.')
|
||||
.map(|part| Ident {
|
||||
value: part.to_string(),
|
||||
quote_style: None,
|
||||
})
|
||||
.collect();
|
||||
let name = ObjectName(name);
|
||||
|
||||
let args: Vec<FunctionArg> = expressions
|
||||
.into_iter()
|
||||
.map(|expr| {
|
||||
let arg = FunctionArgExpr::Expr(expr);
|
||||
|
||||
FunctionArg::Unnamed(arg)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expression: ExprDb = Expr::Function(Function {
|
||||
name,
|
||||
args,
|
||||
over: None,
|
||||
distinct: call.has_flag("distinct"),
|
||||
})
|
||||
.into();
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{SetExpr, Statement};
|
||||
|
||||
@ -14,7 +15,7 @@ pub struct GroupByDb;
|
||||
|
||||
impl Command for GroupByDb {
|
||||
fn name(&self) -> &str {
|
||||
"db group-by"
|
||||
"group-by"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -28,6 +29,8 @@ impl Command for GroupByDb {
|
||||
SyntaxShape::Any,
|
||||
"Select expression(s) on the table",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -36,15 +39,54 @@ impl Command for GroupByDb {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"db open db.mysql
|
||||
| db from table_a
|
||||
| db select a
|
||||
| db group-by a
|
||||
| db describe"#,
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "groups by column a and calculates the max",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select (fn max a)
|
||||
| group-by a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT max(a) FROM table_a GROUP BY a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "groups by column column a and counts records",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select (fn count *)
|
||||
| group-by a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT count(*) FROM table_a GROUP BY a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -100,3 +142,24 @@ impl Command for GroupByDb {
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, FunctionExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(GroupByDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FunctionExpr {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{
|
||||
Ident, Join, JoinConstraint, JoinOperator, Select, SetExpr, Statement, TableAlias,
|
||||
@ -15,7 +16,7 @@ pub struct JoinDb;
|
||||
|
||||
impl Command for JoinDb {
|
||||
fn name(&self) -> &str {
|
||||
"db join"
|
||||
"join"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -40,6 +41,8 @@ impl Command for JoinDb {
|
||||
.switch("right", "right outer join", Some('r'))
|
||||
.switch("outer", "full outer join", Some('o'))
|
||||
.switch("cross", "cross join", Some('c'))
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -48,11 +51,61 @@ impl Command for JoinDb {
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "",
|
||||
example: "",
|
||||
result: None,
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "joins two tables on col_b",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select col_a
|
||||
| from table_1 --as t1
|
||||
| join table_2 col_b --as t2
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT col_a FROM table_1 AS t1 JOIN table_2 AS t2 ON col_b"
|
||||
.into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "joins a table with a derived table using aliases",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select col_a
|
||||
| from table_1 --as t1
|
||||
| join (
|
||||
open db.mysql
|
||||
| into db
|
||||
| select col_c
|
||||
| from table_2
|
||||
) ((field t1.col_a) == (field t2.col_c)) --as t2 --right
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT col_a FROM table_1 AS t1 RIGHT JOIN (SELECT col_c FROM table_2) AS t2 ON t1.col_a = t2.col_c"
|
||||
.into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -176,3 +229,23 @@ fn modify_from(
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(JoinDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::Statement;
|
||||
|
||||
@ -13,7 +14,7 @@ pub struct LimitDb;
|
||||
|
||||
impl Command for LimitDb {
|
||||
fn name(&self) -> &str {
|
||||
"db limit"
|
||||
"limit"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -27,6 +28,8 @@ impl Command for LimitDb {
|
||||
SyntaxShape::Int,
|
||||
"Number of rows to extract for query",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -37,12 +40,26 @@ impl Command for LimitDb {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Limits selection from table",
|
||||
example: r#"db open db.mysql
|
||||
| db from table_a
|
||||
| db select a
|
||||
| db limit 10
|
||||
| db describe"#,
|
||||
result: None,
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select a
|
||||
| limit 10
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_a LIMIT 10".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
@ -84,3 +101,23 @@ impl Command for LimitDb {
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(LimitDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -3,22 +3,19 @@ pub mod conversions;
|
||||
|
||||
mod alias;
|
||||
mod and;
|
||||
mod col;
|
||||
mod collect;
|
||||
mod command;
|
||||
mod describe;
|
||||
mod from;
|
||||
mod function;
|
||||
mod group_by;
|
||||
mod join;
|
||||
mod limit;
|
||||
mod open;
|
||||
mod or;
|
||||
mod order_by;
|
||||
mod over;
|
||||
mod query;
|
||||
mod schema;
|
||||
mod select;
|
||||
mod to_db;
|
||||
mod where_;
|
||||
|
||||
// Temporal module to create Query objects
|
||||
@ -27,27 +24,24 @@ use testing::TestingDb;
|
||||
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
|
||||
use alias::AliasExpr;
|
||||
use alias::AliasDb;
|
||||
use and::AndDb;
|
||||
use col::ColExpr;
|
||||
use collect::CollectDb;
|
||||
use command::Database;
|
||||
use describe::DescribeDb;
|
||||
use from::FromDb;
|
||||
use function::FunctionExpr;
|
||||
pub(crate) use describe::DescribeDb;
|
||||
pub(crate) use from::FromDb;
|
||||
use group_by::GroupByDb;
|
||||
use join::JoinDb;
|
||||
use limit::LimitDb;
|
||||
use open::OpenDb;
|
||||
use or::OrDb;
|
||||
use order_by::OrderByDb;
|
||||
use over::OverExpr;
|
||||
use query::QueryDb;
|
||||
use schema::SchemaDb;
|
||||
use select::ProjectionDb;
|
||||
pub(crate) use select::ProjectionDb;
|
||||
pub(crate) use to_db::ToDataBase;
|
||||
use where_::WhereDb;
|
||||
|
||||
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
||||
pub fn add_commands_decls(working_set: &mut StateWorkingSet) {
|
||||
macro_rules! bind_command {
|
||||
( $command:expr ) => {
|
||||
working_set.add_decl(Box::new($command));
|
||||
@ -59,21 +53,18 @@ pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
||||
|
||||
// Series commands
|
||||
bind_command!(
|
||||
AliasExpr,
|
||||
ToDataBase,
|
||||
AliasDb,
|
||||
AndDb,
|
||||
ColExpr,
|
||||
CollectDb,
|
||||
Database,
|
||||
DescribeDb,
|
||||
FromDb,
|
||||
FunctionExpr,
|
||||
GroupByDb,
|
||||
JoinDb,
|
||||
LimitDb,
|
||||
OpenDb,
|
||||
OrderByDb,
|
||||
OrDb,
|
||||
OverExpr,
|
||||
QueryDb,
|
||||
ProjectionDb,
|
||||
SchemaDb,
|
||||
|
@ -4,6 +4,7 @@ use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@ -12,12 +13,14 @@ pub struct OpenDb;
|
||||
|
||||
impl Command for OpenDb {
|
||||
fn name(&self) -> &str {
|
||||
"db open"
|
||||
"open-db"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("query", SyntaxShape::Filepath, "SQLite file to be opened")
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -31,8 +34,8 @@ impl Command for OpenDb {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Open a sqlite file",
|
||||
example: r#"db open file.sqlite"#,
|
||||
description: "Creates a connection to a sqlite database based on the file name",
|
||||
example: r#"open-db file.sqlite"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
@ -15,16 +15,18 @@ pub struct OrDb;
|
||||
|
||||
impl Command for OrDb {
|
||||
fn name(&self) -> &str {
|
||||
"db or"
|
||||
"or"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an OR clause for a query or expression"
|
||||
"Includes an OR clause for a query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("where", SyntaxShape::Any, "Where expression on the table")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -35,23 +37,52 @@ impl Command for OrDb {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database with a where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1)
|
||||
| db or ((db col b) == 1)
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "selects a column from a database with an OR clause",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1)
|
||||
| or ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE a > 1 OR b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a nested where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1 | db or ((db col a) < 10))
|
||||
| db describe"#,
|
||||
result: None,
|
||||
description: "Creates an OR clause in the column names and a column",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1 | or ((field a) < 10))
|
||||
| or ((field b) == 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE (a > 1 OR a < 10) OR b = 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -66,18 +97,7 @@ impl Command for OrDb {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expression) = ExprDb::try_from_value(&value) {
|
||||
let expression = Expr::BinaryOp {
|
||||
left: Box::new(expression.into_native()),
|
||||
op: BinaryOperator::Or,
|
||||
right: Box::new(expr),
|
||||
};
|
||||
|
||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
} else if let Ok(mut db) = SQLiteDatabase::try_from_value(value.clone()) {
|
||||
let mut db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
match db.statement {
|
||||
Some(ref mut statement) => match statement {
|
||||
Statement::Query(query) => modify_query(query, expr, call.head)?,
|
||||
@ -103,14 +123,6 @@ impl Command for OrDb {
|
||||
};
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
} else {
|
||||
Err(ShellError::CantConvert(
|
||||
"expression or query".into(),
|
||||
value.get_type().to_string(),
|
||||
value.span()?,
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,3 +162,23 @@ fn modify_select(select: &mut Box<Select>, expression: Expr, span: Span) -> Resu
|
||||
select.as_mut().selection = Some(new_expression);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(OrDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, OrderByExpr, Statement};
|
||||
|
||||
@ -14,7 +15,7 @@ pub struct OrderByDb;
|
||||
|
||||
impl Command for OrderByDb {
|
||||
fn name(&self) -> &str {
|
||||
"db order-by"
|
||||
"order-by"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -30,23 +31,65 @@ impl Command for OrderByDb {
|
||||
SyntaxShape::Any,
|
||||
"Select expression(s) on the table",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "select"]
|
||||
vec!["database", "order-by"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
vec![
|
||||
Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"db open db.mysql
|
||||
| db from table_a
|
||||
| db select a
|
||||
| db order-by a
|
||||
| db describe"#,
|
||||
result: None,
|
||||
}]
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select a
|
||||
| order-by a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_a ORDER BY a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "orders query by column a ascending and by column b",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| from table_a
|
||||
| select a
|
||||
| order-by a --ascending
|
||||
| order-by b
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_a ORDER BY a ASC, b".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -155,3 +198,23 @@ fn update_connection(
|
||||
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb, WhereDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(OrderByDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, WindowSpec};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverExpr;
|
||||
|
||||
impl Command for OverExpr {
|
||||
fn name(&self) -> &str {
|
||||
"db over"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest(
|
||||
"partition-by",
|
||||
SyntaxShape::Any,
|
||||
"columns to partition the window function",
|
||||
)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Adds a partition to an expression function"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Adds a partition to a function expresssion",
|
||||
example: "db function avg col_a | db over col_b",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "column", "expression"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let partitions = ExprDb::extract_exprs(value)?;
|
||||
|
||||
let mut expression = ExprDb::try_from_pipeline(input, call.head)?;
|
||||
match expression.as_mut() {
|
||||
Expr::Function(function) => {
|
||||
function.over = Some(WindowSpec {
|
||||
partition_by: partitions,
|
||||
order_by: Vec::new(),
|
||||
window_frame: None,
|
||||
});
|
||||
}
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Expression doesnt define a function".into(),
|
||||
format!("Expected an expression with a function. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape,
|
||||
Type,
|
||||
};
|
||||
|
||||
use super::super::SQLiteDatabase;
|
||||
@ -12,7 +13,7 @@ pub struct QueryDb;
|
||||
|
||||
impl Command for QueryDb {
|
||||
fn name(&self) -> &str {
|
||||
"db query"
|
||||
"query"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
@ -22,6 +23,8 @@ impl Command for QueryDb {
|
||||
SyntaxShape::String,
|
||||
"SQL to execute against the database",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -31,8 +34,8 @@ impl Command for QueryDb {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Get 1 table out of a SQLite database",
|
||||
example: r#"db open foo.db | db query "SELECT * FROM Bar""#,
|
||||
description: "Execute a query statement using the database connection",
|
||||
example: r#"open foo.db | into db | query "SELECT * FROM Bar""#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use crate::database::values::definitions::{db::Db, db_row::DbRow, db_table::DbTa
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
use rusqlite::Connection;
|
||||
#[derive(Clone)]
|
||||
@ -11,21 +11,24 @@ pub struct SchemaDb;
|
||||
|
||||
impl Command for SchemaDb {
|
||||
fn name(&self) -> &str {
|
||||
"db schema"
|
||||
"schema"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Custom("database".into()))
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show database information, including its schema."
|
||||
"Show sqlite database information, including its schema."
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Show the schema of a SQLite database",
|
||||
example: r#"open foo.db | db schema"#,
|
||||
example: r#"open foo.db | into db | schema"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Value,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Query, Select, SelectItem, SetExpr, Statement};
|
||||
|
||||
@ -13,7 +13,7 @@ pub struct ProjectionDb;
|
||||
|
||||
impl Command for ProjectionDb {
|
||||
fn name(&self) -> &str {
|
||||
"db select"
|
||||
"select"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -27,6 +27,8 @@ impl Command for ProjectionDb {
|
||||
SyntaxShape::Any,
|
||||
"Select expression(s) on the table",
|
||||
)
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -38,13 +40,43 @@ impl Command for ProjectionDb {
|
||||
vec![
|
||||
Example {
|
||||
description: "selects a column from a database",
|
||||
example: "db open db.mysql | db select a | db describe",
|
||||
result: None,
|
||||
example: "open db.mysql | into db | select a | describe",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "selects columns from a database",
|
||||
example: "db open db.mysql | db select a b c | db describe",
|
||||
result: None,
|
||||
description: "selects columns from a database using alias",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select (field a | as new_a) b c
|
||||
| from table_1
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a AS new_a, b, c FROM table_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
@ -104,7 +136,7 @@ fn modify_statement(
|
||||
Ok(statement)
|
||||
}
|
||||
s => Err(ShellError::GenericError(
|
||||
"Connection doesnt define a statement".into(),
|
||||
"Connection doesn't define a statement".into(),
|
||||
format!("Expected a connection with query. Got {}", s),
|
||||
Some(span),
|
||||
None,
|
||||
@ -129,3 +161,21 @@ fn create_select(projection: Vec<SelectItem>) -> Select {
|
||||
having: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{AliasExpr, FieldExpr};
|
||||
use super::super::FromDb;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(AliasExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ pub struct TestingDb;
|
||||
|
||||
impl Command for TestingDb {
|
||||
fn name(&self) -> &str {
|
||||
"db testing"
|
||||
"testing-db"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
@ -27,7 +27,7 @@ impl Command for TestingDb {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Create query object"
|
||||
"Temporal Command: Create query object"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
50
crates/nu-command/src/database/commands/to_db.rs
Normal file
50
crates/nu-command/src/database/commands/to_db.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use super::super::SQLiteDatabase;
|
||||
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToDataBase;
|
||||
|
||||
impl Command for ToDataBase {
|
||||
fn name(&self) -> &str {
|
||||
"into db"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Converts into an open db connection"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "into", "db"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Converts an open file into a db object",
|
||||
example: "open db.mysql | into db",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let db = SQLiteDatabase::try_from_pipeline(input, call.head)?;
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, Query, Select, SetExpr, Statement};
|
||||
|
||||
@ -14,7 +15,7 @@ pub struct WhereDb;
|
||||
|
||||
impl Command for WhereDb {
|
||||
fn name(&self) -> &str {
|
||||
"db where"
|
||||
"where"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -24,6 +25,8 @@ impl Command for WhereDb {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("where", SyntaxShape::Any, "Where expression on the table")
|
||||
.input_type(Type::Custom("database".into()))
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
@ -34,12 +37,26 @@ impl Command for WhereDb {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "selects a column from a database with a where clause",
|
||||
example: r#"db open db.mysql
|
||||
| db select a
|
||||
| db from table_1
|
||||
| db where ((db col a) > 1)
|
||||
| db describe"#,
|
||||
result: None,
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select a
|
||||
| from table_1
|
||||
| where ((field a) > 1)
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT a FROM table_1 WHERE a > 1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
@ -111,3 +128,23 @@ fn create_select(expression: Expr) -> Select {
|
||||
having: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::expressions::{FieldExpr, OrExpr};
|
||||
use super::super::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
Box::new(WhereDb {}),
|
||||
Box::new(FieldExpr {}),
|
||||
Box::new(OrExpr {}),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
132
crates/nu-command/src/database/expressions/alias.rs
Normal file
132
crates/nu-command/src/database/expressions/alias.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use crate::database::values::dsl::{ExprDb, SelectDb};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Ident, SelectItem};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AliasExpr;
|
||||
|
||||
impl Command for AliasExpr {
|
||||
fn name(&self) -> &str {
|
||||
"as"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("alias", SyntaxShape::String, "alias name")
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates an alias for a column selection"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an alias for a column selection",
|
||||
example: "field name_a | as new_a | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["expression".into(), "alias".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "name_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "new_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "alias", "column"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let alias: String = call.req(engine_state, stack, 0)?;
|
||||
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
||||
alias_selection(expr.into_native().into(), alias, call)
|
||||
} else {
|
||||
let select = SelectDb::try_from_value(&value)?;
|
||||
alias_selection(select, alias, call)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn alias_selection(
|
||||
select: SelectDb,
|
||||
alias: String,
|
||||
call: &Call,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let select = match select.into_native() {
|
||||
SelectItem::UnnamedExpr(expr) => SelectItem::ExprWithAlias {
|
||||
expr,
|
||||
alias: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
},
|
||||
SelectItem::ExprWithAlias { expr, .. } => SelectItem::ExprWithAlias {
|
||||
expr,
|
||||
alias: Ident {
|
||||
value: alias,
|
||||
quote_style: None,
|
||||
},
|
||||
},
|
||||
select => select,
|
||||
};
|
||||
|
||||
let select: SelectDb = select.into();
|
||||
Ok(select.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(AliasExpr {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
141
crates/nu-command/src/database/expressions/and.rs
Normal file
141
crates/nu-command/src/database/expressions/and.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AndExpr;
|
||||
|
||||
impl Command for AndExpr {
|
||||
fn name(&self) -> &str {
|
||||
"and"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an AND clause for an expression"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("and", SyntaxShape::Any, "AND expression")
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "and", "expression"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an AND expression",
|
||||
example: r#"(field a) > 1 | and ((field a) < 10) | into nu"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: ">".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "AND".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "<".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "10".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let expression = ExprDb::try_from_pipeline(input, call.head)?;
|
||||
let expression = Expr::BinaryOp {
|
||||
left: Box::new(expression.into_native()),
|
||||
op: BinaryOperator::And,
|
||||
right: Box::new(expr),
|
||||
};
|
||||
|
||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(AndExpr {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
76
crates/nu-command/src/database/expressions/as_nu.rs
Normal file
76
crates/nu-command/src/database/expressions/as_nu.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use crate::database::values::dsl::{ExprDb, SelectDb};
|
||||
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExprAsNu;
|
||||
|
||||
impl Command for ExprAsNu {
|
||||
fn name(&self) -> &str {
|
||||
"into nu"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Convert a db expression into a nu value for access and exploration"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Any)
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Convert a col expression into a nushell value",
|
||||
example: "field name_1 | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "name_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value = input.into_value(call.head);
|
||||
if let Ok(expr) = ExprDb::try_from_value(&value) {
|
||||
Ok(expr.to_value(call.head).into_pipeline_data())
|
||||
} else {
|
||||
let select = SelectDb::try_from_value(&value)?;
|
||||
Ok(select.to_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(ExprAsNu {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
78
crates/nu-command/src/database/expressions/field.rs
Normal file
78
crates/nu-command/src/database/expressions/field.rs
Normal file
@ -0,0 +1,78 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FieldExpr;
|
||||
|
||||
impl Command for FieldExpr {
|
||||
fn name(&self) -> &str {
|
||||
"field"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("name", SyntaxShape::String, "column name")
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates column expression for database"
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "column", "expression"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates a named field expression",
|
||||
example: "field name_1 | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "name_1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expression = ExprDb::try_from_value(&value)?;
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
157
crates/nu-command/src/database/expressions/function.rs
Normal file
157
crates/nu-command/src/database/expressions/function.rs
Normal file
@ -0,0 +1,157 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, Function, FunctionArg, FunctionArgExpr, Ident, ObjectName};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FunctionExpr;
|
||||
|
||||
impl Command for FunctionExpr {
|
||||
fn name(&self) -> &str {
|
||||
"fn"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("name", SyntaxShape::String, "function name")
|
||||
.switch("distinct", "distict values", Some('d'))
|
||||
.rest("arguments", SyntaxShape::Any, "function arguments")
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Creates function expression for a select operation"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Creates a function expression",
|
||||
example: "fn count name_1 | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec![
|
||||
"name".into(),
|
||||
"args".into(),
|
||||
"over".into(),
|
||||
"distinct".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "count".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::List {
|
||||
vals: vec![Value::String {
|
||||
val: "name_1".into(),
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select (fn lead col_a)
|
||||
| from table_a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT lead(col_a) FROM table_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "function", "expression"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let name: String = call.req(engine_state, stack, 0)?;
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 1)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let expressions = ExprDb::extract_exprs(value)?;
|
||||
|
||||
let name: Vec<Ident> = name
|
||||
.split('.')
|
||||
.map(|part| Ident {
|
||||
value: part.to_string(),
|
||||
quote_style: None,
|
||||
})
|
||||
.collect();
|
||||
let name = ObjectName(name);
|
||||
|
||||
let args: Vec<FunctionArg> = expressions
|
||||
.into_iter()
|
||||
.map(|expr| {
|
||||
let arg = FunctionArgExpr::Expr(expr);
|
||||
|
||||
FunctionArg::Unnamed(arg)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let expression: ExprDb = Expr::Function(Function {
|
||||
name,
|
||||
args,
|
||||
over: None,
|
||||
distinct: call.has_flag("distinct"),
|
||||
})
|
||||
.into();
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::commands::{FromDb, ProjectionDb};
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(FunctionExpr {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
40
crates/nu-command/src/database/expressions/mod.rs
Normal file
40
crates/nu-command/src/database/expressions/mod.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// Conversions between value and sqlparser objects
|
||||
mod alias;
|
||||
mod and;
|
||||
mod as_nu;
|
||||
mod field;
|
||||
mod function;
|
||||
mod or;
|
||||
mod over;
|
||||
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
|
||||
pub(crate) use alias::AliasExpr;
|
||||
pub(crate) use and::AndExpr;
|
||||
pub(crate) use as_nu::ExprAsNu;
|
||||
pub(crate) use field::FieldExpr;
|
||||
pub(crate) use function::FunctionExpr;
|
||||
pub(crate) use or::OrExpr;
|
||||
pub(crate) use over::OverExpr;
|
||||
|
||||
pub fn add_expressions_decls(working_set: &mut StateWorkingSet) {
|
||||
macro_rules! bind_command {
|
||||
( $command:expr ) => {
|
||||
working_set.add_decl(Box::new($command));
|
||||
};
|
||||
( $( $command:expr ),* ) => {
|
||||
$( working_set.add_decl(Box::new($command)); )*
|
||||
};
|
||||
}
|
||||
|
||||
// Series commands
|
||||
bind_command!(
|
||||
ExprAsNu,
|
||||
AliasExpr,
|
||||
AndExpr,
|
||||
FieldExpr,
|
||||
FunctionExpr,
|
||||
OrExpr,
|
||||
OverExpr
|
||||
);
|
||||
}
|
141
crates/nu-command/src/database/expressions/or.rs
Normal file
141
crates/nu-command/src/database/expressions/or.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{BinaryOperator, Expr};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OrExpr;
|
||||
|
||||
impl Command for OrExpr {
|
||||
fn name(&self) -> &str {
|
||||
"or"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Includes an OR clause for an expression"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.required("or", SyntaxShape::Any, "OR expression")
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "or", "expression"]
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Creates an AND expression",
|
||||
example: r#"(field a) > 1 | or ((field a) < 10) | into nu"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: ">".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "1".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "OR".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Record {
|
||||
cols: vec!["left".into(), "op".into(), "right".into()],
|
||||
vals: vec![
|
||||
Value::Record {
|
||||
cols: vec!["value".into(), "quoted_style".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "None".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "<".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "10".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value: Value = call.req(engine_state, stack, 0)?;
|
||||
let expr = ExprDb::try_from_value(&value)?.into_native();
|
||||
|
||||
let expression = ExprDb::try_from_pipeline(input, call.head)?;
|
||||
let expression = Expr::BinaryOp {
|
||||
left: Box::new(expression.into_native()),
|
||||
op: BinaryOperator::Or,
|
||||
right: Box::new(expr),
|
||||
};
|
||||
|
||||
let expression: ExprDb = Expr::Nested(Box::new(expression)).into();
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::FieldExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![Box::new(OrExpr {}), Box::new(FieldExpr {})])
|
||||
}
|
||||
}
|
153
crates/nu-command/src/database/expressions/over.rs
Normal file
153
crates/nu-command/src/database/expressions/over.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use crate::database::values::dsl::ExprDb;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
|
||||
Type, Value,
|
||||
};
|
||||
use sqlparser::ast::{Expr, WindowSpec};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OverExpr;
|
||||
|
||||
impl Command for OverExpr {
|
||||
fn name(&self) -> &str {
|
||||
"over"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest(
|
||||
"partition-by",
|
||||
SyntaxShape::Any,
|
||||
"columns to partition the window function",
|
||||
)
|
||||
.input_type(Type::Custom("db-expression".into()))
|
||||
.output_type(Type::Custom("db-expression".into()))
|
||||
.category(Category::Custom("db-expression".into()))
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Adds a partition to an expression function"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Adds a partition to a function expression",
|
||||
example: "fn avg col_a | over col_b | into nu",
|
||||
result: Some(Value::Record {
|
||||
cols: vec![
|
||||
"name".into(),
|
||||
"args".into(),
|
||||
"over".into(),
|
||||
"distinct".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "avg".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::List {
|
||||
vals: vec![Value::String {
|
||||
val: "col_a".into(),
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "Some(WindowSpec { partition_by: [Identifier(Ident { value: \"col_b\", quote_style: None })], order_by: [], window_frame: None })".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::Bool {
|
||||
val: false,
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "orders query by a column",
|
||||
example: r#"open db.mysql
|
||||
| into db
|
||||
| select (fn lead col_a | over col_b)
|
||||
| from table_a
|
||||
| describe"#,
|
||||
result: Some(Value::Record {
|
||||
cols: vec!["connection".into(), "query".into()],
|
||||
vals: vec![
|
||||
Value::String {
|
||||
val: "db.mysql".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
Value::String {
|
||||
val: "SELECT lead(col_a) OVER (PARTITION BY col_b) FROM table_a".into(),
|
||||
span: Span::test_data(),
|
||||
},
|
||||
],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["database", "over", "expression"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
|
||||
let value = Value::List {
|
||||
vals,
|
||||
span: call.head,
|
||||
};
|
||||
let partitions = ExprDb::extract_exprs(value)?;
|
||||
|
||||
let mut expression = ExprDb::try_from_pipeline(input, call.head)?;
|
||||
match expression.as_mut() {
|
||||
Expr::Function(function) => {
|
||||
function.over = Some(WindowSpec {
|
||||
partition_by: partitions,
|
||||
order_by: Vec::new(),
|
||||
window_frame: None,
|
||||
});
|
||||
}
|
||||
s => {
|
||||
return Err(ShellError::GenericError(
|
||||
"Expression doesnt define a function".into(),
|
||||
format!("Expected an expression with a function. Got {}", s),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(expression.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::commands::{FromDb, ProjectionDb};
|
||||
use super::super::FunctionExpr;
|
||||
use super::*;
|
||||
use crate::database::test_database::test_database;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_database(vec![
|
||||
Box::new(OverExpr {}),
|
||||
Box::new(FunctionExpr {}),
|
||||
Box::new(ProjectionDb {}),
|
||||
Box::new(FromDb {}),
|
||||
])
|
||||
}
|
||||
}
|
@ -1,8 +1,21 @@
|
||||
mod commands;
|
||||
mod expressions;
|
||||
mod values;
|
||||
|
||||
pub use commands::add_database_decls;
|
||||
use commands::add_commands_decls;
|
||||
use expressions::add_expressions_decls;
|
||||
|
||||
pub use values::{
|
||||
convert_sqlite_row_to_nu_value, convert_sqlite_value_to_nu_value, open_connection_in_memory,
|
||||
SQLiteDatabase,
|
||||
};
|
||||
|
||||
use nu_protocol::engine::StateWorkingSet;
|
||||
|
||||
pub fn add_database_decls(working_set: &mut StateWorkingSet) {
|
||||
add_commands_decls(working_set);
|
||||
add_expressions_decls(working_set);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_database;
|
||||
|
141
crates/nu-command/src/database/test_database.rs
Normal file
141
crates/nu-command/src/database/test_database.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use std::path::Path;
|
||||
|
||||
use super::commands::{DescribeDb, ToDataBase};
|
||||
use super::expressions::ExprAsNu;
|
||||
use crate::SQLiteDatabase;
|
||||
use nu_engine::{eval_block, CallExt};
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||
Category, IntoPipelineData, PipelineData, Signature, Span, Type,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CustomOpen;
|
||||
|
||||
impl Command for CustomOpen {
|
||||
fn name(&self) -> &str {
|
||||
"open"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Mock open file command"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build(self.name())
|
||||
.required(
|
||||
"filename",
|
||||
nu_protocol::SyntaxShape::String,
|
||||
"the filename to use",
|
||||
)
|
||||
.input_type(Type::Any)
|
||||
.output_type(Type::Custom("database".into()))
|
||||
.category(Category::Custom("database".into()))
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &nu_protocol::ast::Call,
|
||||
_input: nu_protocol::PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let path: String = call.req(engine_state, stack, 0)?;
|
||||
let path = Path::new(&path);
|
||||
|
||||
let db = SQLiteDatabase::new(path);
|
||||
Ok(db.into_value(call.head).into_pipeline_data())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_database(cmds: Vec<Box<dyn Command + 'static>>) {
|
||||
if cmds.is_empty() {
|
||||
panic!("Empty commands vector")
|
||||
}
|
||||
|
||||
// The first element in the cmds vector must be the one tested
|
||||
let examples = cmds[0].examples();
|
||||
let mut engine_state = Box::new(EngineState::new());
|
||||
|
||||
let delta = {
|
||||
// Base functions that are needed for testing
|
||||
// Try to keep this working set small to keep tests running as fast as possible
|
||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||
working_set.add_decl(Box::new(DescribeDb {}));
|
||||
working_set.add_decl(Box::new(ToDataBase {}));
|
||||
working_set.add_decl(Box::new(CustomOpen {}));
|
||||
working_set.add_decl(Box::new(ExprAsNu {}));
|
||||
|
||||
// Adding the command that is being tested to the working set
|
||||
for cmd in cmds {
|
||||
working_set.add_decl(cmd);
|
||||
}
|
||||
|
||||
working_set.render()
|
||||
};
|
||||
|
||||
engine_state
|
||||
.merge_delta(delta)
|
||||
.expect("Error merging delta");
|
||||
|
||||
for example in examples {
|
||||
// Skip tests that don't have results to compare to
|
||||
if example.result.is_none() {
|
||||
continue;
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&*engine_state);
|
||||
let (output, err) = parse(
|
||||
&mut working_set,
|
||||
None,
|
||||
example.example.as_bytes(),
|
||||
false,
|
||||
&[],
|
||||
);
|
||||
|
||||
if let Some(err) = err {
|
||||
panic!("test parse error in `{}`: {:?}", example.example, err)
|
||||
}
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
engine_state
|
||||
.merge_delta(delta)
|
||||
.expect("Error merging delta");
|
||||
|
||||
let mut stack = Stack::new();
|
||||
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
PipelineData::new(Span::test_data()),
|
||||
true,
|
||||
true,
|
||||
) {
|
||||
Err(err) => panic!("test eval error in `{}`: {:?}", example.example, err),
|
||||
Ok(result) => {
|
||||
let result = result.into_value(Span::test_data());
|
||||
println!("input: {}", example.example);
|
||||
println!("result: {:?}", result);
|
||||
println!("done: {:?}", start.elapsed());
|
||||
|
||||
// Note. Value implements PartialEq for Bool, Int, Float, String and Block
|
||||
// If the command you are testing requires to compare another case, then
|
||||
// you need to define its equality in the Value struct
|
||||
if let Some(expected) = example.result {
|
||||
if result != expected {
|
||||
panic!(
|
||||
"the example result is different to expected value: {:?} != {:?}",
|
||||
result, expected
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -118,6 +118,11 @@ impl CustomValue for ExprDb {
|
||||
Operator::In
|
||||
| Operator::NotIn
|
||||
| Operator::Pow
|
||||
| Operator::BitOr
|
||||
| Operator::BitXor
|
||||
| Operator::BitAnd
|
||||
| Operator::ShiftLeft
|
||||
| Operator::ShiftRight
|
||||
| Operator::StartsWith
|
||||
| Operator::EndsWith => Err(ShellError::UnsupportedOperator(operator, op)),
|
||||
}?;
|
||||
|
@ -23,6 +23,8 @@ impl Command for AppendDF {
|
||||
Signature::build(self.name())
|
||||
.required("other", SyntaxShape::Any, "dataframe to be appended")
|
||||
.switch("col", "appends in col orientation", Some('c'))
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
@ -30,7 +32,7 @@ impl Command for AppendDF {
|
||||
vec![
|
||||
Example {
|
||||
description: "Appends a dataframe as new columns",
|
||||
example: r#"let a = ([[a b]; [1 2] [3 4]] | to-df);
|
||||
example: r#"let a = ([[a b]; [1 2] [3 4]] | into df);
|
||||
$a | append $a"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
@ -57,7 +59,7 @@ impl Command for AppendDF {
|
||||
},
|
||||
Example {
|
||||
description: "Appends a dataframe merging at the end of columns",
|
||||
example: r#"let a = ([[a b]; [1 2] [3 4]] | to-df);
|
||||
example: r#"let a = ([[a b]; [1 2] [3 4]] | into df);
|
||||
$a | append $a --col"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
@ -87,14 +89,6 @@ impl Command for AppendDF {
|
||||
]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -29,6 +29,8 @@ impl Command for DescribeDF {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.named(
|
||||
"quantiles",
|
||||
SyntaxShape::Table,
|
||||
@ -40,7 +42,7 @@ impl Command for DescribeDF {
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "dataframe description",
|
||||
example: "[[a b]; [1 1] [1 1]] | to-df | describe",
|
||||
example: "[[a b]; [1 1] [1 1]] | into df | describe",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
@ -95,14 +97,6 @@ impl Command for DescribeDF {
|
||||
}]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -23,13 +23,15 @@ impl Command for DropDF {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest("rest", SyntaxShape::Any, "column names to be dropped")
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "drop column a",
|
||||
example: "[[a b]; [1 2] [3 4]] | to-df | drop a",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | drop a",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"b".to_string(),
|
||||
@ -41,14 +43,6 @@ impl Command for DropDF {
|
||||
}]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -34,13 +34,15 @@ impl Command for DropDuplicates {
|
||||
"keeps last duplicate value (by default keeps first)",
|
||||
Some('l'),
|
||||
)
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "drop duplicates",
|
||||
example: "[[a b]; [1 2] [3 4] [1 2]] | to-df | drop-duplicates",
|
||||
example: "[[a b]; [1 2] [3 4] [1 2]] | into df | drop-duplicates",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
@ -58,14 +60,6 @@ impl Command for DropDuplicates {
|
||||
}]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -27,6 +27,8 @@ impl Command for DropNulls {
|
||||
SyntaxShape::Table,
|
||||
"subset of columns to drop nulls",
|
||||
)
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
@ -34,7 +36,7 @@ impl Command for DropNulls {
|
||||
vec![
|
||||
Example {
|
||||
description: "drop null values in dataframe",
|
||||
example: r#"let df = ([[a b]; [1 2] [3 0] [1 2]] | to-df);
|
||||
example: r#"let df = ([[a b]; [1 2] [3 0] [1 2]] | into df);
|
||||
let res = ($df.b / $df.b);
|
||||
let a = ($df | with-column $res --name res);
|
||||
$a | drop-nulls"#,
|
||||
@ -59,7 +61,7 @@ impl Command for DropNulls {
|
||||
},
|
||||
Example {
|
||||
description: "drop null values in dataframe",
|
||||
example: r#"let s = ([1 2 0 0 3 4] | to-df);
|
||||
example: r#"let s = ([1 2 0 0 3 4] | into df);
|
||||
($s / $s) | drop-nulls"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
@ -78,14 +80,6 @@ impl Command for DropNulls {
|
||||
]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -18,13 +18,16 @@ impl Command for DataTypes {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Dataframe dtypes",
|
||||
example: "[[a b]; [1 2] [3 4]] | to-df | dtypes",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | dtypes",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
@ -42,14 +45,6 @@ impl Command for DataTypes {
|
||||
}]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -11,7 +11,7 @@ pub struct Dummies;
|
||||
|
||||
impl Command for Dummies {
|
||||
fn name(&self) -> &str {
|
||||
"to-dummies"
|
||||
"dummies"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
@ -19,14 +19,17 @@ impl Command for Dummies {
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name()).category(Category::Custom("dataframe".into()))
|
||||
Signature::build(self.name())
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Create new dataframe with dummy variables from a dataframe",
|
||||
example: "[[a b]; [1 2] [3 4]] | to-df | to-dummies",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | dummies",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
@ -52,7 +55,7 @@ impl Command for Dummies {
|
||||
},
|
||||
Example {
|
||||
description: "Create new dataframe with dummy variables from a series",
|
||||
example: "[1 2 2 3 3] | to-df | to-dummies",
|
||||
example: "[1 2 2 3 3] | into df | dummies",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
@ -93,14 +96,6 @@ impl Command for Dummies {
|
||||
]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -29,6 +29,8 @@ impl Command for FilterWith {
|
||||
SyntaxShape::Any,
|
||||
"boolean mask used to filter data",
|
||||
)
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe or lazyframe".into()))
|
||||
}
|
||||
|
||||
@ -36,8 +38,8 @@ impl Command for FilterWith {
|
||||
vec![
|
||||
Example {
|
||||
description: "Filter dataframe using a bool mask",
|
||||
example: r#"let mask = ([true false] | to-df);
|
||||
[[a b]; [1 2] [3 4]] | to-df | filter-with $mask"#,
|
||||
example: r#"let mask = ([true false] | into df);
|
||||
[[a b]; [1 2] [3 4]] | into df | filter-with $mask"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
||||
@ -49,7 +51,7 @@ impl Command for FilterWith {
|
||||
},
|
||||
Example {
|
||||
description: "Filter dataframe using an expression",
|
||||
example: "[[a b]; [1 2] [3 4]] | to-df | filter-with ((col a) > 1)",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | filter-with ((col a) > 1)",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(3)]),
|
||||
@ -62,14 +64,6 @@ impl Command for FilterWith {
|
||||
]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -21,13 +21,15 @@ impl Command for FirstDF {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.optional("rows", SyntaxShape::Int, "Number of rows for head")
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create new dataframe with head rows",
|
||||
example: "[[a b]; [1 2] [3 4]] | to-df | first 1",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | first 1",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(1)]),
|
||||
@ -39,14 +41,6 @@ impl Command for FirstDF {
|
||||
}]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -24,13 +24,15 @@ impl Command for GetDF {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest("rest", SyntaxShape::Any, "column names to sort dataframe")
|
||||
.input_type(Type::Custom("dataframe".into()))
|
||||
.output_type(Type::Custom("dataframe".into()))
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Returns the selected column",
|
||||
example: "[[a b]; [1 2] [3 4]] | to-df | get a",
|
||||
example: "[[a b]; [1 2] [3 4]] | into df | get a",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"a".to_string(),
|
||||
@ -42,14 +44,6 @@ impl Command for GetDF {
|
||||
}]
|
||||
}
|
||||
|
||||
fn input_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn output_type(&self) -> Type {
|
||||
Type::Custom("dataframe".into())
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user