Compare commits

..

1 Commits

Author SHA1 Message Date
286a6b021c Revert "Add format meta command (#11334)"
This reverts commit fd77114d82.
2023-12-15 06:25:37 -06:00
481 changed files with 8104 additions and 14218 deletions

1
.github/.typos.toml vendored
View File

@ -12,4 +12,3 @@ IIF = "IIF"
numer = "numer" numer = "numer"
ratatui = "ratatui" ratatui = "ratatui"
doas = "doas" doas = "doas"
wheres = "wheres"

View File

@ -41,7 +41,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
with: with:
rustflags: "" rustflags: ""
@ -63,10 +63,6 @@ jobs:
platform: [windows-latest, macos-latest, ubuntu-20.04] platform: [windows-latest, macos-latest, ubuntu-20.04]
feature: [default, dataframe, extra] feature: [default, dataframe, extra]
include: include:
# linux CI cannot handle clipboard feature
- default-flags: ""
- platform: ubuntu-20.04
default-flags: "--no-default-features --features=default-no-clipboard"
- feature: default - feature: default
flags: "" flags: ""
- feature: dataframe - feature: dataframe
@ -89,23 +85,12 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
with: with:
rustflags: "" rustflags: ""
- name: Tests - name: Tests
run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.default-flags }} ${{ matrix.flags }} run: cargo test --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
- name: Check for clean repo
shell: bash
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "there are changes";
git status --porcelain
exit 1
else
echo "no changes in working directory";
fi
std-lib-and-python-virtualenv: std-lib-and-python-virtualenv:
strategy: strategy:
@ -121,7 +106,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
with: with:
rustflags: "" rustflags: ""
@ -129,7 +114,7 @@ jobs:
run: cargo install --path . --locked --no-default-features run: cargo install --path . --locked --no-default-features
- name: Standard library tests - name: Standard library tests
run: nu -c 'use crates/nu-std/testing.nu; testing run-tests --path crates/nu-std' run: nu -c 'use std testing; testing run-tests --path crates/nu-std'
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5 uses: actions/setup-python@v5
@ -144,17 +129,6 @@ jobs:
run: nu scripts/test_virtualenv.nu run: nu scripts/test_virtualenv.nu
shell: bash shell: bash
- name: Check for clean repo
shell: bash
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "there are changes";
git status --porcelain
exit 1
else
echo "no changes in working directory";
fi
plugins: plugins:
strategy: strategy:
fail-fast: true fail-fast: true
@ -167,7 +141,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
with: with:
rustflags: "" rustflags: ""
@ -176,14 +150,3 @@ jobs:
- name: Tests - name: Tests
run: cargo test --profile ci --package nu_plugin_* run: cargo test --profile ci --package nu_plugin_*
- name: Check for clean repo
shell: bash
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "there are changes";
git status --porcelain
exit 1
else
echo "no changes in working directory";
fi

View File

@ -81,7 +81,7 @@ jobs:
- x86_64-unknown-linux-musl - x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu - aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf - armv7-unknown-linux-gnueabihf
# - riscv64gc-unknown-linux-gnu - riscv64gc-unknown-linux-gnu
extra: ['bin'] extra: ['bin']
include: include:
- target: aarch64-apple-darwin - target: aarch64-apple-darwin
@ -118,9 +118,9 @@ jobs:
- target: armv7-unknown-linux-gnueabihf - target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04 os: ubuntu-20.04
target_rustflags: '' target_rustflags: ''
# - target: riscv64gc-unknown-linux-gnu - target: riscv64gc-unknown-linux-gnu
# os: ubuntu-latest os: ubuntu-20.04
# target_rustflags: '' target_rustflags: ''
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
@ -135,7 +135,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
rustflags: '' rustflags: ''
@ -249,7 +249,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
rustflags: '' rustflags: ''

View File

@ -82,8 +82,8 @@ print $'Start building ($bin)...'; hr-line
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# Build for Ubuntu and macOS # Build for Ubuntu and macOS
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] { if $os in [$USE_UBUNTU, 'macos-latest'] {
if $os starts-with ubuntu { if $os == $USE_UBUNTU {
sudo apt update sudo apt update
sudo apt-get install libxcb-composite0-dev -y sudo apt-get install libxcb-composite0-dev -y
} }
@ -106,7 +106,7 @@ if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] {
_ => { _ => {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?' # musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target # Actually just for x86_64-unknown-linux-musl target
if $os starts-with ubuntu { sudo apt install musl-tools -y } if $os == $USE_UBUNTU { sudo apt install musl-tools -y }
cargo-build-nu $flags cargo-build-nu $flags
} }
} }
@ -153,7 +153,7 @@ if ($ver | str trim | is-empty) {
# Create a release archive and send it to output for the following steps # Create a release archive and send it to output for the following steps
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
cd $dist; print $'(char nl)Creating release archive...'; hr-line cd $dist; print $'(char nl)Creating release archive...'; hr-line
if $os in [$USE_UBUNTU, 'macos-latest', 'ubuntu-latest'] { if $os in [$USE_UBUNTU, 'macos-latest'] {
let files = (ls | get name) let files = (ls | get name)
let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' } let dest = if $env.RELEASE_TYPE == 'full' { $'($bin)-($version)-($FULL_NAME)' } else { $'($bin)-($version)-($target)' }

View File

@ -28,7 +28,7 @@ jobs:
- x86_64-unknown-linux-musl - x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu - aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf - armv7-unknown-linux-gnueabihf
# - riscv64gc-unknown-linux-gnu - riscv64gc-unknown-linux-gnu
extra: ['bin'] extra: ['bin']
include: include:
- target: aarch64-apple-darwin - target: aarch64-apple-darwin
@ -65,9 +65,9 @@ jobs:
- target: armv7-unknown-linux-gnueabihf - target: armv7-unknown-linux-gnueabihf
os: ubuntu-20.04 os: ubuntu-20.04
target_rustflags: '' target_rustflags: ''
# - target: riscv64gc-unknown-linux-gnu - target: riscv64gc-unknown-linux-gnu
# os: ubuntu-latest os: ubuntu-20.04
# target_rustflags: '' target_rustflags: ''
runs-on: ${{matrix.os}} runs-on: ${{matrix.os}}
@ -79,7 +79,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
rustflags: '' rustflags: ''
@ -170,7 +170,7 @@ jobs:
echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml echo "targets = ['${{matrix.target}}']" >> rust-toolchain.toml
- name: Setup Rust toolchain and cache - name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0 uses: actions-rust-lang/setup-rust-toolchain@v1.6.0
# WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135` # WARN: Keep the rustflags to prevent from the winget submission error: `CAQuietExec: Error 0xc0000135`
with: with:
rustflags: '' rustflags: ''

View File

@ -10,6 +10,6 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Check spelling - name: Check spelling
uses: crate-ci/typos@v1.18.0 uses: crate-ci/typos@v1.16.24
with: with:
config: ./.github/.typos.toml config: ./.github/.typos.toml

1564
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ license = "MIT"
name = "nu" name = "nu"
repository = "https://github.com/nushell/nushell" repository = "https://github.com/nushell/nushell"
rust-version = "1.72.1" rust-version = "1.72.1"
version = "0.90.1" version = "0.88.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -33,11 +33,7 @@ members = [
"crates/nu-cmd-lang", "crates/nu-cmd-lang",
"crates/nu-cmd-dataframe", "crates/nu-cmd-dataframe",
"crates/nu-command", "crates/nu-command",
"crates/nu-color-config",
"crates/nu-explore",
"crates/nu-json",
"crates/nu-lsp", "crates/nu-lsp",
"crates/nu-pretty-hex",
"crates/nu-protocol", "crates/nu-protocol",
"crates/nu-plugin", "crates/nu-plugin",
"crates/nu_plugin_inc", "crates/nu_plugin_inc",
@ -47,38 +43,33 @@ members = [
"crates/nu_plugin_custom_values", "crates/nu_plugin_custom_values",
"crates/nu_plugin_formats", "crates/nu_plugin_formats",
"crates/nu-std", "crates/nu-std",
"crates/nu-table",
"crates/nu-term-grid",
"crates/nu-utils", "crates/nu-utils",
] ]
[dependencies] [dependencies]
nu-cli = { path = "./crates/nu-cli", version = "0.90.1" } nu-cli = { path = "./crates/nu-cli", version = "0.88.2" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.90.1" } nu-color-config = { path = "./crates/nu-color-config", version = "0.88.2" }
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.90.1" } nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.88.2" }
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.90.1" } nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.88.2" }
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.90.1", features = [ nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.88.2", features = ["dataframe"], optional = true }
"dataframe", nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.88.2", optional = true }
], optional = true } nu-command = { path = "./crates/nu-command", version = "0.88.2" }
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.90.1", optional = true } nu-engine = { path = "./crates/nu-engine", version = "0.88.2" }
nu-command = { path = "./crates/nu-command", version = "0.90.1" } nu-explore = { path = "./crates/nu-explore", version = "0.88.2" }
nu-engine = { path = "./crates/nu-engine", version = "0.90.1" } nu-json = { path = "./crates/nu-json", version = "0.88.2" }
nu-explore = { path = "./crates/nu-explore", version = "0.90.1" } nu-lsp = { path = "./crates/nu-lsp/", version = "0.88.2" }
nu-json = { path = "./crates/nu-json", version = "0.90.1" } nu-parser = { path = "./crates/nu-parser", version = "0.88.2" }
nu-lsp = { path = "./crates/nu-lsp/", version = "0.90.1" } nu-path = { path = "./crates/nu-path", version = "0.88.2" }
nu-parser = { path = "./crates/nu-parser", version = "0.90.1" } nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.88.2" }
nu-path = { path = "./crates/nu-path", version = "0.90.1" } nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.88.2" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.90.1" } nu-protocol = { path = "./crates/nu-protocol", version = "0.88.2" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.90.1" } nu-system = { path = "./crates/nu-system", version = "0.88.2" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.90.1" } nu-table = { path = "./crates/nu-table", version = "0.88.2" }
nu-system = { path = "./crates/nu-system", version = "0.90.1" } nu-term-grid = { path = "./crates/nu-term-grid", version = "0.88.2" }
nu-table = { path = "./crates/nu-table", version = "0.90.1" } nu-std = { path = "./crates/nu-std", version = "0.88.2" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.90.1" } nu-utils = { path = "./crates/nu-utils", version = "0.88.2" }
nu-std = { path = "./crates/nu-std", version = "0.90.1" } nu-ansi-term = "0.49.0"
nu-utils = { path = "./crates/nu-utils", version = "0.90.1" } reedline = { version = "0.27.0", features = ["bashisms", "sqlite"] }
nu-ansi-term = "0.50.0"
reedline = { version = "0.29.0", features = ["bashisms", "sqlite"] }
crossterm = "0.27" crossterm = "0.27"
ctrlc = "3.4" ctrlc = "3.4"
@ -92,6 +83,7 @@ time = "0.3"
[target.'cfg(not(target_os = "windows"))'.dependencies] [target.'cfg(not(target_os = "windows"))'.dependencies]
# Our dependencies don't use OpenSSL on Windows # Our dependencies don't use OpenSSL on Windows
openssl = { version = "0.10", features = ["vendored"], optional = true } openssl = { version = "0.10", features = ["vendored"], optional = true }
signal-hook = { version = "0.3", default-features = false }
[target.'cfg(windows)'.build-dependencies] [target.'cfg(windows)'.build-dependencies]
winresource = "0.1" winresource = "0.1"
@ -105,7 +97,7 @@ nix = { version = "0.27", default-features = false, features = [
] } ] }
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "./crates/nu-test-support", version = "0.90.1" } nu-test-support = { path = "./crates/nu-test-support", version = "0.88.2" }
assert_cmd = "2.0" assert_cmd = "2.0"
criterion = "0.5" criterion = "0.5"
pretty_assertions = "1.4" pretty_assertions = "1.4"
@ -122,16 +114,7 @@ plugin = [
"nu-protocol/plugin", "nu-protocol/plugin",
"nu-engine/plugin", "nu-engine/plugin",
] ]
default = ["default-no-clipboard", "system-clipboard"] default = ["plugin", "which-support", "trash-support", "sqlite", "mimalloc"]
# Enables convenient omitting of the system-clipboard feature, as it leads to problems in ci on linux
# See https://github.com/nushell/nushell/pull/11535
default-no-clipboard = [
"plugin",
"which-support",
"trash-support",
"sqlite",
"mimalloc",
]
stable = ["default"] stable = ["default"]
wasi = ["nu-cmd-lang/wasi"] wasi = ["nu-cmd-lang/wasi"]
# NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command # NOTE: individual features are also passed to `nu-cmd-lang` that uses them to generate the feature matrix in the `version` command
@ -141,7 +124,6 @@ wasi = ["nu-cmd-lang/wasi"]
static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"] static-link-openssl = ["dep:openssl", "nu-cmd-lang/static-link-openssl"]
mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"] mimalloc = ["nu-cmd-lang/mimalloc", "dep:mimalloc"]
system-clipboard = ["reedline/system_clipboard"]
# Stable (Default) # Stable (Default)
which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"] which-support = ["nu-command/which-support", "nu-cmd-lang/which-support"]
@ -183,8 +165,8 @@ bench = false
# To use a development version of a dependency please use a global override here # To use a development version of a dependency please use a global override here
# changing versions in each sub-crate of the workspace is tedious # changing versions in each sub-crate of the workspace is tedious
#[patch.crates-io] [patch.crates-io]
#reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # reedline = { git = "https://github.com/nushell/reedline.git", branch = "main" }
# nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"} # nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"}
# uu_cp = { git = "https://github.com/uutils/coreutils.git", branch = "main" } # uu_cp = { git = "https://github.com/uutils/coreutils.git", branch = "main" }

View File

@ -24,9 +24,13 @@ fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
} }
fn get_home_path(engine_state: &EngineState) -> PathBuf { fn get_home_path(engine_state: &EngineState) -> PathBuf {
nu_path::home_dir() let home_path = if let Some(path) = nu_path::home_dir() {
.map(|path| canonicalize_path(engine_state, &path)) let canon_home_path = canonicalize_path(engine_state, &path);
.unwrap_or_default() canon_home_path
} else {
std::path::PathBuf::new()
};
home_path
} }
// FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking. // FIXME: All benchmarks live in this 1 file to speed up build times when benchmarking.

View File

@ -5,31 +5,31 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cli" name = "nu-cli"
version = "0.90.1" version = "0.88.2"
[lib] [lib]
bench = false bench = false
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.90.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.88.2" }
nu-command = { path = "../nu-command", version = "0.90.1" } nu-command = { path = "../nu-command", version = "0.88.2" }
nu-test-support = { path = "../nu-test-support", version = "0.90.1" } nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
rstest = { version = "0.18.1", default-features = false } rstest = { version = "0.18.1", default-features = false }
[dependencies] [dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.90.1" } nu-cmd-base = { path = "../nu-cmd-base", version = "0.88.2" }
nu-engine = { path = "../nu-engine", version = "0.90.1" } nu-engine = { path = "../nu-engine", version = "0.88.2" }
nu-path = { path = "../nu-path", version = "0.90.1" } nu-path = { path = "../nu-path", version = "0.88.2" }
nu-parser = { path = "../nu-parser", version = "0.90.1" } nu-parser = { path = "../nu-parser", version = "0.88.2" }
nu-protocol = { path = "../nu-protocol", version = "0.90.1" } nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
nu-utils = { path = "../nu-utils", version = "0.90.1" } nu-utils = { path = "../nu-utils", version = "0.88.2" }
nu-color-config = { path = "../nu-color-config", version = "0.90.1" } nu-color-config = { path = "../nu-color-config", version = "0.88.2" }
nu-ansi-term = "0.50.0" nu-ansi-term = "0.49.0"
reedline = { version = "0.29.0", features = ["bashisms", "sqlite"] } reedline = { version = "0.27.0", features = ["bashisms", "sqlite"] }
chrono = { default-features = false, features = ["std"], version = "0.4" } chrono = { default-features = false, features = ["std"], version = "0.4" }
crossterm = "0.27" crossterm = "0.27"
fancy-regex = "0.12" fancy-regex = "0.11"
fuzzy-matcher = "0.3" fuzzy-matcher = "0.3"
is_executable = "1.0" is_executable = "1.0"
log = "0.4" log = "0.4"
@ -37,7 +37,7 @@ miette = { version = "5.10", features = ["fancy-no-backtrace"] }
once_cell = "1.18" once_cell = "1.18"
percent-encoding = "2" percent-encoding = "2"
pathdiff = "0.2" pathdiff = "0.2"
sysinfo = "0.30" sysinfo = "0.29"
unicode-segmentation = "1.10" unicode-segmentation = "1.10"
uuid = { version = "1.6.0", features = ["v4"] } uuid = { version = "1.6.0", features = ["v4"] }
which = "5.0.0" which = "5.0.0"

View File

@ -71,7 +71,7 @@ impl Command for Commandline {
if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? { if let Some(cmd) = call.opt::<Value>(engine_state, stack, 0)? {
let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag(engine_state, stack, "cursor")? { if call.has_flag("cursor") {
let cmd_str = cmd.as_string()?; let cmd_str = cmd.as_string()?;
match cmd_str.parse::<i64>() { match cmd_str.parse::<i64>() {
Ok(n) => { Ok(n) => {
@ -96,9 +96,9 @@ impl Command for Commandline {
}) })
} }
} }
} else if call.has_flag(engine_state, stack, "append")? { } else if call.has_flag("append") {
repl.buffer.push_str(&cmd.as_string()?); repl.buffer.push_str(&cmd.as_string()?);
} else if call.has_flag(engine_state, stack, "insert")? { } else if call.has_flag("insert") {
let cmd_str = cmd.as_string()?; let cmd_str = cmd.as_string()?;
let cursor_pos = repl.cursor_pos; let cursor_pos = repl.cursor_pos;
repl.buffer.insert_str(cursor_pos, &cmd_str); repl.buffer.insert_str(cursor_pos, &cmd_str);
@ -110,10 +110,10 @@ impl Command for Commandline {
Ok(Value::nothing(call.head).into_pipeline_data()) Ok(Value::nothing(call.head).into_pipeline_data())
} else { } else {
let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
if call.has_flag(engine_state, stack, "cursor-end")? { if call.has_flag("cursor-end") {
repl.cursor_pos = repl.buffer.len(); repl.cursor_pos = repl.buffer.graphemes(true).count();
Ok(Value::nothing(call.head).into_pipeline_data()) Ok(Value::nothing(call.head).into_pipeline_data())
} else if call.has_flag(engine_state, stack, "cursor")? { } else if call.has_flag("cursor") {
let char_pos = repl let char_pos = repl
.buffer .buffer
.grapheme_indices(true) .grapheme_indices(true)

View File

@ -1,4 +1,3 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{ use nu_protocol::{
@ -24,7 +23,10 @@ impl Command for History {
fn signature(&self) -> nu_protocol::Signature { fn signature(&self) -> nu_protocol::Signature {
Signature::build("history") Signature::build("history")
.input_output_types(vec![(Type::Nothing, Type::Any)]) .input_output_types(vec![
(Type::Nothing, Type::Table(vec![])),
(Type::Nothing, Type::Nothing),
])
.allow_variants_without_examples(true) .allow_variants_without_examples(true)
.switch("clear", "Clears out the history entries", Some('c')) .switch("clear", "Clears out the history entries", Some('c'))
.switch( .switch(
@ -38,25 +40,21 @@ impl Command for History {
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
_input: PipelineData, _input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let head = call.head; let head = call.head;
let Some(history) = engine_state.history_config() else {
return Ok(PipelineData::empty());
};
// todo for sqlite history this command should be an alias to `open ~/.config/nushell/history.sqlite3 | get history` // 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() { if let Some(config_path) = nu_path::config_dir() {
let clear = call.has_flag(engine_state, stack, "clear")?; let clear = call.has_flag("clear");
let long = call.has_flag(engine_state, stack, "long")?; let long = call.has_flag("long");
let ctrlc = engine_state.ctrlc.clone(); let ctrlc = engine_state.ctrlc.clone();
let mut history_path = config_path; let mut history_path = config_path;
history_path.push("nushell"); history_path.push("nushell");
match history.file_format { match engine_state.config.history_file_format {
HistoryFileFormat::Sqlite => { HistoryFileFormat::Sqlite => {
history_path.push("history.sqlite3"); history_path.push("history.sqlite3");
} }
@ -70,27 +68,29 @@ impl Command for History {
// TODO: FIXME also clear the auxiliary files when using sqlite // TODO: FIXME also clear the auxiliary files when using sqlite
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} else { } else {
let history_reader: Option<Box<dyn ReedlineHistory>> = match history.file_format { let history_reader: Option<Box<dyn ReedlineHistory>> =
HistoryFileFormat::Sqlite => { match engine_state.config.history_file_format {
SqliteBackedHistory::with_file(history_path, None, None) HistoryFileFormat::Sqlite => {
.map(|inner| { SqliteBackedHistory::with_file(history_path, None, None)
let boxed: Box<dyn ReedlineHistory> = Box::new(inner); .map(|inner| {
boxed let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
}) boxed
.ok() })
} .ok()
}
HistoryFileFormat::PlainText => { HistoryFileFormat::PlainText => FileBackedHistory::with_file(
FileBackedHistory::with_file(history.max_size as usize, history_path) engine_state.config.max_history_size as usize,
.map(|inner| { history_path,
let boxed: Box<dyn ReedlineHistory> = Box::new(inner); )
boxed .map(|inner| {
}) let boxed: Box<dyn ReedlineHistory> = Box::new(inner);
.ok() boxed
} })
}; .ok(),
};
match history.file_format { match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Ok(history_reader HistoryFileFormat::PlainText => Ok(history_reader
.and_then(|h| { .and_then(|h| {
h.search(SearchQuery::everything(SearchDirection::Forward, None)) h.search(SearchQuery::everything(SearchDirection::Forward, None))

View File

@ -94,7 +94,6 @@ impl CommandCompletion {
.map(move |x| Suggestion { .map(move |x| Suggestion {
value: String::from_utf8_lossy(&x.0).to_string(), value: String::from_utf8_lossy(&x.0).to_string(),
description: x.1, description: x.1,
style: None,
extra: None, extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true, append_whitespace: true,
@ -111,7 +110,6 @@ impl CommandCompletion {
.map(move |x| Suggestion { .map(move |x| Suggestion {
value: x, value: x,
description: None, description: None,
style: None,
extra: None, extra: None,
span: reedline::Span::new(span.start - offset, span.end - offset), span: reedline::Span::new(span.start - offset, span.end - offset),
append_whitespace: true, append_whitespace: true,
@ -125,7 +123,6 @@ impl CommandCompletion {
results.push(Suggestion { results.push(Suggestion {
value: format!("^{}", external.value), value: format!("^{}", external.value),
description: None, description: None,
style: None,
extra: None, extra: None,
span: external.span, span: external.span,
append_whitespace: true, append_whitespace: true,

View File

@ -2,7 +2,6 @@ use crate::completions::{
CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion, CommandCompletion, Completer, CompletionOptions, CustomCompletion, DirectoryCompletion,
DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion, DotNuCompletion, FileCompletion, FlagCompletion, VariableCompletion,
}; };
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
use nu_engine::eval_block; use nu_engine::eval_block;
use nu_parser::{flatten_expression, parse, FlatShape}; use nu_parser::{flatten_expression, parse, FlatShape};
use nu_protocol::{ use nu_protocol::{
@ -111,16 +110,10 @@ impl NuCompleter {
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> { fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut working_set = StateWorkingSet::new(&self.engine_state); let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start(); let offset = working_set.next_span_start();
// TODO: Callers should be trimming the line themselves
let line = if line.len() > pos { &line[..pos] } else { line };
// Adjust offset so that the spans of the suggestions will start at the right
// place even with `only_buffer_difference: true`
let fake_offset = offset + line.len() - pos;
let pos = offset + line.len();
let initial_line = line.to_string(); let initial_line = line.to_string();
let mut line = line.to_string(); let mut line = line.to_string();
line.push('a'); line.insert(pos, 'a');
let pos = offset + pos;
let config = self.engine_state.get_config(); let config = self.engine_state.get_config();
let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false); let output = parse(&mut working_set, Some("completer"), line.as_bytes(), false);
@ -193,7 +186,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
} }
@ -207,7 +200,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix.clone(), prefix.clone(),
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
@ -218,12 +211,9 @@ impl NuCompleter {
// We got no results for internal completion // We got no results for internal completion
// now we can check if external completer is set and use it // now we can check if external completer is set and use it
if let Some(block_id) = config.external_completer { if let Some(block_id) = config.external_completer {
if let Some(external_result) = self.external_completion( if let Some(external_result) = self
block_id, .external_completion(block_id, &spans, offset, new_span)
&spans, {
fake_offset,
new_span,
) {
return external_result; return external_result;
} }
} }
@ -247,7 +237,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
} }
@ -260,9 +250,7 @@ impl NuCompleter {
working_set.get_span_contents(previous_expr.0).to_vec(); working_set.get_span_contents(previous_expr.0).to_vec();
// Completion for .nu files // Completion for .nu files
if prev_expr_str == b"use" if prev_expr_str == b"use" || prev_expr_str == b"source-env"
|| prev_expr_str == b"overlay use"
|| prev_expr_str == b"source-env"
{ {
let mut completer = let mut completer =
DotNuCompletion::new(self.engine_state.clone()); DotNuCompletion::new(self.engine_state.clone());
@ -272,7 +260,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
} else if prev_expr_str == b"ls" { } else if prev_expr_str == b"ls" {
@ -284,7 +272,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
} }
@ -306,7 +294,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
} }
@ -319,7 +307,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
} }
@ -332,7 +320,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
} }
@ -351,7 +339,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix.clone(), prefix.clone(),
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
@ -362,12 +350,11 @@ impl NuCompleter {
// Try to complete using an external completer (if set) // Try to complete using an external completer (if set)
if let Some(block_id) = config.external_completer { if let Some(block_id) = config.external_completer {
if let Some(external_result) = self.external_completion( if let Some(external_result) = self.external_completion(
block_id, block_id, &spans, offset, new_span,
&spans,
fake_offset,
new_span,
) { ) {
return external_result; if !external_result.is_empty() {
return external_result;
}
} }
} }
@ -379,7 +366,7 @@ impl NuCompleter {
&working_set, &working_set,
prefix, prefix,
new_span, new_span,
fake_offset, offset,
pos, pos,
); );
@ -466,7 +453,6 @@ pub fn map_value_completions<'a>(
return Some(Suggestion { return Some(Suggestion {
value: s, value: s,
description: None, description: None,
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
@ -481,7 +467,6 @@ pub fn map_value_completions<'a>(
let mut suggestion = Suggestion { let mut suggestion = Suggestion {
value: String::from(""), // Initialize with empty string value: String::from(""), // Initialize with empty string
description: None, description: None,
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
@ -509,16 +494,6 @@ pub fn map_value_completions<'a>(
suggestion.description = Some(desc_str); suggestion.description = Some(desc_str);
} }
} }
// Match `style` column
if it.0 == "style" {
// Convert the value to string
suggestion.style = match it.1 {
Value::String { val, .. } => Some(lookup_ansi_color_style(val)),
Value::Record { .. } => Some(color_record_to_nustyle(it.1)),
_ => None,
};
}
}); });
return Some(suggestion); return Some(suggestion);

View File

@ -22,10 +22,7 @@ fn complete_rec(
Some(base) if matches(base, &entry_name, options) => { Some(base) if matches(base, &entry_name, options) => {
let partial = &partial[1..]; let partial = &partial[1..];
if !partial.is_empty() || isdir { if !partial.is_empty() || isdir {
completions.extend(complete_rec(partial, &path, options, dir, isdir)); completions.extend(complete_rec(partial, &path, options, dir, isdir))
if entry_name.eq(base) {
break;
}
} else { } else {
completions.push(path) completions.push(path)
} }

View File

@ -44,7 +44,6 @@ impl Completer for DirectoryCompletion {
.map(move |x| Suggestion { .map(move |x| Suggestion {
value: x.1, value: x.1,
description: None, description: None,
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,

View File

@ -5,7 +5,7 @@ use nu_protocol::{
}; };
use reedline::Suggestion; use reedline::Suggestion;
use std::{ use std::{
path::{is_separator, Path, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR}, path::{is_separator, MAIN_SEPARATOR as SEP, MAIN_SEPARATOR_STR},
sync::Arc, sync::Arc,
}; };
@ -91,27 +91,21 @@ impl Completer for DotNuCompletion {
// and transform them into suggestions // and transform them into suggestions
let output: Vec<Suggestion> = search_dirs let output: Vec<Suggestion> = search_dirs
.into_iter() .into_iter()
.flat_map(|search_dir| { .flat_map(|it| {
let completions = file_path_completion(span, &partial, &search_dir, options); file_path_completion(span, &partial, &it, options)
completions
.into_iter() .into_iter()
.filter(move |it| { .filter(|it| {
// Different base dir, so we list the .nu files or folders // Different base dir, so we list the .nu files or folders
if !is_current_folder { if !is_current_folder {
it.1.ends_with(".nu") || it.1.ends_with(SEP) it.1.ends_with(".nu") || it.1.ends_with(SEP)
} else { } else {
// Lib dirs, so we filter only the .nu files or directory modules // Lib dirs, so we filter only the .nu files
if it.1.ends_with(SEP) { it.1.ends_with(".nu")
Path::new(&search_dir).join(&it.1).join("mod.nu").exists()
} else {
it.1.ends_with(".nu")
}
} }
}) })
.map(move |x| Suggestion { .map(move |x| Suggestion {
value: x.1, value: x.1,
description: None, description: None,
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,

View File

@ -49,7 +49,6 @@ impl Completer for FileCompletion {
.map(move |x| Suggestion { .map(move |x| Suggestion {
value: x.1, value: x.1,
description: None, description: None,
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: x.0.start - offset, start: x.0.start - offset,

View File

@ -46,7 +46,6 @@ impl Completer for FlagCompletion {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(&named).to_string(), value: String::from_utf8_lossy(&named).to_string(),
description: Some(flag_desc.to_string()), description: Some(flag_desc.to_string()),
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,
@ -69,7 +68,6 @@ impl Completer for FlagCompletion {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(&named).to_string(), value: String::from_utf8_lossy(&named).to_string(),
description: Some(flag_desc.to_string()), description: Some(flag_desc.to_string()),
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: span.start - offset, start: span.start - offset,

View File

@ -95,7 +95,6 @@ impl Completer for VariableCompletion {
output.push(Suggestion { output.push(Suggestion {
value: env_var.0, value: env_var.0,
description: None, description: None,
style: None,
extra: None, extra: None,
span: current_span, span: current_span,
append_whitespace: false, append_whitespace: false,
@ -166,7 +165,6 @@ impl Completer for VariableCompletion {
output.push(Suggestion { output.push(Suggestion {
value: builtin.to_string(), value: builtin.to_string(),
description: None, description: None,
style: None,
extra: None, extra: None,
span: current_span, span: current_span,
append_whitespace: false, append_whitespace: false,
@ -189,7 +187,6 @@ impl Completer for VariableCompletion {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None, description: None,
style: None,
extra: None, extra: None,
span: current_span, span: current_span,
append_whitespace: false, append_whitespace: false,
@ -211,7 +208,6 @@ impl Completer for VariableCompletion {
output.push(Suggestion { output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(), value: String::from_utf8_lossy(v.0).to_string(),
description: None, description: None,
style: None,
extra: None, extra: None,
span: current_span, span: current_span,
append_whitespace: false, append_whitespace: false,
@ -243,7 +239,6 @@ fn nested_suggestions(
output.push(Suggestion { output.push(Suggestion {
value: col, value: col,
description: None, description: None,
style: None,
extra: None, extra: None,
span: current_span, span: current_span,
append_whitespace: false, append_whitespace: false,
@ -258,7 +253,6 @@ fn nested_suggestions(
output.push(Suggestion { output.push(Suggestion {
value: column_name.to_string(), value: column_name.to_string(),
description: None, description: None,
style: None,
extra: None, extra: None,
span: current_span, span: current_span,
append_whitespace: false, append_whitespace: false,
@ -272,7 +266,6 @@ fn nested_suggestions(
output.push(Suggestion { output.push(Suggestion {
value: column_name, value: column_name,
description: None, description: None,
style: None,
extra: None, extra: None,
span: current_span, span: current_span,
append_whitespace: false, append_whitespace: false,

View File

@ -35,10 +35,6 @@ pub fn evaluate_commands(
let mut working_set = StateWorkingSet::new(engine_state); let mut working_set = StateWorkingSet::new(engine_state);
let output = parse(&mut working_set, None, commands.item.as_bytes(), false); let output = parse(&mut working_set, None, commands.item.as_bytes(), false);
if let Some(warning) = working_set.parse_warnings.first() {
report_error(&working_set, warning);
}
if let Some(err) = working_set.parse_errors.first() { if let Some(err) = working_set.parse_errors.first() {
report_error(&working_set, err); report_error(&working_set, err);

View File

@ -19,7 +19,7 @@ pub use completions::{FileCompletion, NuCompleter};
pub use config_files::eval_config_contents; pub use config_files::eval_config_contents;
pub use eval_cmds::evaluate_commands; pub use eval_cmds::evaluate_commands;
pub use eval_file::evaluate_file; pub use eval_file::evaluate_file;
pub use menus::NuHelpCompleter; pub use menus::{DescriptionMenu, NuHelpCompleter};
pub use nu_cmd_base::util::get_init_cwd; pub use nu_cmd_base::util::get_init_cwd;
pub use nu_highlight::NuHighlight; pub use nu_highlight::NuHighlight;
pub use print::Print; pub use print::Print;

View File

@ -0,0 +1,730 @@
use {
nu_ansi_term::{ansi::RESET, Style},
reedline::{
menu_functions::string_difference, Completer, Editor, Menu, MenuEvent, MenuTextStyle,
Painter, Suggestion, UndoBehavior,
},
};
/// Default values used as reference for the menu. These values are set during
/// the initial declaration of the menu and are always kept as reference for the
/// changeable [`WorkingDetails`]
struct DefaultMenuDetails {
/// Number of columns that the menu will have
pub columns: u16,
/// Column width
pub col_width: Option<usize>,
/// Column padding
pub col_padding: usize,
/// Number of rows for commands
pub selection_rows: u16,
/// Number of rows allowed to display the description
pub description_rows: usize,
}
impl Default for DefaultMenuDetails {
fn default() -> Self {
Self {
columns: 4,
col_width: None,
col_padding: 2,
selection_rows: 4,
description_rows: 10,
}
}
}
/// Represents the actual column conditions of the menu. These conditions change
/// since they need to accommodate possible different line sizes for the column values
#[derive(Default)]
struct WorkingDetails {
/// Number of columns that the menu will have
pub columns: u16,
/// Column width
pub col_width: usize,
/// Number of rows for description
pub description_rows: usize,
}
/// Completion menu definition
pub struct DescriptionMenu {
/// Menu name
name: String,
/// Menu status
active: bool,
/// Menu coloring
color: MenuTextStyle,
/// Default column details that are set when creating the menu
/// These values are the reference for the working details
default_details: DefaultMenuDetails,
/// Number of minimum rows that are displayed when
/// the required lines is larger than the available lines
min_rows: u16,
/// Working column details keep changing based on the collected values
working_details: WorkingDetails,
/// Menu cached values
values: Vec<Suggestion>,
/// column position of the cursor. Starts from 0
col_pos: u16,
/// row position in the menu. Starts from 0
row_pos: u16,
/// Menu marker when active
marker: String,
/// Event sent to the menu
event: Option<MenuEvent>,
/// String collected after the menu is activated
input: Option<String>,
/// Examples to select
examples: Vec<String>,
/// Example index
example_index: Option<usize>,
/// Examples may not be shown if there is not enough space in the screen
show_examples: bool,
/// Skipped description rows
skipped_rows: usize,
/// Calls the completer using only the line buffer difference difference
/// after the menu was activated
only_buffer_difference: bool,
}
impl Default for DescriptionMenu {
fn default() -> Self {
Self {
name: "description_menu".to_string(),
active: false,
color: MenuTextStyle::default(),
default_details: DefaultMenuDetails::default(),
min_rows: 3,
working_details: WorkingDetails::default(),
values: Vec::new(),
col_pos: 0,
row_pos: 0,
marker: "? ".to_string(),
event: None,
input: None,
examples: Vec::new(),
example_index: None,
show_examples: true,
skipped_rows: 0,
only_buffer_difference: true,
}
}
}
// Menu configuration
impl DescriptionMenu {
/// Menu builder with new name
pub fn with_name(mut self, name: &str) -> Self {
self.name = name.into();
self
}
/// Menu builder with new value for text style
pub fn with_text_style(mut self, text_style: Style) -> Self {
self.color.text_style = text_style;
self
}
/// Menu builder with new value for text style
pub fn with_selected_text_style(mut self, selected_text_style: Style) -> Self {
self.color.selected_text_style = selected_text_style;
self
}
/// Menu builder with new value for text style
pub fn with_description_text_style(mut self, description_text_style: Style) -> Self {
self.color.description_style = description_text_style;
self
}
/// Menu builder with new columns value
pub fn with_columns(mut self, columns: u16) -> Self {
self.default_details.columns = columns;
self
}
/// Menu builder with new column width value
pub fn with_column_width(mut self, col_width: Option<usize>) -> Self {
self.default_details.col_width = col_width;
self
}
/// Menu builder with new column width value
pub fn with_column_padding(mut self, col_padding: usize) -> Self {
self.default_details.col_padding = col_padding;
self
}
/// Menu builder with new selection rows value
pub fn with_selection_rows(mut self, selection_rows: u16) -> Self {
self.default_details.selection_rows = selection_rows;
self
}
/// Menu builder with new description rows value
pub fn with_description_rows(mut self, description_rows: usize) -> Self {
self.default_details.description_rows = description_rows;
self
}
/// Menu builder with marker
pub fn with_marker(mut self, marker: String) -> Self {
self.marker = marker;
self
}
/// Menu builder with new only buffer difference
pub fn with_only_buffer_difference(mut self, only_buffer_difference: bool) -> Self {
self.only_buffer_difference = only_buffer_difference;
self
}
}
// Menu functionality
impl DescriptionMenu {
/// Move menu cursor to the next element
fn move_next(&mut self) {
let mut new_col = self.col_pos + 1;
let mut new_row = self.row_pos;
if new_col >= self.get_cols() {
new_row += 1;
new_col = 0;
}
if new_row >= self.get_rows() {
new_row = 0;
new_col = 0;
}
let position = new_row * self.get_cols() + new_col;
if position >= self.get_values().len() as u16 {
self.reset_position();
} else {
self.col_pos = new_col;
self.row_pos = new_row;
}
}
/// Move menu cursor to the previous element
fn move_previous(&mut self) {
let new_col = self.col_pos.checked_sub(1);
let (new_col, new_row) = match new_col {
Some(col) => (col, self.row_pos),
None => match self.row_pos.checked_sub(1) {
Some(row) => (self.get_cols().saturating_sub(1), row),
None => (
self.get_cols().saturating_sub(1),
self.get_rows().saturating_sub(1),
),
},
};
let position = new_row * self.get_cols() + new_col;
if position >= self.get_values().len() as u16 {
self.col_pos = (self.get_values().len() as u16 % self.get_cols()).saturating_sub(1);
self.row_pos = self.get_rows().saturating_sub(1);
} else {
self.col_pos = new_col;
self.row_pos = new_row;
}
}
/// Menu index based on column and row position
fn index(&self) -> usize {
let index = self.row_pos * self.get_cols() + self.col_pos;
index as usize
}
/// Get selected value from the menu
fn get_value(&self) -> Option<Suggestion> {
self.get_values().get(self.index()).cloned()
}
/// Calculates how many rows the Menu will use
fn get_rows(&self) -> u16 {
let values = self.get_values().len() as u16;
if values == 0 {
// When the values are empty the no_records_msg is shown, taking 1 line
return 1;
}
let rows = values / self.get_cols();
if values % self.get_cols() != 0 {
rows + 1
} else {
rows
}
}
/// Returns working details col width
fn get_width(&self) -> usize {
self.working_details.col_width
}
/// Reset menu position
fn reset_position(&mut self) {
self.col_pos = 0;
self.row_pos = 0;
self.skipped_rows = 0;
}
fn no_records_msg(&self, use_ansi_coloring: bool) -> String {
let msg = "TYPE TO START SEARCH";
if use_ansi_coloring {
format!(
"{}{}{}",
self.color.selected_text_style.prefix(),
msg,
RESET
)
} else {
msg.to_string()
}
}
/// Returns working details columns
fn get_cols(&self) -> u16 {
self.working_details.columns.max(1)
}
/// End of line for menu
fn end_of_line(&self, column: u16, index: usize) -> &str {
let is_last = index == self.values.len().saturating_sub(1);
if column == self.get_cols().saturating_sub(1) || is_last {
"\r\n"
} else {
""
}
}
/// Update list of examples from the actual value
fn update_examples(&mut self) {
self.examples = self
.get_value()
.and_then(|suggestion| suggestion.extra)
.unwrap_or_default();
self.example_index = None;
}
/// Creates default string that represents one suggestion from the menu
fn create_entry_string(
&self,
suggestion: &Suggestion,
index: usize,
column: u16,
empty_space: usize,
use_ansi_coloring: bool,
) -> String {
if use_ansi_coloring {
if index == self.index() {
format!(
"{}{}{}{:>empty$}{}",
self.color.selected_text_style.prefix(),
&suggestion.value,
RESET,
"",
self.end_of_line(column, index),
empty = empty_space,
)
} else {
format!(
"{}{}{}{:>empty$}{}",
self.color.text_style.prefix(),
&suggestion.value,
RESET,
"",
self.end_of_line(column, index),
empty = empty_space,
)
}
} else {
// If no ansi coloring is found, then the selection word is
// the line in uppercase
let (marker, empty_space) = if index == self.index() {
(">", empty_space.saturating_sub(1))
} else {
("", empty_space)
};
let line = format!(
"{}{}{:>empty$}{}",
marker,
&suggestion.value,
"",
self.end_of_line(column, index),
empty = empty_space,
);
if index == self.index() {
line.to_uppercase()
} else {
line
}
}
}
/// Description string with color
fn create_description_string(&self, use_ansi_coloring: bool) -> String {
let description = self
.get_value()
.and_then(|suggestion| suggestion.description)
.unwrap_or_default()
.lines()
.skip(self.skipped_rows)
.take(self.working_details.description_rows)
.collect::<Vec<&str>>()
.join("\r\n");
if use_ansi_coloring && !description.is_empty() {
format!(
"{}{}{}",
self.color.description_style.prefix(),
description,
RESET,
)
} else {
description
}
}
/// Selectable list of examples from the actual value
fn create_example_string(&self, use_ansi_coloring: bool) -> String {
if !self.show_examples {
return "".into();
}
let examples: String = self
.examples
.iter()
.enumerate()
.map(|(index, example)| {
if let Some(example_index) = self.example_index {
if index == example_index {
format!(
" {}{}{}\r\n",
self.color.selected_text_style.prefix(),
example,
RESET
)
} else {
format!(" {example}\r\n")
}
} else {
format!(" {example}\r\n")
}
})
.collect();
if examples.is_empty() {
"".into()
} else if use_ansi_coloring {
format!(
"{}\r\n\r\nExamples:\r\n{}{}",
self.color.description_style.prefix(),
RESET,
examples,
)
} else {
format!("\r\n\r\nExamples:\r\n{examples}",)
}
}
}
impl Menu for DescriptionMenu {
/// Menu name
fn name(&self) -> &str {
self.name.as_str()
}
/// Menu indicator
fn indicator(&self) -> &str {
self.marker.as_str()
}
/// Deactivates context menu
fn is_active(&self) -> bool {
self.active
}
/// The menu stays active even with one record
fn can_quick_complete(&self) -> bool {
false
}
/// The menu does not need to partially complete
fn can_partially_complete(
&mut self,
_values_updated: bool,
_editor: &mut Editor,
_completer: &mut dyn Completer,
) -> bool {
false
}
/// Selects what type of event happened with the menu
fn menu_event(&mut self, event: MenuEvent) {
match &event {
MenuEvent::Activate(_) => self.active = true,
MenuEvent::Deactivate => {
self.active = false;
self.input = None;
self.values = Vec::new();
}
_ => {}
};
self.event = Some(event);
}
/// Updates menu values
fn update_values(&mut self, editor: &mut Editor, completer: &mut dyn Completer) {
if self.only_buffer_difference {
if let Some(old_string) = &self.input {
let (start, input) = string_difference(editor.get_buffer(), old_string);
if !input.is_empty() {
self.reset_position();
self.values = completer.complete(input, start);
}
}
} else {
let trimmed_buffer = editor.get_buffer().replace('\n', " ");
self.values = completer.complete(
trimmed_buffer.as_str(),
editor.line_buffer().insertion_point(),
);
self.reset_position();
}
}
/// The working details for the menu changes based on the size of the lines
/// collected from the completer
fn update_working_details(
&mut self,
editor: &mut Editor,
completer: &mut dyn Completer,
painter: &Painter,
) {
if let Some(event) = self.event.take() {
// Updating all working parameters from the menu before executing any of the
// possible event
let max_width = self.get_values().iter().fold(0, |acc, suggestion| {
let str_len = suggestion.value.len() + self.default_details.col_padding;
if str_len > acc {
str_len
} else {
acc
}
});
// If no default width is found, then the total screen width is used to estimate
// the column width based on the default number of columns
let default_width = if let Some(col_width) = self.default_details.col_width {
col_width
} else {
let col_width = painter.screen_width() / self.default_details.columns;
col_width as usize
};
// Adjusting the working width of the column based the max line width found
// in the menu values
if max_width > default_width {
self.working_details.col_width = max_width;
} else {
self.working_details.col_width = default_width;
};
// The working columns is adjusted based on possible number of columns
// that could be fitted in the screen with the calculated column width
let possible_cols = painter.screen_width() / self.working_details.col_width as u16;
if possible_cols > self.default_details.columns {
self.working_details.columns = self.default_details.columns.max(1);
} else {
self.working_details.columns = possible_cols;
}
// Updating the working rows to display the description
if self.menu_required_lines(painter.screen_width()) <= painter.remaining_lines() {
self.working_details.description_rows = self.default_details.description_rows;
self.show_examples = true;
} else {
self.working_details.description_rows = painter
.remaining_lines()
.saturating_sub(self.default_details.selection_rows + 1)
as usize;
self.show_examples = false;
}
match event {
MenuEvent::Activate(_) => {
self.reset_position();
self.input = Some(editor.get_buffer().to_string());
self.update_values(editor, completer);
}
MenuEvent::Deactivate => self.active = false,
MenuEvent::Edit(_) => {
self.reset_position();
self.update_values(editor, completer);
self.update_examples()
}
MenuEvent::NextElement => {
self.skipped_rows = 0;
self.move_next();
self.update_examples();
}
MenuEvent::PreviousElement => {
self.skipped_rows = 0;
self.move_previous();
self.update_examples();
}
MenuEvent::MoveUp => {
if let Some(example_index) = self.example_index {
if let Some(index) = example_index.checked_sub(1) {
self.example_index = Some(index);
} else {
self.example_index = Some(self.examples.len().saturating_sub(1));
}
} else if !self.examples.is_empty() {
self.example_index = Some(0);
}
}
MenuEvent::MoveDown => {
if let Some(example_index) = self.example_index {
let index = example_index + 1;
if index < self.examples.len() {
self.example_index = Some(index);
} else {
self.example_index = Some(0);
}
} else if !self.examples.is_empty() {
self.example_index = Some(0);
}
}
MenuEvent::MoveLeft => self.skipped_rows = self.skipped_rows.saturating_sub(1),
MenuEvent::MoveRight => {
let skipped = self.skipped_rows + 1;
let description_rows = self
.get_value()
.and_then(|suggestion| suggestion.description)
.unwrap_or_default()
.lines()
.count();
let allowed_skips =
description_rows.saturating_sub(self.working_details.description_rows);
if skipped < allowed_skips {
self.skipped_rows = skipped;
} else {
self.skipped_rows = allowed_skips;
}
}
MenuEvent::PreviousPage | MenuEvent::NextPage => {}
}
}
}
/// The buffer gets replaced in the Span location
fn replace_in_buffer(&self, editor: &mut Editor) {
if let Some(Suggestion { value, span, .. }) = self.get_value() {
let start = span.start.min(editor.line_buffer().len());
let end = span.end.min(editor.line_buffer().len());
let replacement = if let Some(example_index) = self.example_index {
self.examples
.get(example_index)
.expect("the example index is always checked")
} else {
&value
};
editor.edit_buffer(
|lb| {
lb.replace_range(start..end, replacement);
let mut offset = lb.insertion_point();
offset += lb
.len()
.saturating_sub(end.saturating_sub(start))
.saturating_sub(start);
lb.set_insertion_point(offset);
},
UndoBehavior::CreateUndoPoint,
);
}
}
/// Minimum rows that should be displayed by the menu
fn min_rows(&self) -> u16 {
self.get_rows().min(self.min_rows)
}
/// Gets values from filler that will be displayed in the menu
fn get_values(&self) -> &[Suggestion] {
&self.values
}
fn menu_required_lines(&self, _terminal_columns: u16) -> u16 {
let example_lines = self
.examples
.iter()
.fold(0, |acc, example| example.lines().count() + acc);
self.default_details.selection_rows
+ self.default_details.description_rows as u16
+ example_lines as u16
+ 3
}
fn menu_string(&self, _available_lines: u16, use_ansi_coloring: bool) -> String {
if self.get_values().is_empty() {
self.no_records_msg(use_ansi_coloring)
} else {
// The skip values represent the number of lines that should be skipped
// while printing the menu
let available_lines = self.default_details.selection_rows;
let skip_values = if self.row_pos >= available_lines {
let skip_lines = self.row_pos.saturating_sub(available_lines) + 1;
(skip_lines * self.get_cols()) as usize
} else {
0
};
// It seems that crossterm prefers to have a complete string ready to be printed
// rather than looping through the values and printing multiple things
// This reduces the flickering when printing the menu
let available_values = (available_lines * self.get_cols()) as usize;
let selection_values: String = self
.get_values()
.iter()
.skip(skip_values)
.take(available_values)
.enumerate()
.map(|(index, suggestion)| {
// Correcting the enumerate index based on the number of skipped values
let index = index + skip_values;
let column = index as u16 % self.get_cols();
let empty_space = self.get_width().saturating_sub(suggestion.value.len());
self.create_entry_string(
suggestion,
index,
column,
empty_space,
use_ansi_coloring,
)
})
.collect();
format!(
"{}{}{}",
selection_values,
self.create_description_string(use_ansi_coloring),
self.create_example_string(use_ansi_coloring)
)
}
}
}

View File

@ -102,11 +102,10 @@ impl NuHelpCompleter {
Suggestion { Suggestion {
value: sig.name.clone(), value: sig.name.clone(),
description: Some(long_desc), description: Some(long_desc),
style: None,
extra: Some(extra), extra: Some(extra),
span: reedline::Span { span: reedline::Span {
start: pos - line.len(), start: pos,
end: pos, end: pos + line.len(),
}, },
append_whitespace: false, append_whitespace: false,
} }
@ -120,42 +119,3 @@ impl Completer for NuHelpCompleter {
self.completion_helper(line, pos) self.completion_helper(line, pos)
} }
} }
#[cfg(test)]
mod test {
use super::*;
use rstest::rstest;
#[rstest]
#[case("who", 5, 8, &["whoami"])]
#[case("hash", 1, 5, &["hash", "hash md5", "hash sha256"])]
#[case("into f", 0, 6, &["into float", "into filesize"])]
#[case("into nonexistent", 0, 16, &[])]
fn test_help_completer(
#[case] line: &str,
#[case] start: usize,
#[case] end: usize,
#[case] expected: &[&str],
) {
let engine_state =
nu_command::add_shell_command_context(nu_cmd_lang::create_default_context());
let mut completer = NuHelpCompleter::new(engine_state.into());
let suggestions = completer.complete(line, end);
assert_eq!(
expected.len(),
suggestions.len(),
"expected {:?}, got {:?}",
expected,
suggestions
.iter()
.map(|s| s.value.clone())
.collect::<Vec<_>>()
);
for (exp, actual) in expected.iter().zip(suggestions) {
assert_eq!(exp, &actual.value);
assert_eq!(reedline::Span::new(start, end), actual.span);
}
}
}

View File

@ -101,13 +101,9 @@ fn convert_to_suggestions(
} }
} }
_ => reedline::Span { _ => reedline::Span {
start: if only_buffer_difference { start: if only_buffer_difference { pos } else { 0 },
pos - line.len()
} else {
0
},
end: if only_buffer_difference { end: if only_buffer_difference {
pos pos + line.len()
} else { } else {
line.len() line.len()
}, },
@ -115,13 +111,9 @@ fn convert_to_suggestions(
} }
} }
_ => reedline::Span { _ => reedline::Span {
start: if only_buffer_difference { start: if only_buffer_difference { pos } else { 0 },
pos - line.len()
} else {
0
},
end: if only_buffer_difference { end: if only_buffer_difference {
pos pos + line.len()
} else { } else {
line.len() line.len()
}, },
@ -146,7 +138,6 @@ fn convert_to_suggestions(
vec![Suggestion { vec![Suggestion {
value: text, value: text,
description, description,
style: None,
extra, extra,
span, span,
append_whitespace: false, append_whitespace: false,
@ -159,19 +150,10 @@ fn convert_to_suggestions(
_ => vec![Suggestion { _ => vec![Suggestion {
value: format!("Not a record: {value:?}"), value: format!("Not a record: {value:?}"),
description: None, description: None,
style: None,
extra: None, extra: None,
span: reedline::Span { span: reedline::Span {
start: if only_buffer_difference { start: 0,
pos - line.len() end: line.len(),
} else {
0
},
end: if only_buffer_difference {
pos
} else {
line.len()
},
}, },
append_whitespace: false, append_whitespace: false,
}], }],

View File

@ -1,5 +1,7 @@
mod description_menu;
mod help_completions; mod help_completions;
mod menu_completions; mod menu_completions;
pub use description_menu::DescriptionMenu;
pub use help_completions::NuHelpCompleter; pub use help_completions::NuHelpCompleter;
pub use menu_completions::NuMenuCompleter; pub use menu_completions::NuMenuCompleter;

View File

@ -28,7 +28,7 @@ impl Command for NuHighlight {
fn run( fn run(
&self, &self,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
@ -40,7 +40,6 @@ impl Command for NuHighlight {
let highlighter = crate::NuHighlighter { let highlighter = crate::NuHighlighter {
engine_state, engine_state,
stack: std::sync::Arc::new(stack.clone()),
config, config,
}; };

View File

@ -54,8 +54,8 @@ Since this command has no output, there is no point in piping it with other comm
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let args: Vec<Value> = call.rest(engine_state, stack, 0)?; let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
let no_newline = call.has_flag(engine_state, stack, "no-newline")?; let no_newline = call.has_flag("no-newline");
let to_stderr = call.has_flag(engine_state, stack, "stderr")?; let to_stderr = call.has_flag("stderr");
// This will allow for easy printing of pipelines as well // This will allow for easy printing of pipelines as well
if !args.is_empty() { if !args.is_empty() {

View File

@ -1,4 +1,3 @@
use crate::prompt_update::{POST_PROMPT_MARKER, PRE_PROMPT_MARKER};
#[cfg(windows)] #[cfg(windows)]
use nu_utils::enable_vt_processing; use nu_utils::enable_vt_processing;
use reedline::DefaultPrompt; use reedline::DefaultPrompt;
@ -12,7 +11,6 @@ use {
/// Nushell prompt definition /// Nushell prompt definition
#[derive(Clone)] #[derive(Clone)]
pub struct NushellPrompt { pub struct NushellPrompt {
shell_integration: bool,
left_prompt_string: Option<String>, left_prompt_string: Option<String>,
right_prompt_string: Option<String>, right_prompt_string: Option<String>,
default_prompt_indicator: Option<String>, default_prompt_indicator: Option<String>,
@ -22,10 +20,15 @@ pub struct NushellPrompt {
render_right_prompt_on_last_line: bool, render_right_prompt_on_last_line: bool,
} }
impl Default for NushellPrompt {
fn default() -> Self {
NushellPrompt::new()
}
}
impl NushellPrompt { impl NushellPrompt {
pub fn new(shell_integration: bool) -> NushellPrompt { pub fn new() -> NushellPrompt {
NushellPrompt { NushellPrompt {
shell_integration,
left_prompt_string: None, left_prompt_string: None,
right_prompt_string: None, right_prompt_string: None,
default_prompt_indicator: None, default_prompt_indicator: None,
@ -108,11 +111,7 @@ impl Prompt for NushellPrompt {
.to_string() .to_string()
.replace('\n', "\r\n"); .replace('\n', "\r\n");
if self.shell_integration { prompt.into()
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
} else {
prompt.into()
}
} }
} }

View File

@ -7,6 +7,8 @@ use nu_protocol::{
Config, PipelineData, Value, Config, PipelineData, Value,
}; };
use reedline::Prompt; use reedline::Prompt;
use std::borrow::Cow;
use std::sync::Arc;
// Name of environment variable where the prompt could be stored // Name of environment variable where the prompt could be stored
pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
@ -26,8 +28,8 @@ pub(crate) const TRANSIENT_PROMPT_MULTILINE_INDICATOR: &str =
"TRANSIENT_PROMPT_MULTILINE_INDICATOR"; "TRANSIENT_PROMPT_MULTILINE_INDICATOR";
// According to Daniel Imms @Tyriar, we need to do these this way: // According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output> // <133 A><prompt><133 B><command><133 C><command output>
pub(crate) const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\"; const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
pub(crate) const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\"; const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
fn get_prompt_string( fn get_prompt_string(
prompt: &str, prompt: &str,
@ -96,12 +98,12 @@ fn get_prompt_string(
}) })
} }
pub(crate) fn update_prompt( pub(crate) fn update_prompt<'prompt>(
config: &Config, config: &Config,
engine_state: &EngineState, engine_state: &EngineState,
stack: &Stack, stack: &Stack,
nu_prompt: &mut NushellPrompt, nu_prompt: &'prompt mut NushellPrompt,
) { ) -> &'prompt dyn Prompt {
let mut stack = stack.clone(); let mut stack = stack.clone();
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack); let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
@ -144,55 +146,125 @@ pub(crate) fn update_prompt(
(prompt_vi_insert_string, prompt_vi_normal_string), (prompt_vi_insert_string, prompt_vi_normal_string),
config.render_right_prompt_on_last_line, config.render_right_prompt_on_last_line,
); );
let ret_val = nu_prompt as &dyn Prompt;
trace!("update_prompt {}:{}:{}", file!(), line!(), column!()); trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
ret_val
} }
/// Construct the transient prompt based on the normal nu_prompt struct TransientPrompt {
pub(crate) fn make_transient_prompt( engine_state: Arc<EngineState>,
stack: Stack,
}
/// Try getting `$env.TRANSIENT_PROMPT_<X>`, and get `$env.PROMPT_<X>` if that fails
fn get_transient_prompt_string(
transient_prompt: &str,
prompt: &str,
config: &Config, config: &Config,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &mut Stack,
nu_prompt: &NushellPrompt, ) -> Option<String> {
) -> Box<dyn Prompt> { get_prompt_string(transient_prompt, config, engine_state, stack)
let mut nu_prompt = nu_prompt.clone(); .or_else(|| get_prompt_string(prompt, config, engine_state, stack))
}
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_COMMAND, config, engine_state, stack) {
nu_prompt.update_prompt_left(Some(s)) impl Prompt for TransientPrompt {
} fn render_prompt_left(&self) -> Cow<str> {
let mut nu_prompt = NushellPrompt::new();
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_COMMAND_RIGHT, config, engine_state, stack) let config = &self.engine_state.get_config().clone();
{ let mut stack = self.stack.clone();
nu_prompt.update_prompt_right(Some(s), config.render_right_prompt_on_last_line) nu_prompt.update_prompt_left(get_transient_prompt_string(
} TRANSIENT_PROMPT_COMMAND,
PROMPT_COMMAND,
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_INDICATOR, config, engine_state, stack) { config,
nu_prompt.update_prompt_indicator(Some(s)) &self.engine_state,
} &mut stack,
if let Some(s) = get_prompt_string( ));
TRANSIENT_PROMPT_INDICATOR_VI_INSERT, nu_prompt.render_prompt_left().to_string().into()
config, }
engine_state,
stack, fn render_prompt_right(&self) -> Cow<str> {
) { let mut nu_prompt = NushellPrompt::new();
nu_prompt.update_prompt_vi_insert(Some(s)) let config = &self.engine_state.get_config().clone();
} let mut stack = self.stack.clone();
if let Some(s) = get_prompt_string( nu_prompt.update_prompt_right(
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL, get_transient_prompt_string(
config, TRANSIENT_PROMPT_COMMAND_RIGHT,
engine_state, PROMPT_COMMAND_RIGHT,
stack, config,
) { &self.engine_state,
nu_prompt.update_prompt_vi_normal(Some(s)) &mut stack,
} ),
config.render_right_prompt_on_last_line,
if let Some(s) = get_prompt_string( );
TRANSIENT_PROMPT_MULTILINE_INDICATOR, nu_prompt.render_prompt_right().to_string().into()
config, }
engine_state,
stack, fn render_prompt_indicator(&self, prompt_mode: reedline::PromptEditMode) -> Cow<str> {
) { let mut nu_prompt = NushellPrompt::new();
nu_prompt.update_prompt_multiline(Some(s)) let config = &self.engine_state.get_config().clone();
} let mut stack = self.stack.clone();
nu_prompt.update_prompt_indicator(get_transient_prompt_string(
Box::new(nu_prompt) TRANSIENT_PROMPT_INDICATOR,
PROMPT_INDICATOR,
config,
&self.engine_state,
&mut stack,
));
nu_prompt.update_prompt_vi_insert(get_transient_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_INSERT,
PROMPT_INDICATOR_VI_INSERT,
config,
&self.engine_state,
&mut stack,
));
nu_prompt.update_prompt_vi_normal(get_transient_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL,
PROMPT_INDICATOR_VI_NORMAL,
config,
&self.engine_state,
&mut stack,
));
nu_prompt
.render_prompt_indicator(prompt_mode)
.to_string()
.into()
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
let mut nu_prompt = NushellPrompt::new();
let config = &self.engine_state.get_config().clone();
let mut stack = self.stack.clone();
nu_prompt.update_prompt_multiline(get_transient_prompt_string(
TRANSIENT_PROMPT_MULTILINE_INDICATOR,
PROMPT_MULTILINE_INDICATOR,
config,
&self.engine_state,
&mut stack,
));
nu_prompt
.render_prompt_multiline_indicator()
.to_string()
.into()
}
fn render_prompt_history_search_indicator(
&self,
history_search: reedline::PromptHistorySearch,
) -> Cow<str> {
NushellPrompt::new()
.render_prompt_history_search_indicator(history_search)
.to_string()
.into()
}
}
/// Construct the transient prompt
pub(crate) fn transient_prompt(engine_state: Arc<EngineState>, stack: &Stack) -> Box<dyn Prompt> {
Box::new(TransientPrompt {
engine_state,
stack: stack.clone(),
})
} }

View File

@ -1,3 +1,4 @@
use super::DescriptionMenu;
use crate::{menus::NuMenuCompleter, NuHelpCompleter}; use crate::{menus::NuMenuCompleter, NuHelpCompleter};
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{KeyCode, KeyModifiers};
use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style};
@ -11,8 +12,7 @@ use nu_protocol::{
}; };
use reedline::{ use reedline::{
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
ColumnarMenu, DescriptionMenu, DescriptionMode, EditCommand, IdeMenu, Keybindings, ListMenu, ColumnarMenu, EditCommand, Keybindings, ListMenu, Reedline, ReedlineEvent, ReedlineMenu,
MenuBuilder, Reedline, ReedlineEvent, ReedlineMenu,
}; };
use std::sync::Arc; use std::sync::Arc;
@ -138,10 +138,9 @@ fn add_menu(
match layout.as_str() { match layout.as_str() {
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config), "columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
"list" => add_list_menu(line_editor, menu, engine_state, stack, config), "list" => add_list_menu(line_editor, menu, engine_state, stack, config),
"ide" => add_ide_menu(line_editor, menu, engine_state, stack, config),
"description" => add_description_menu(line_editor, menu, engine_state, stack, config), "description" => add_description_menu(line_editor, menu, engine_state, stack, config),
_ => Err(ShellError::UnsupportedConfigValue { _ => Err(ShellError::UnsupportedConfigValue {
expected: "columnar, list, ide or description".to_string(), expected: "columnar, list or description".to_string(),
value: menu.menu_type.into_abbreviated_string(config), value: menu.menu_type.into_abbreviated_string(config),
span: menu.menu_type.span(), span: menu.menu_type.span(),
}), }),
@ -236,26 +235,10 @@ pub(crate) fn add_columnar_menu(
columnar_menu, columnar_menu,
ColumnarMenu::with_description_text_style ColumnarMenu::with_description_text_style
); );
add_style!(
"match_text",
val,
span,
config,
columnar_menu,
ColumnarMenu::with_match_text_style
);
add_style!(
"selected_match_text",
val,
span,
config,
columnar_menu,
ColumnarMenu::with_selected_match_text_style
);
} }
let marker = menu.marker.into_string("", config); let marker = menu.marker.into_string("", config);
columnar_menu = columnar_menu.with_marker(&marker); columnar_menu = columnar_menu.with_marker(marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?; let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference); columnar_menu = columnar_menu.with_only_buffer_difference(only_buffer_difference);
@ -337,7 +320,7 @@ pub(crate) fn add_list_menu(
} }
let marker = menu.marker.into_string("", config); let marker = menu.marker.into_string("", config);
list_menu = list_menu.with_marker(&marker); list_menu = list_menu.with_marker(marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?; let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
list_menu = list_menu.with_only_buffer_difference(only_buffer_difference); list_menu = list_menu.with_only_buffer_difference(only_buffer_difference);
@ -368,232 +351,6 @@ pub(crate) fn add_list_menu(
} }
} }
// Adds an IDE menu to the line editor
pub(crate) fn add_ide_menu(
line_editor: Reedline,
menu: &ParsedMenu,
engine_state: Arc<EngineState>,
stack: &Stack,
config: &Config,
) -> Result<Reedline, ShellError> {
let span = menu.menu_type.span();
let name = menu.name.into_string("", config);
let mut ide_menu = IdeMenu::default().with_name(&name);
if let Value::Record { val, .. } = &menu.menu_type {
ide_menu = match extract_value("min_completion_width", val, span) {
Ok(min_completion_width) => {
let min_completion_width = min_completion_width.as_int()?;
ide_menu.with_min_completion_width(min_completion_width as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("max_completion_width", val, span) {
Ok(max_completion_width) => {
let max_completion_width = max_completion_width.as_int()?;
ide_menu.with_max_completion_width(max_completion_width as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("max_completion_height", val, span) {
Ok(max_completion_height) => {
let max_completion_height = max_completion_height.as_int()?;
ide_menu.with_max_completion_height(max_completion_height as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("padding", val, span) {
Ok(padding) => {
let padding = padding.as_int()?;
ide_menu.with_padding(padding as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("border", val, span) {
Ok(border) => {
if let Ok(border) = border.as_bool() {
if border {
ide_menu.with_default_border()
} else {
ide_menu
}
} else if let Ok(border_chars) = border.as_record() {
let top_right = extract_value("top_right", border_chars, span)?.as_char()?;
let top_left = extract_value("top_left", border_chars, span)?.as_char()?;
let bottom_right =
extract_value("bottom_right", border_chars, span)?.as_char()?;
let bottom_left =
extract_value("bottom_left", border_chars, span)?.as_char()?;
let horizontal = extract_value("horizontal", border_chars, span)?.as_char()?;
let vertical = extract_value("vertical", border_chars, span)?.as_char()?;
ide_menu.with_border(
top_right,
top_left,
bottom_right,
bottom_left,
horizontal,
vertical,
)
} else {
return Err(ShellError::UnsupportedConfigValue {
expected: "bool or record".to_string(),
value: border.into_abbreviated_string(config),
span: border.span(),
});
}
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("cursor_offset", val, span) {
Ok(cursor_offset) => {
let cursor_offset = cursor_offset.as_int()?;
ide_menu.with_cursor_offset(cursor_offset as i16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("description_mode", val, span) {
Ok(description_mode) => {
let description_mode_str = description_mode.as_string()?;
match description_mode_str.as_str() {
"left" => ide_menu.with_description_mode(DescriptionMode::Left),
"right" => ide_menu.with_description_mode(DescriptionMode::Right),
"prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight),
_ => {
return Err(ShellError::UnsupportedConfigValue {
expected: "\"left\", \"right\" or \"prefer_right\"".to_string(),
value: description_mode.into_abbreviated_string(config),
span: description_mode.span(),
});
}
}
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("min_description_width", val, span) {
Ok(min_description_width) => {
let min_description_width = min_description_width.as_int()?;
ide_menu.with_min_description_width(min_description_width as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("max_description_width", val, span) {
Ok(max_description_width) => {
let max_description_width = max_description_width.as_int()?;
ide_menu.with_max_description_width(max_description_width as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("max_description_height", val, span) {
Ok(max_description_height) => {
let max_description_height = max_description_height.as_int()?;
ide_menu.with_max_description_height(max_description_height as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("description_offset", val, span) {
Ok(description_padding) => {
let description_padding = description_padding.as_int()?;
ide_menu.with_description_offset(description_padding as u16)
}
Err(_) => ide_menu,
};
ide_menu = match extract_value("correct_cursor_pos", val, span) {
Ok(correct_cursor_pos) => {
let correct_cursor_pos = correct_cursor_pos.as_bool()?;
ide_menu.with_correct_cursor_pos(correct_cursor_pos)
}
Err(_) => ide_menu,
};
}
let span = menu.style.span();
if let Value::Record { val, .. } = &menu.style {
add_style!(
"text",
val,
span,
config,
ide_menu,
IdeMenu::with_text_style
);
add_style!(
"selected_text",
val,
span,
config,
ide_menu,
IdeMenu::with_selected_text_style
);
add_style!(
"description_text",
val,
span,
config,
ide_menu,
IdeMenu::with_description_text_style
);
add_style!(
"match_text",
val,
span,
config,
ide_menu,
IdeMenu::with_match_text_style
);
add_style!(
"selected_match_text",
val,
span,
config,
ide_menu,
IdeMenu::with_selected_match_text_style
);
}
let marker = menu.marker.into_string("", config);
ide_menu = ide_menu.with_marker(&marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference);
let span = menu.source.span();
match &menu.source {
Value::Nothing { .. } => {
Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu))))
}
Value::Closure { val, .. } => {
let menu_completer = NuMenuCompleter::new(
val.block_id,
span,
stack.captures_to_stack(val.captures.clone()),
engine_state,
only_buffer_difference,
);
Ok(line_editor.with_menu(ReedlineMenu::WithCompleter {
menu: Box::new(ide_menu),
completer: Box::new(menu_completer),
}))
}
_ => Err(ShellError::UnsupportedConfigValue {
expected: "block or omitted value".to_string(),
value: menu.source.into_abbreviated_string(config),
span,
}),
}
}
// Adds a description menu to the line editor // Adds a description menu to the line editor
pub(crate) fn add_description_menu( pub(crate) fn add_description_menu(
line_editor: Reedline, line_editor: Reedline,
@ -677,7 +434,7 @@ pub(crate) fn add_description_menu(
} }
let marker = menu.marker.into_string("", config); let marker = menu.marker.into_string("", config);
description_menu = description_menu.with_marker(&marker); description_menu = description_menu.with_marker(marker);
let only_buffer_difference = menu.only_buffer_difference.as_bool()?; let only_buffer_difference = menu.only_buffer_difference.as_bool()?;
description_menu = description_menu.with_only_buffer_difference(only_buffer_difference); description_menu = description_menu.with_only_buffer_difference(only_buffer_difference);
@ -1104,82 +861,22 @@ fn edit_from_record(
span: Span, span: Span,
) -> Result<EditCommand, ShellError> { ) -> Result<EditCommand, ShellError> {
let edit = match name { let edit = match name {
"movetostart" => EditCommand::MoveToStart { "movetostart" => EditCommand::MoveToStart,
select: extract_value("select", record, span) "movetolinestart" => EditCommand::MoveToLineStart,
.and_then(|value| value.as_bool()) "movetoend" => EditCommand::MoveToEnd,
.unwrap_or(false), "movetolineend" => EditCommand::MoveToLineEnd,
}, "moveleft" => EditCommand::MoveLeft,
"movetolinestart" => EditCommand::MoveToLineStart { "moveright" => EditCommand::MoveRight,
select: extract_value("select", record, span) "movewordleft" => EditCommand::MoveWordLeft,
.and_then(|value| value.as_bool()) "movebigwordleft" => EditCommand::MoveBigWordLeft,
.unwrap_or(false), "movewordright" => EditCommand::MoveWordRight,
}, "movewordrightend" => EditCommand::MoveWordRightEnd,
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
"movetoend" => EditCommand::MoveToEnd { "movewordrightstart" => EditCommand::MoveWordRightStart,
select: extract_value("select", record, span) "movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movetolineend" => EditCommand::MoveToLineEnd {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"moveleft" => EditCommand::MoveLeft {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"moveright" => EditCommand::MoveRight {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movewordleft" => EditCommand::MoveWordLeft {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movebigwordleft" => EditCommand::MoveBigWordLeft {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movewordright" => EditCommand::MoveWordRight {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movewordrightend" => EditCommand::MoveWordRightEnd {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movewordrightstart" => EditCommand::MoveWordRightStart {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart {
select: extract_value("select", record, span)
.and_then(|value| value.as_bool())
.unwrap_or(false),
},
"movetoposition" => { "movetoposition" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
let select = extract_value("select", record, span) EditCommand::MoveToPosition(value.as_int()? as usize)
.and_then(|value| value.as_bool())
.unwrap_or(false);
EditCommand::MoveToPosition {
position: value.as_int()? as usize,
select,
}
} }
"insertchar" => { "insertchar" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
@ -1231,18 +928,12 @@ fn edit_from_record(
"moverightuntil" => { "moverightuntil" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
let select = extract_value("select", record, span) EditCommand::MoveRightUntil(char)
.and_then(|value| value.as_bool())
.unwrap_or(false);
EditCommand::MoveRightUntil { c: char, select }
} }
"moverightbefore" => { "moverightbefore" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
let select = extract_value("select", record, span) EditCommand::MoveRightBefore(char)
.and_then(|value| value.as_bool())
.unwrap_or(false);
EditCommand::MoveRightBefore { c: char, select }
} }
"cutleftuntil" => { "cutleftuntil" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
@ -1257,23 +948,14 @@ fn edit_from_record(
"moveleftuntil" => { "moveleftuntil" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
let select = extract_value("select", record, span) EditCommand::MoveLeftUntil(char)
.and_then(|value| value.as_bool())
.unwrap_or(false);
EditCommand::MoveLeftUntil { c: char, select }
} }
"moveleftbefore" => { "moveleftbefore" => {
let value = extract_value("value", record, span)?; let value = extract_value("value", record, span)?;
let char = extract_char(value, config)?; let char = extract_char(value, config)?;
let select = extract_value("select", record, span) EditCommand::MoveLeftBefore(char)
.and_then(|value| value.as_bool())
.unwrap_or(false);
EditCommand::MoveLeftBefore { c: char, select }
} }
"complete" => EditCommand::Complete, "complete" => EditCommand::Complete,
"cutselection" => EditCommand::CutSelection,
"copyselection" => EditCommand::CopySelection,
"selectall" => EditCommand::SelectAll,
e => { e => {
return Err(ShellError::UnsupportedConfigValue { return Err(ShellError::UnsupportedConfigValue {
expected: "reedline EditCommand".to_string(), expected: "reedline EditCommand".to_string(),
@ -1427,57 +1109,4 @@ mod test {
let b = EventType::try_from_record(&event, span); let b = EventType::try_from_record(&event, span);
assert!(matches!(b, Err(ShellError::MissingConfigValue { .. }))); assert!(matches!(b, Err(ShellError::MissingConfigValue { .. })));
} }
#[test]
fn test_move_without_optional_select() {
let event = record! {
"edit" => Value::test_string("moveleft")
};
let event = Value::test_record(event);
let config = Config::default();
let parsed_event = parse_event(&event, &config).unwrap();
assert_eq!(
parsed_event,
Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
select: false
}]))
);
}
#[test]
fn test_move_with_select_false() {
let event = record! {
"edit" => Value::test_string("moveleft"),
"select" => Value::test_bool(false)
};
let event = Value::test_record(event);
let config = Config::default();
let parsed_event = parse_event(&event, &config).unwrap();
assert_eq!(
parsed_event,
Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
select: false
}]))
);
}
#[test]
fn test_move_with_select_true() {
let event = record! {
"edit" => Value::test_string("moveleft"),
"select" => Value::test_bool(true)
};
let event = Value::test_record(event);
let config = Config::default();
let parsed_event = parse_event(&event, &config).unwrap();
assert_eq!(
parsed_event,
Some(ReedlineEvent::Edit(vec![EditCommand::MoveLeft {
select: true
}]))
);
}
} }

View File

@ -11,13 +11,13 @@ use miette::{ErrReport, IntoDiagnostic, Result};
use nu_cmd_base::util::get_guaranteed_cwd; use nu_cmd_base::util::get_guaranteed_cwd;
use nu_cmd_base::{hook::eval_hook, util::get_editor}; use nu_cmd_base::{hook::eval_hook, util::get_editor};
use nu_color_config::StyleComputer; use nu_color_config::StyleComputer;
use nu_engine::{convert_env_values, env_to_strings}; use nu_engine::convert_env_values;
use nu_parser::{lex, parse, trim_quotes_str}; use nu_parser::{lex, parse, trim_quotes_str};
use nu_protocol::{ use nu_protocol::{
config::NuCursorShape, config::NuCursorShape,
engine::{EngineState, Stack, StateWorkingSet}, engine::{EngineState, Stack, StateWorkingSet},
eval_const::create_nu_constant, eval_const::create_nu_constant,
report_error_new, HistoryConfig, HistoryFileFormat, PipelineData, ShellError, Span, Spanned, report_error, report_error_new, HistoryFileFormat, PipelineData, ShellError, Span, Spanned,
Value, NU_VARIABLE_ID, Value, NU_VARIABLE_ID,
}; };
use nu_utils::utils::perf; use nu_utils::utils::perf;
@ -28,11 +28,11 @@ use reedline::{
use std::{ use std::{
env::temp_dir, env::temp_dir,
io::{self, IsTerminal, Write}, io::{self, IsTerminal, Write},
path::PathBuf, path::Path,
sync::atomic::Ordering, sync::atomic::Ordering,
time::{Duration, Instant}, time::Instant,
}; };
use sysinfo::System; use sysinfo::SystemExt;
// According to Daniel Imms @Tyriar, we need to do these this way: // According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output> // <133 A><prompt><133 B><command><133 C><command output>
@ -44,9 +44,6 @@ const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\"; // const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
const RESET_APPLICATION_MODE: &str = "\x1b[?1l"; const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
///
/// The main REPL loop, including spinning up the prompt itself.
///
pub fn evaluate_repl( pub fn evaluate_repl(
engine_state: &mut EngineState, engine_state: &mut EngineState,
stack: &mut Stack, stack: &mut Stack,
@ -57,19 +54,27 @@ pub fn evaluate_repl(
) -> Result<()> { ) -> Result<()> {
use nu_cmd_base::hook; use nu_cmd_base::hook;
use reedline::Signal; use reedline::Signal;
let config = engine_state.get_config(); let use_color = engine_state.get_config().use_ansi_coloring;
let use_color = config.use_ansi_coloring;
confirm_stdin_is_terminal()?; // Guard against invocation without a connected terminal.
// reedline / crossterm event polling will fail without a connected tty
if !std::io::stdin().is_terminal() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
))
.into_diagnostic();
}
let mut entry_num = 0; let mut entry_num = 0;
let mut nu_prompt = NushellPrompt::new(config.shell_integration); let mut nu_prompt = NushellPrompt::new();
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
// Translate environment variables from Strings to Values // Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) { if let Some(e) = convert_env_values(engine_state, stack) {
report_error_new(engine_state, &e); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
} }
perf( perf(
"translate env vars", "translate env vars",
@ -103,20 +108,42 @@ pub fn evaluate_repl(
use_color, use_color,
); );
if let Some(history) = engine_state.history_config() { // Setup history_isolation aka "history per session"
start_time = std::time::Instant::now(); let history_isolation = engine_state.get_config().history_isolation;
let history_session_id = if history_isolation {
Reedline::create_history_session_id()
} else {
None
};
line_editor = setup_history(nushell_path, engine_state, line_editor, history)?; start_time = std::time::Instant::now();
let history_path = crate::config_files::get_history_path(
nushell_path,
engine_state.config.history_file_format,
);
if let Some(history_path) = history_path.as_deref() {
line_editor =
update_line_editor_history(engine_state, history_path, line_editor, history_session_id)?
};
perf(
"setup history",
start_time,
file!(),
line!(),
column!(),
use_color,
);
perf( start_time = std::time::Instant::now();
"setup history", let sys = sysinfo::System::new();
start_time, perf(
file!(), "get sysinfo",
line!(), start_time,
column!(), file!(),
use_color, line!(),
); column!(),
} use_color,
);
if let Some(s) = prerun_command { if let Some(s) = prerun_command {
eval_source( eval_source(
@ -147,7 +174,9 @@ pub fn evaluate_repl(
); );
} }
kitty_protocol_healthcheck(engine_state); if engine_state.get_config().use_kitty_protocol && !reedline::kitty_protocol_available() {
warn!("Terminal doesn't support use_kitty_protocol config");
}
loop { loop {
let loop_start_time = std::time::Instant::now(); let loop_start_time = std::time::Instant::now();
@ -183,6 +212,20 @@ pub fn evaluate_repl(
use_color, use_color,
); );
start_time = std::time::Instant::now();
// Reset the SIGQUIT handler
if let Some(sig_quit) = engine_state.get_sig_quit() {
sig_quit.store(false, Ordering::SeqCst);
}
perf(
"reset sig_quit",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
let config = engine_state.get_config(); let config = engine_state.get_config();
@ -212,7 +255,6 @@ pub fn evaluate_repl(
.use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste) .use_bracketed_paste(cfg!(not(target_os = "windows")) && config.bracketed_paste)
.with_highlighter(Box::new(NuHighlighter { .with_highlighter(Box::new(NuHighlighter {
engine_state: engine_reference.clone(), engine_state: engine_reference.clone(),
stack: std::sync::Arc::new(stack.clone()),
config: config.clone(), config: config.clone(),
})) }))
.with_validator(Box::new(NuValidator { .with_validator(Box::new(NuValidator {
@ -225,7 +267,11 @@ pub fn evaluate_repl(
.with_quick_completions(config.quick_completions) .with_quick_completions(config.quick_completions)
.with_partial_completions(config.partial_completions) .with_partial_completions(config.partial_completions)
.with_ansi_colors(config.use_ansi_coloring) .with_ansi_colors(config.use_ansi_coloring)
.with_cursor_config(cursor_config); .with_cursor_config(cursor_config)
.with_transient_prompt(prompt_update::transient_prompt(
engine_reference.clone(),
stack,
));
perf( perf(
"reedline builder", "reedline builder",
start_time, start_time,
@ -258,7 +304,8 @@ pub fn evaluate_repl(
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| { line_editor = add_menus(line_editor, engine_reference, stack, config).unwrap_or_else(|e| {
report_error_new(engine_state, &e); let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
Reedline::create() Reedline::create()
}); });
perf( perf(
@ -275,9 +322,12 @@ pub fn evaluate_repl(
line_editor = if let Ok((cmd, args)) = buffer_editor { line_editor = if let Ok((cmd, args)) = buffer_editor {
let mut command = std::process::Command::new(&cmd); let mut command = std::process::Command::new(&cmd);
command command.args(args).envs(
.args(args) engine_state
.envs(env_to_strings(engine_state, stack)?); .render_env_vars()
.into_iter()
.filter_map(|(k, v)| v.as_string().ok().map(|v| (k, v))),
);
line_editor.with_buffer_editor(command, temp_file.clone()) line_editor.with_buffer_editor(command, temp_file.clone())
} else { } else {
line_editor line_editor
@ -291,26 +341,43 @@ pub fn evaluate_repl(
use_color, use_color,
); );
if let Some(history) = engine_state.history_config() { start_time = std::time::Instant::now();
start_time = std::time::Instant::now(); if config.sync_history_on_enter {
if history.sync_on_enter { if let Err(e) = line_editor.sync_history() {
if let Err(e) = line_editor.sync_history() { warn!("Failed to sync history: {}", e);
warn!("Failed to sync history: {}", e);
}
} }
perf(
"sync_history",
start_time,
file!(),
line!(),
column!(),
use_color,
);
} }
perf(
"sync_history",
start_time,
file!(),
line!(),
column!(),
use_color,
);
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
// Changing the line editor based on the found keybindings // Changing the line editor based on the found keybindings
line_editor = setup_keybindings(engine_state, line_editor); line_editor = match create_keybindings(config) {
Ok(keybindings) => match keybindings {
KeybindingsMode::Emacs(keybindings) => {
let edit_mode = Box::new(Emacs::new(keybindings));
line_editor.with_edit_mode(edit_mode)
}
KeybindingsMode::Vi {
insert_keybindings,
normal_keybindings,
} => {
let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings));
line_editor.with_edit_mode(edit_mode)
}
},
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
line_editor
}
};
perf( perf(
"keybindings", "keybindings",
start_time, start_time,
@ -357,9 +424,7 @@ pub fn evaluate_repl(
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
let config = &engine_state.get_config().clone(); let config = &engine_state.get_config().clone();
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt); let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
let transient_prompt =
prompt_update::make_transient_prompt(config, engine_state, stack, &nu_prompt);
perf( perf(
"update_prompt", "update_prompt",
start_time, start_time,
@ -372,20 +437,25 @@ pub fn evaluate_repl(
entry_num += 1; entry_num += 1;
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
line_editor = line_editor.with_transient_prompt(transient_prompt); let input = line_editor.read_line(prompt);
let input = line_editor.read_line(&nu_prompt);
let shell_integration = config.shell_integration; let shell_integration = config.shell_integration;
match input { match input {
Ok(Signal::Success(s)) => { Ok(Signal::Success(s)) => {
let hostname = System::host_name(); let hostname = sys.host_name();
let history_supports_meta = matches!( let history_supports_meta =
engine_state.history_config().map(|h| h.file_format), matches!(config.history_file_format, HistoryFileFormat::Sqlite);
Some(HistoryFileFormat::Sqlite) if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
); {
line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname = hostname.clone();
if history_supports_meta { c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
prepare_history_metadata(&s, &hostname, engine_state, &mut line_editor)?; c
})
.into_diagnostic()?; // todo: don't stop repl if error here?
} }
// Right before we start running the code the user gave us, fire the `pre_execution` // Right before we start running the code the user gave us, fire the `pre_execution`
@ -412,27 +482,107 @@ pub fn evaluate_repl(
run_ansi_sequence(PRE_EXECUTE_MARKER)?; run_ansi_sequence(PRE_EXECUTE_MARKER)?;
} }
// Actual command execution logic starts from here
let start_time = Instant::now(); let start_time = Instant::now();
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
match parse_operation(s.clone(), engine_state, stack)? { let mut orig = s.clone();
ReplOperation::AutoCd { cwd, target, span } => { if orig.starts_with('`') {
do_auto_cd(target, cwd, stack, engine_state, span); orig = trim_quotes_str(&orig).to_string()
}
ReplOperation::RunCommand(cmd) => {
line_editor = do_run_cmd(
&cmd,
stack,
engine_state,
line_editor,
shell_integration,
entry_num,
)?;
}
// as the name implies, we do nothing in this case
ReplOperation::DoNothing => {}
} }
let path = nu_path::expand_path_with(&orig, &cwd);
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
// We have an auto-cd
let (path, span) = {
if !path.exists() {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::DirectoryNotFound {
dir: path.to_string_lossy().to_string(),
span: tokens.0[0].span,
},
);
}
let path = nu_path::canonicalize_with(path, &cwd)
.expect("internal error: cannot canonicalize known path");
(path.to_string_lossy().to_string(), tokens.0[0].span)
};
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
//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("PWD".into(), Value::string(path.clone(), Span::unknown()));
let cwd = Value::string(cwd, span);
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
let mut shells = if let Some(v) = shells {
v.as_list()
.map(|x| x.to_vec())
.unwrap_or_else(|_| vec![cwd])
} else {
vec![cwd]
};
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
let current_shell = if let Some(v) = current_shell {
v.as_int().unwrap_or_default() as usize
} else {
0
};
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
let last_shell = if let Some(v) = last_shell {
v.as_int().unwrap_or_default() as usize
} else {
0
};
shells[current_shell] = Value::string(path, span);
stack.add_env_var("NUSHELL_SHELLS".into(), Value::list(shells, span));
stack.add_env_var(
"NUSHELL_LAST_SHELL".into(),
Value::int(last_shell as i64, span),
);
} else if !s.trim().is_empty() {
trace!("eval source: {}", s);
let mut cmds = s.split_whitespace();
if let Some("exit") = cmds.next() {
let mut working_set = StateWorkingSet::new(engine_state);
let _ = parse(&mut working_set, None, s.as_bytes(), false);
if working_set.parse_errors.is_empty() {
match cmds.next() {
Some(s) => {
if let Ok(n) = s.parse::<i32>() {
drop(line_editor);
std::process::exit(n);
}
}
None => {
drop(line_editor);
std::process::exit(0);
}
}
}
}
eval_source(
engine_state,
stack,
s.as_bytes(),
&format!("entry #{entry_num}"),
PipelineData::empty(),
false,
);
}
let cmd_duration = start_time.elapsed(); let cmd_duration = start_time.elapsed();
stack.add_env_var( stack.add_env_var(
@ -440,21 +590,74 @@ pub fn evaluate_repl(
Value::string(format!("{}", cmd_duration.as_millis()), Span::unknown()), Value::string(format!("{}", cmd_duration.as_millis()), Span::unknown()),
); );
if history_supports_meta { if history_supports_meta && !s.is_empty() && line_editor.has_last_command_context()
fill_in_result_related_history_metadata( {
&s, line_editor
engine_state, .update_last_command_context(&|mut c| {
cmd_duration, c.duration = Some(cmd_duration);
stack, c.exit_status = stack
&mut line_editor, .get_env_var(engine_state, "LAST_EXIT_CODE")
)?; .and_then(|e| e.as_i64().ok());
c
})
.into_diagnostic()?; // todo: don't stop repl if error here?
} }
if shell_integration { if shell_integration {
do_shell_integration_finalize_command(hostname, engine_state, stack)?; 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()?;
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
if stack.get_env_var(engine_state, "TERM_PROGRAM")
== Some(Value::test_string("vscode"))
{
// If we're in vscode, run their specific ansi escape sequence.
// This is helpful for ctrl+g to change directories in the terminal.
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path))?;
} else {
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
&hostname.unwrap_or_else(|| "localhost".to_string()),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(
&path,
percent_encoding::CONTROLS
)
))?;
}
// 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;{maybe_abbrev_path}\x07"))?;
}
run_ansi_sequence(RESET_APPLICATION_MODE)?;
} }
flush_engine_state_repl_buffer(engine_state, &mut line_editor); let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
line_editor.run_edit_commands(&[
EditCommand::Clear,
EditCommand::InsertString(repl.buffer.to_string()),
EditCommand::MoveToPosition(repl.cursor_pos),
]);
repl.buffer = "".to_string();
repl.cursor_pos = 0;
drop(repl);
} }
Ok(Signal::CtrlC) => { Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown // `Reedline` clears the line content. New prompt is shown
@ -506,364 +709,6 @@ pub fn evaluate_repl(
Ok(()) Ok(())
} }
///
/// Put in history metadata not related to the result of running the command
///
fn prepare_history_metadata(
s: &str,
hostname: &Option<String>,
engine_state: &EngineState,
line_editor: &mut Reedline,
) -> Result<()> {
if !s.is_empty() && line_editor.has_last_command_context() {
line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname = hostname.clone();
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
c
})
.into_diagnostic()?; // todo: don't stop repl if error here?
}
Ok(())
}
///
/// Fills in history item metadata based on the execution result (notably duration and exit code)
///
fn fill_in_result_related_history_metadata(
s: &str,
engine_state: &EngineState,
cmd_duration: Duration,
stack: &mut Stack,
line_editor: &mut Reedline,
) -> Result<()> {
if !s.is_empty() && line_editor.has_last_command_context() {
line_editor
.update_last_command_context(&|mut c| {
c.duration = Some(cmd_duration);
c.exit_status = stack
.get_env_var(engine_state, "LAST_EXIT_CODE")
.and_then(|e| e.as_i64().ok());
c
})
.into_diagnostic()?; // todo: don't stop repl if error here?
}
Ok(())
}
/// The kinds of operations you can do in a single loop iteration of the REPL
enum ReplOperation {
/// "auto-cd": change directory by typing it in directly
AutoCd {
/// the current working directory
cwd: String,
/// the target
target: PathBuf,
/// span information for debugging
span: Span,
},
/// run a command
RunCommand(String),
/// do nothing (usually through an empty string)
DoNothing,
}
///
/// Parses one "REPL line" of input, to try and derive intent.
/// Notably, this is where we detect whether the user is attempting an
/// "auto-cd" (writing a relative path directly instead of `cd path`)
///
/// Returns the ReplOperation we believe the user wants to do
///
fn parse_operation(
s: String,
engine_state: &EngineState,
stack: &Stack,
) -> Result<ReplOperation, ErrReport> {
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
let cwd = nu_engine::env::current_dir_str(engine_state, stack)?;
let mut orig = s.clone();
if orig.starts_with('`') {
orig = trim_quotes_str(&orig).to_string()
}
let path = nu_path::expand_path_with(&orig, &cwd);
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
Ok(ReplOperation::AutoCd {
cwd,
target: path,
span: tokens.0[0].span,
})
} else if !s.trim().is_empty() {
Ok(ReplOperation::RunCommand(s))
} else {
Ok(ReplOperation::DoNothing)
}
}
///
/// Execute an "auto-cd" operation, changing the current working directory.
///
fn do_auto_cd(
path: PathBuf,
cwd: String,
stack: &mut Stack,
engine_state: &mut EngineState,
span: Span,
) {
let path = {
if !path.exists() {
report_error_new(
engine_state,
&ShellError::DirectoryNotFound {
dir: path.to_string_lossy().to_string(),
span,
},
);
}
let path = nu_path::canonicalize_with(path, &cwd)
.expect("internal error: cannot canonicalize known path");
path.to_string_lossy().to_string()
};
stack.add_env_var("OLDPWD".into(), Value::string(cwd.clone(), Span::unknown()));
//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("PWD".into(), Value::string(path.clone(), Span::unknown()));
let cwd = Value::string(cwd, span);
let shells = stack.get_env_var(engine_state, "NUSHELL_SHELLS");
let mut shells = if let Some(v) = shells {
v.as_list()
.map(|x| x.to_vec())
.unwrap_or_else(|_| vec![cwd])
} else {
vec![cwd]
};
let current_shell = stack.get_env_var(engine_state, "NUSHELL_CURRENT_SHELL");
let current_shell = if let Some(v) = current_shell {
v.as_int().unwrap_or_default() as usize
} else {
0
};
let last_shell = stack.get_env_var(engine_state, "NUSHELL_LAST_SHELL");
let last_shell = if let Some(v) = last_shell {
v.as_int().unwrap_or_default() as usize
} else {
0
};
shells[current_shell] = Value::string(path, span);
stack.add_env_var("NUSHELL_SHELLS".into(), Value::list(shells, span));
stack.add_env_var(
"NUSHELL_LAST_SHELL".into(),
Value::int(last_shell as i64, span),
);
}
///
/// Run a command as received from reedline. This is where we are actually
/// running a thing!
///
fn do_run_cmd(
s: &str,
stack: &mut Stack,
engine_state: &mut EngineState,
// we pass in the line editor so it can be dropped in the case of a process exit
// (in the normal case we don't want to drop it so return it as-is otherwise)
line_editor: Reedline,
shell_integration: bool,
entry_num: usize,
) -> Result<Reedline> {
trace!("eval source: {}", s);
let mut cmds = s.split_whitespace();
if let Some("exit") = cmds.next() {
let mut working_set = StateWorkingSet::new(engine_state);
let _ = parse(&mut working_set, None, s.as_bytes(), false);
if working_set.parse_errors.is_empty() {
match cmds.next() {
Some(s) => {
if let Ok(n) = s.parse::<i32>() {
drop(line_editor);
std::process::exit(n);
}
}
None => {
drop(line_editor);
std::process::exit(0);
}
}
}
}
if shell_integration {
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
};
let binary_name = s.split_whitespace().next();
if let Some(binary_name) = binary_name {
run_ansi_sequence(&format!("\x1b]2;{maybe_abbrev_path}> {binary_name}\x07"))?;
}
}
}
eval_source(
engine_state,
stack,
s.as_bytes(),
&format!("entry #{entry_num}"),
PipelineData::empty(),
false,
);
Ok(line_editor)
}
///
/// Output some things and set environment variables so shells with the right integration
/// can have more information about what is going on (after we have run a command)
///
fn do_shell_integration_finalize_command(
hostname: Option<String>,
engine_state: &EngineState,
stack: &mut Stack,
) -> Result<()> {
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()?;
// Supported escape sequences of Microsoft's Visual Studio Code (vscode)
// https://code.visualstudio.com/docs/terminal/shell-integration#_supported-escape-sequences
if stack.get_env_var(engine_state, "TERM_PROGRAM") == Some(Value::test_string("vscode")) {
// If we're in vscode, run their specific ansi escape sequence.
// This is helpful for ctrl+g to change directories in the terminal.
run_ansi_sequence(&format!("\x1b]633;P;Cwd={}\x1b\\", path))?;
} else {
// Otherwise, communicate the path as OSC 7 (often used for spawning new tabs in the same dir)
run_ansi_sequence(&format!(
"\x1b]7;file://{}{}{}\x1b\\",
percent_encoding::utf8_percent_encode(
&hostname.unwrap_or_else(|| "localhost".to_string()),
percent_encoding::CONTROLS
),
if path.starts_with('/') { "" } else { "/" },
percent_encoding::utf8_percent_encode(&path, percent_encoding::CONTROLS)
))?;
}
// 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;{maybe_abbrev_path}\x07"))?;
}
run_ansi_sequence(RESET_APPLICATION_MODE)?;
Ok(())
}
///
/// Clear the screen and output anything remaining in the EngineState buffer.
///
fn flush_engine_state_repl_buffer(engine_state: &mut EngineState, line_editor: &mut Reedline) {
let mut repl = engine_state.repl_state.lock().expect("repl state mutex");
line_editor.run_edit_commands(&[
EditCommand::Clear,
EditCommand::InsertString(repl.buffer.to_string()),
EditCommand::MoveToPosition {
position: repl.cursor_pos,
select: false,
},
]);
repl.buffer = "".to_string();
repl.cursor_pos = 0;
}
///
/// Setup history management for Reedline
///
fn setup_history(
nushell_path: &str,
engine_state: &mut EngineState,
line_editor: Reedline,
history: HistoryConfig,
) -> Result<Reedline> {
// Setup history_isolation aka "history per session"
let history_session_id = if history.isolation {
Reedline::create_history_session_id()
} else {
None
};
if let Some(path) = crate::config_files::get_history_path(nushell_path, history.file_format) {
return update_line_editor_history(
engine_state,
path,
history,
line_editor,
history_session_id,
);
};
Ok(line_editor)
}
///
/// Setup Reedline keybindingds based on the provided config
///
fn setup_keybindings(engine_state: &EngineState, line_editor: Reedline) -> Reedline {
return match create_keybindings(engine_state.get_config()) {
Ok(keybindings) => match keybindings {
KeybindingsMode::Emacs(keybindings) => {
let edit_mode = Box::new(Emacs::new(keybindings));
line_editor.with_edit_mode(edit_mode)
}
KeybindingsMode::Vi {
insert_keybindings,
normal_keybindings,
} => {
let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings));
line_editor.with_edit_mode(edit_mode)
}
},
Err(e) => {
report_error_new(engine_state, &e);
line_editor
}
};
}
///
/// Make sure that the terminal supports the kitty protocol if the config is asking for it
///
fn kitty_protocol_healthcheck(engine_state: &EngineState) {
if engine_state.get_config().use_kitty_protocol && !reedline::kitty_protocol_available() {
warn!("Terminal doesn't support use_kitty_protocol config");
}
}
fn store_history_id_in_engine(engine_state: &mut EngineState, line_editor: &Reedline) { fn store_history_id_in_engine(engine_state: &mut EngineState, line_editor: &Reedline) {
let session_id = line_editor let session_id = line_editor
.get_history_session_id() .get_history_session_id()
@ -875,15 +720,18 @@ fn store_history_id_in_engine(engine_state: &mut EngineState, line_editor: &Reed
fn update_line_editor_history( fn update_line_editor_history(
engine_state: &mut EngineState, engine_state: &mut EngineState,
history_path: PathBuf, history_path: &Path,
history: HistoryConfig,
line_editor: Reedline, line_editor: Reedline,
history_session_id: Option<HistorySessionId>, history_session_id: Option<HistorySessionId>,
) -> Result<Reedline, ErrReport> { ) -> Result<Reedline, ErrReport> {
let history: Box<dyn reedline::History> = match history.file_format { let config = engine_state.get_config();
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Box::new( HistoryFileFormat::PlainText => Box::new(
FileBackedHistory::with_file(history.max_size as usize, history_path) FileBackedHistory::with_file(
.into_diagnostic()?, config.max_history_size as usize,
history_path.to_path_buf(),
)
.into_diagnostic()?,
), ),
HistoryFileFormat::Sqlite => Box::new( HistoryFileFormat::Sqlite => Box::new(
SqliteBackedHistory::with_file( SqliteBackedHistory::with_file(
@ -904,18 +752,6 @@ fn update_line_editor_history(
Ok(line_editor) Ok(line_editor)
} }
fn confirm_stdin_is_terminal() -> Result<()> {
// Guard against invocation without a connected terminal.
// reedline / crossterm event polling will fail without a connected tty
if !std::io::stdin().is_terminal() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Nushell launched as a REPL, but STDIN is not a TTY; either launch in a valid terminal or provide arguments to invoke a script!",
))
.into_diagnostic();
}
Ok(())
}
fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> { fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorStyle> {
match shape { match shape {
NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock), NuCursorShape::Block => Some(SetCursorStyle::SteadyBlock),
@ -928,7 +764,7 @@ fn map_nucursorshape_to_cursorshape(shape: NuCursorShape) -> Option<SetCursorSty
} }
} }
fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String { pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
let exit_code = stack let exit_code = stack
.get_env_var(engine_state, "LAST_EXIT_CODE") .get_env_var(engine_state, "LAST_EXIT_CODE")
.and_then(|e| e.as_i64().ok()); .and_then(|e| e.as_i64().ok());
@ -1003,18 +839,14 @@ fn trailing_slash_looks_like_path() {
#[test] #[test]
fn are_session_ids_in_sync() { fn are_session_ids_in_sync() {
let engine_state = &mut EngineState::new(); let engine_state = &mut EngineState::new();
let history = engine_state.history_config().unwrap(); let history_path_o =
let history_path = crate::config_files::get_history_path("nushell", engine_state.config.history_file_format);
crate::config_files::get_history_path("nushell", history.file_format).unwrap(); assert!(history_path_o.is_some());
let history_path = history_path_o.as_deref().unwrap();
let line_editor = reedline::Reedline::create(); let line_editor = reedline::Reedline::create();
let history_session_id = reedline::Reedline::create_history_session_id(); let history_session_id = reedline::Reedline::create_history_session_id();
let line_editor = update_line_editor_history( let line_editor =
engine_state, update_line_editor_history(engine_state, history_path, line_editor, history_session_id);
history_path,
history,
line_editor,
history_session_id,
);
assert_eq!( assert_eq!(
i64::from(line_editor.unwrap().get_history_session_id().unwrap()), i64::from(line_editor.unwrap().get_history_session_id().unwrap()),
engine_state.history_session_id engine_state.history_session_id

View File

@ -1,17 +1,15 @@
use log::trace; use log::trace;
use nu_ansi_term::Style; use nu_ansi_term::Style;
use nu_color_config::{get_matching_brackets_style, get_shape_color}; use nu_color_config::{get_matching_brackets_style, get_shape_color};
use nu_engine::env;
use nu_parser::{flatten_block, parse, FlatShape}; use nu_parser::{flatten_block, parse, FlatShape};
use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement, RecordItem}; use nu_protocol::ast::{Argument, Block, Expr, Expression, PipelineElement, RecordItem};
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet}; use nu_protocol::engine::{EngineState, StateWorkingSet};
use nu_protocol::{Config, Span}; use nu_protocol::{Config, Span};
use reedline::{Highlighter, StyledText}; use reedline::{Highlighter, StyledText};
use std::sync::Arc; use std::sync::Arc;
pub struct NuHighlighter { pub struct NuHighlighter {
pub engine_state: Arc<EngineState>, pub engine_state: Arc<EngineState>,
pub stack: Arc<Stack>,
pub config: Config, pub config: Config,
} }
@ -34,17 +32,7 @@ impl Highlighter for NuHighlighter {
working_set.get_span_contents(Span::new(span.start, span.end)); working_set.get_span_contents(Span::new(span.start, span.end));
let str_word = String::from_utf8_lossy(str_contents).to_string(); let str_word = String::from_utf8_lossy(str_contents).to_string();
let paths = env::path_str(&self.engine_state, &self.stack, *span).ok(); if which::which(str_word).ok().is_some() {
let res = if let Ok(cwd) =
env::current_dir_str(&self.engine_state, &self.stack)
{
which::which_in(str_word, paths.as_ref(), cwd).ok()
} else {
which::which_in_global(str_word, paths.as_ref())
.ok()
.and_then(|mut i| i.next())
};
if res.is_some() {
*shape = FlatShape::ExternalResolved; *shape = FlatShape::ExternalResolved;
} }
} }
@ -333,14 +321,15 @@ fn find_matching_block_end_in_expr(
Expr::Keyword(..) => None, Expr::Keyword(..) => None,
Expr::ValueWithUnit(..) => None, Expr::ValueWithUnit(..) => None,
Expr::DateTime(_) => None, Expr::DateTime(_) => None,
Expr::Filepath(_, _) => None, Expr::Filepath(_) => None,
Expr::Directory(_, _) => None, Expr::Directory(_) => None,
Expr::GlobPattern(_, _) => None, Expr::GlobPattern(_) => None,
Expr::String(_) => None, Expr::String(_) => None,
Expr::CellPath(_) => None, Expr::CellPath(_) => None,
Expr::ImportPattern(_) => None, Expr::ImportPattern(_) => None,
Expr::Overlay(_) => None, Expr::Overlay(_) => None,
Expr::Signature(_) => None, Expr::Signature(_) => None,
Expr::MatchPattern(_) => None,
Expr::MatchBlock(_) => None, Expr::MatchBlock(_) => None,
Expr::Nothing => None, Expr::Nothing => None,
Expr::Garbage => None, Expr::Garbage => None,
@ -397,7 +386,6 @@ fn find_matching_block_end_in_expr(
Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(), Argument::Named((_, _, opt_expr)) => opt_expr.as_ref(),
Argument::Positional(inner_expr) => Some(inner_expr), Argument::Positional(inner_expr) => Some(inner_expr),
Argument::Unknown(inner_expr) => Some(inner_expr), Argument::Unknown(inner_expr) => Some(inner_expr),
Argument::Spread(inner_expr) => Some(inner_expr),
}; };
if let Some(inner_expr) = opt_expr { if let Some(inner_expr) = opt_expr {

View File

@ -220,10 +220,6 @@ pub fn eval_source(
source, source,
false, false,
); );
if let Some(warning) = working_set.parse_warnings.first() {
report_error(&working_set, warning);
}
if let Some(err) = working_set.parse_errors.first() { if let Some(err) = working_set.parse_errors.first() {
set_last_exit_code(stack, 1); set_last_exit_code(stack, 1);
report_error(&working_set, err); report_error(&working_set, err);

View File

@ -91,7 +91,7 @@ fn variables_dollar_sign_with_varialblecompletion() {
let target_dir = "$ "; let target_dir = "$ ";
let suggestions = completer.complete(target_dir, target_dir.len()); let suggestions = completer.complete(target_dir, target_dir.len());
assert_eq!(8, suggestions.len()); assert_eq!(7, suggestions.len());
} }
#[rstest] #[rstest]
@ -144,34 +144,15 @@ fn dotnu_completions() {
let completion_str = "source-env ".to_string(); let completion_str = "source-env ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len()); let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(2, suggestions.len()); assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value); assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
#[cfg(windows)]
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
#[cfg(not(windows))]
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
// Test use completion // Test use completion
let completion_str = "use ".to_string(); let completion_str = "use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len()); let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(2, suggestions.len()); assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value); assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
#[cfg(windows)]
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
#[cfg(not(windows))]
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
// Test overlay use completion
let completion_str = "overlay use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(2, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.first().unwrap().value);
#[cfg(windows)]
assert_eq!("directory_completion\\", suggestions.get(1).unwrap().value);
#[cfg(not(windows))]
assert_eq!("directory_completion/", suggestions.get(1).unwrap().value);
} }
#[test] #[test]
@ -227,7 +208,6 @@ fn file_completions() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
folder(dir.join("another")), folder(dir.join("another")),
file(dir.join("custom_completion.nu")), file(dir.join("custom_completion.nu")),
folder(dir.join("directory_completion")),
file(dir.join("nushell")), file(dir.join("nushell")),
folder(dir.join("test_a")), folder(dir.join("test_a")),
folder(dir.join("test_b")), folder(dir.join("test_b")),
@ -343,7 +323,6 @@ fn command_ls_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -354,7 +333,6 @@ fn command_ls_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -377,7 +355,6 @@ fn command_open_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -388,7 +365,6 @@ fn command_open_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -412,7 +388,6 @@ fn command_rm_with_globcompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -423,7 +398,6 @@ fn command_rm_with_globcompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -447,7 +421,6 @@ fn command_cp_with_globcompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -458,7 +431,6 @@ fn command_cp_with_globcompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -482,7 +454,6 @@ fn command_save_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -493,7 +464,6 @@ fn command_save_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -517,7 +487,6 @@ fn command_touch_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -528,7 +497,6 @@ fn command_touch_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -552,7 +520,6 @@ fn command_watch_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -563,7 +530,6 @@ fn command_watch_with_filecompletion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -659,7 +625,6 @@ fn folder_with_directorycompletions() {
// Create the expected values // Create the expected values
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
folder(dir.join("another")), folder(dir.join("another")),
folder(dir.join("directory_completion")),
folder(dir.join("test_a")), folder(dir.join("test_a")),
folder(dir.join("test_b")), folder(dir.join("test_b")),
folder(dir.join(".hidden_folder")), folder(dir.join(".hidden_folder")),
@ -684,14 +649,13 @@ fn variables_completions() {
// Test completions for $nu // Test completions for $nu
let suggestions = completer.complete("$nu.", 4); let suggestions = completer.complete("$nu.", 4);
assert_eq!(15, suggestions.len()); assert_eq!(14, suggestions.len());
let expected: Vec<String> = vec![ let expected: Vec<String> = vec![
"config-path".into(), "config-path".into(),
"current-exe".into(), "current-exe".into(),
"default-config-dir".into(), "default-config-dir".into(),
"env-path".into(), "env-path".into(),
"history-enabled".into(),
"history-path".into(), "history-path".into(),
"home-path".into(), "home-path".into(),
"is-interactive".into(), "is-interactive".into(),
@ -710,13 +674,9 @@ fn variables_completions() {
// Test completions for $nu.h (filter) // Test completions for $nu.h (filter)
let suggestions = completer.complete("$nu.h", 5); let suggestions = completer.complete("$nu.h", 5);
assert_eq!(3, suggestions.len()); assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec![ let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
"history-enabled".into(),
"history-path".into(),
"home-path".into(),
];
// Match results // Match results
match_suggestions(expected, suggestions); match_suggestions(expected, suggestions);
@ -879,7 +839,6 @@ fn unknown_command_completion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -890,7 +849,6 @@ fn unknown_command_completion() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),
@ -941,7 +899,6 @@ fn filecompletions_triggers_after_cursor() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another\\".to_string(), "another\\".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion\\".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a\\".to_string(), "test_a\\".to_string(),
"test_b\\".to_string(), "test_b\\".to_string(),
@ -952,7 +909,6 @@ fn filecompletions_triggers_after_cursor() {
let expected_paths: Vec<String> = vec![ let expected_paths: Vec<String> = vec![
"another/".to_string(), "another/".to_string(),
"custom_completion.nu".to_string(), "custom_completion.nu".to_string(),
"directory_completion/".to_string(),
"nushell".to_string(), "nushell".to_string(),
"test_a/".to_string(), "test_a/".to_string(),
"test_b/".to_string(), "test_b/".to_string(),

View File

@ -5,21 +5,21 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-base" name = "nu-cmd-base"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
version = "0.90.1" version = "0.88.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.90.1" } nu-engine = { path = "../nu-engine", version = "0.88.2" }
nu-glob = { path = "../nu-glob", version = "0.90.1" } nu-glob = { path = "../nu-glob", version = "0.88.2" }
nu-parser = { path = "../nu-parser", version = "0.90.1" } nu-parser = { path = "../nu-parser", version = "0.88.2" }
nu-path = { path = "../nu-path", version = "0.90.1" } nu-path = { path = "../nu-path", version = "0.88.2" }
nu-protocol = { path = "../nu-protocol", version = "0.90.1" } nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
nu-utils = { path = "../nu-utils", version = "0.90.1" } nu-utils = { path = "../nu-utils", version = "0.88.2" }
indexmap = "2.1" indexmap = "2.1"
miette = "5.10.0" miette = "5.10.0"
[dev-dependencies] [dev-dependencies]
nu-test-support = { path = "../nu-test-support", version = "0.90.1" } nu-test-support = { path = "../nu-test-support", version = "0.88.2" }
rstest = "0.18.2" rstest = "0.18.2"

View File

@ -0,0 +1,205 @@
// utilities for expanding globs in command arguments
use nu_glob::{glob_with_parent, MatchOptions, Paths};
use nu_protocol::{ShellError, Spanned};
use std::fs;
use std::path::{Path, PathBuf};
// standard glob options to use for filesystem command arguments
const GLOB_PARAMS: MatchOptions = MatchOptions {
case_sensitive: true,
require_literal_separator: false,
require_literal_leading_dot: false,
recursive_match_hidden_dir: true,
};
// handle an argument that could be a literal path or a glob.
// if literal path, return just that (whether user can access it or not).
// if glob, expand into matching paths, using GLOB_PARAMS options.
pub fn arg_glob(
pattern: &Spanned<String>, // alleged path or glob
cwd: &Path, // current working directory
) -> Result<Paths, ShellError> {
arg_glob_opt(pattern, cwd, GLOB_PARAMS)
}
// variant of [arg_glob] that requires literal dot prefix in pattern to match dot-prefixed path.
pub fn arg_glob_leading_dot(pattern: &Spanned<String>, cwd: &Path) -> Result<Paths, ShellError> {
arg_glob_opt(
pattern,
cwd,
MatchOptions {
require_literal_leading_dot: true,
..GLOB_PARAMS
},
)
}
fn arg_glob_opt(
pattern: &Spanned<String>,
cwd: &Path,
options: MatchOptions,
) -> Result<Paths, ShellError> {
// remove ansi coloring (?)
let pattern = {
Spanned {
item: nu_utils::strip_ansi_string_unlikely(pattern.item.clone()),
span: pattern.span,
}
};
// if there's a file with same path as the pattern, just return that.
let pp = cwd.join(&pattern.item);
let md = fs::metadata(pp);
#[allow(clippy::single_match)]
match md {
Ok(_metadata) => {
return Ok(Paths::single(&PathBuf::from(pattern.item), cwd));
}
// file not found, but also "invalid chars in file" (e.g * on Windows). Fall through and glob
Err(_) => {}
}
// user wasn't referring to a specific thing in filesystem, try to glob it.
match glob_with_parent(&pattern.item, options, cwd) {
Ok(p) => Ok(p),
Err(pat_err) => Err(ShellError::InvalidGlobPattern {
msg: pat_err.msg.into(),
span: pattern.span,
}),
}
}
#[cfg(test)]
mod test {
use super::*;
use nu_glob::GlobResult;
use nu_protocol::{Span, Spanned};
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::Playground;
use rstest::rstest;
fn spanned_string(str: &str) -> Spanned<String> {
Spanned {
item: str.to_string(),
span: Span::test_data(),
}
}
#[test]
fn does_something() {
let act = arg_glob(&spanned_string("*"), &PathBuf::from("."));
assert!(act.is_ok());
for f in act.expect("checked ok") {
match f {
Ok(p) => {
assert!(!p.to_str().unwrap().is_empty());
}
Err(e) => panic!("unexpected error {:?}", e),
};
}
}
#[test]
fn glob_format_error() {
let act = arg_glob(&spanned_string(r#"ab]c[def"#), &PathBuf::from("."));
assert!(act.is_err());
}
#[rstest]
#[case("*", 4, "no dirs")]
#[case("**/*", 7, "incl dirs")]
fn glob_subdirs(#[case] pat: &str, #[case] exp_count: usize, #[case] case: &str) {
Playground::setup("glob_subdirs", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jttxt"),
EmptyFile("andres.txt"),
]);
sandbox.mkdir(".children");
sandbox.within(".children").with_files(vec![
EmptyFile("timothy.txt"),
EmptyFile("tiffany.txt"),
EmptyFile("trish.txt"),
]);
let p: Vec<GlobResult> = arg_glob(&spanned_string(pat), &dirs.test)
.expect("no error")
.collect();
assert_eq!(
exp_count,
p.iter().filter(|i| i.is_ok()).count(),
" case: {case} ",
);
// expected behavior -- that directories are included in results (if name matches pattern)
let t = p
.iter()
.any(|i| i.as_ref().unwrap().to_string_lossy().contains(".children"));
assert!(t, "check for dir, case {case}");
})
}
#[rstest]
#[case("yehuda.txt", true, 1, "matches literal path")]
#[case("*", false, 3, "matches glob")]
#[case(r#"bad[glob.foo"#, true, 1, "matches literal, would be bad glob pat")]
fn exact_vs_glob(
#[case] pat: &str,
#[case] exp_matches_input: bool,
#[case] exp_count: usize,
#[case] case: &str,
) {
Playground::setup("exact_vs_glob", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jttxt"),
EmptyFile("bad[glob.foo"),
]);
let res = arg_glob(&spanned_string(pat), &dirs.test)
.expect("no error")
.collect::<Vec<GlobResult>>();
eprintln!("res: {:?}", res);
if exp_matches_input {
assert_eq!(
exp_count,
res.len(),
" case {case}: matches input, but count not 1? "
);
assert_eq!(
&res[0].as_ref().unwrap().to_string_lossy(),
pat, // todo: is it OK for glob to return relative paths (not to current cwd, but to arg cwd of arg_glob)?
);
} else {
assert_eq!(exp_count, res.len(), " case: {}: matched glob", case);
}
})
}
#[rstest]
#[case(r#"realbad[glob.foo"#, true, 1, "error, bad glob")]
fn exact_vs_bad_glob(
// if path doesn't exist but pattern is not valid glob, should get error.
#[case] pat: &str,
#[case] _exp_matches_input: bool,
#[case] _exp_count: usize,
#[case] _tag: &str,
) {
Playground::setup("exact_vs_bad_glob", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("yehuda.txt"),
EmptyFile("jttxt"),
EmptyFile("bad[glob.foo"),
]);
let res = arg_glob(&spanned_string(pat), &dirs.test);
assert!(res
.expect_err("expected error")
.to_string()
.contains("Invalid glob pattern"));
})
}
}

View File

@ -1,4 +1,7 @@
mod arg_glob;
pub mod formats; pub mod formats;
pub mod hook; pub mod hook;
pub mod input_handler; pub mod input_handler;
pub mod util; pub mod util;
pub use arg_glob::arg_glob;
pub use arg_glob::arg_glob_leading_dot;

View File

@ -5,7 +5,7 @@ edition = "2021"
license = "MIT" license = "MIT"
name = "nu-cmd-dataframe" name = "nu-cmd-dataframe"
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe" repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
version = "0.90.1" version = "0.88.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -13,9 +13,9 @@ version = "0.90.1"
bench = false bench = false
[dependencies] [dependencies]
nu-engine = { path = "../nu-engine", version = "0.90.1" } nu-engine = { path = "../nu-engine", version = "0.88.2" }
nu-parser = { path = "../nu-parser", version = "0.90.1" } nu-parser = { path = "../nu-parser", version = "0.88.2" }
nu-protocol = { path = "../nu-protocol", version = "0.90.1" } nu-protocol = { path = "../nu-protocol", version = "0.88.2" }
# Potential dependencies for extras # Potential dependencies for extras
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false } chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
@ -24,12 +24,11 @@ fancy-regex = "0.12"
indexmap = { version = "2.1" } indexmap = { version = "2.1" }
num = { version = "0.4", optional = true } num = { version = "0.4", optional = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sqlparser = { version = "0.43", optional = true } sqlparser = { version = "0.39", optional = true }
polars-io = { version = "0.36", features = ["avro"], optional = true } polars-io = { version = "0.35", features = ["avro"], optional = true }
polars-arrow = { version = "0.36", optional = true } polars-arrow = "0.35"
polars-ops = { version = "0.36", optional = true } polars-ops = "0.35"
polars-plan = { version = "0.36", optional = true } polars-plan = "0.35"
polars-utils = { version = "0.36", optional = true }
[dependencies.polars] [dependencies.polars]
features = [ features = [
@ -63,12 +62,12 @@ features = [
"to_dummies", "to_dummies",
] ]
optional = true optional = true
version = "0.36" version = "0.35"
[features] [features]
dataframe = ["num", "polars", "polars-io", "polars-arrow", "polars-ops", "polars-plan", "polars-utils", "sqlparser"] dataframe = ["num", "polars", "polars-io", "sqlparser"]
default = [] default = []
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.90.1" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.88.2" }
nu-test-support = { path = "../nu-test-support", version = "0.90.1" } nu-test-support = { path = "../nu-test-support", version = "0.88.2" }

View File

@ -37,27 +37,24 @@ impl Command for AppendDF {
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df); example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df);
$a | dfr append $a"#, $a | dfr append $a"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), Column::new(
Column::new( "a_x".to_string(),
"a_x".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b_x".to_string(),
"b_x".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -67,29 +64,26 @@ impl Command for AppendDF {
example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df); example: r#"let a = ([[a b]; [1 2] [3 4]] | dfr into-df);
$a | dfr append $a --col"#, $a | dfr append $a --col"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![
vec![ Value::test_int(1),
Value::test_int(1), Value::test_int(3),
Value::test_int(3), Value::test_int(1),
Value::test_int(1), Value::test_int(3),
Value::test_int(3), ],
], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![
vec![ Value::test_int(2),
Value::test_int(2), Value::test_int(4),
Value::test_int(4), Value::test_int(2),
Value::test_int(2), Value::test_int(4),
Value::test_int(4), ],
], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -116,7 +110,7 @@ fn command(
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let other: Value = call.req(engine_state, stack, 0)?; let other: Value = call.req(engine_state, stack, 0)?;
let axis = if call.has_flag(engine_state, stack, "col")? { let axis = if call.has_flag("col") {
Axis::Column Axis::Column
} else { } else {
Axis::Row Axis::Row

View File

@ -35,13 +35,10 @@ impl Command for DropDF {
description: "drop column a", description: "drop column a",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr drop a", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr drop a",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -46,19 +46,16 @@ impl Command for DropDuplicates {
description: "drop duplicates", description: "drop duplicates",
example: "[[a b]; [1 2] [3 4] [1 2]] | dfr into-df | dfr drop-duplicates", example: "[[a b]; [1 2] [3 4] [1 2]] | dfr into-df | dfr drop-duplicates",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(3), Value::test_int(1)],
vec![Value::test_int(3), Value::test_int(1)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(4), Value::test_int(2)],
vec![Value::test_int(4), Value::test_int(2)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -95,7 +92,7 @@ fn command(
let subset_slice = subset.as_ref().map(|cols| &cols[..]); let subset_slice = subset.as_ref().map(|cols| &cols[..]);
let keep_strategy = if call.has_flag(engine_state, stack, "last")? { let keep_strategy = if call.has_flag("last") {
UniqueKeepStrategy::Last UniqueKeepStrategy::Last
} else { } else {
UniqueKeepStrategy::First UniqueKeepStrategy::First

View File

@ -43,23 +43,20 @@ impl Command for DropNulls {
let a = ($df | dfr with-column $res --name res); let a = ($df | dfr with-column $res --name res);
$a | dfr drop-nulls"#, $a | dfr drop-nulls"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(1)],
vec![Value::test_int(1), Value::test_int(1)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(2)],
vec![Value::test_int(2), Value::test_int(2)], ),
), Column::new(
Column::new( "res".to_string(),
"res".to_string(), vec![Value::test_int(1), Value::test_int(1)],
vec![Value::test_int(1), Value::test_int(1)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -69,18 +66,15 @@ impl Command for DropNulls {
example: r#"let s = ([1 2 0 0 3 4] | dfr into-df); example: r#"let s = ([1 2 0 0 3 4] | dfr into-df);
($s / $s) | dfr drop-nulls"#, ($s / $s) | dfr drop-nulls"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "div_0_0".to_string(),
"div_0_0".to_string(), vec![
vec![ Value::test_int(1),
Value::test_int(1), Value::test_int(1),
Value::test_int(1), Value::test_int(1),
Value::test_int(1), Value::test_int(1),
Value::test_int(1), ],
], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -31,19 +31,16 @@ impl Command for DataTypes {
description: "Dataframe dtypes", description: "Dataframe dtypes",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr dtypes", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr dtypes",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "column".to_string(),
"column".to_string(), vec![Value::test_string("a"), Value::test_string("b")],
vec![Value::test_string("a"), Value::test_string("b")], ),
), Column::new(
Column::new( "dtype".to_string(),
"dtype".to_string(), vec![Value::test_string("i64"), Value::test_string("i64")],
vec![Value::test_string("i64"), Value::test_string("i64")], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -82,7 +79,6 @@ fn command(
.dtype(); .dtype();
let dtype_str = dtype.to_string(); let dtype_str = dtype.to_string();
dtypes.push(Value::string(dtype_str, call.head)); dtypes.push(Value::string(dtype_str, call.head));
Value::string(*v, call.head) Value::string(*v, call.head)
@ -92,7 +88,7 @@ fn command(
let names_col = Column::new("column".to_string(), names); let names_col = Column::new("column".to_string(), names);
let dtypes_col = Column::new("dtype".to_string(), dtypes); let dtypes_col = Column::new("dtype".to_string(), dtypes);
NuDataFrame::try_from_columns(vec![names_col, dtypes_col], None) NuDataFrame::try_from_columns(vec![names_col, dtypes_col])
.map(|df| PipelineData::Value(df.into_value(call.head), None)) .map(|df| PipelineData::Value(df.into_value(call.head), None))
} }

View File

@ -1,5 +1,4 @@
use super::super::values::NuDataFrame; use super::super::values::NuDataFrame;
use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
@ -79,12 +78,12 @@ impl Command for Dummies {
} }
fn command( fn command(
engine_state: &EngineState, _engine_state: &EngineState,
stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let drop_first: bool = call.has_flag(engine_state, stack, "drop-first")?; let drop_first: bool = call.has_flag("drop-first");
let df = NuDataFrame::try_from_pipeline(input, call.head)?; let df = NuDataFrame::try_from_pipeline(input, call.head)?;
df.as_ref() df.as_ref()

View File

@ -43,13 +43,10 @@ impl Command for FilterWith {
example: r#"let mask = ([true false] | dfr into-df); example: r#"let mask = ([true false] | dfr into-df);
[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with $mask"#, [[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with $mask"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(1)]),
Column::new("a".to_string(), vec![Value::test_int(1)]), Column::new("b".to_string(), vec![Value::test_int(2)]),
Column::new("b".to_string(), vec![Value::test_int(2)]), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -58,13 +55,10 @@ impl Command for FilterWith {
description: "Filter dataframe using an expression", description: "Filter dataframe using an expression",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with ((dfr col a) > 1)", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with ((dfr col a) > 1)",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(3)]),
Column::new("a".to_string(), vec![Value::test_int(3)]), Column::new("b".to_string(), vec![Value::test_int(4)]),
Column::new("b".to_string(), vec![Value::test_int(4)]), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -44,13 +44,10 @@ impl Command for FirstDF {
description: "Return the first row of a dataframe", description: "Return the first row of a dataframe",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(1)]),
Column::new("a".to_string(), vec![Value::test_int(1)]), Column::new("b".to_string(), vec![Value::test_int(2)]),
Column::new("b".to_string(), vec![Value::test_int(2)]), ])
],
None,
)
.expect("should not fail") .expect("should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -59,19 +56,16 @@ impl Command for FirstDF {
description: "Return the first two rows of a dataframe", description: "Return the first two rows of a dataframe",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first 2", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr first 2",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), ])
],
None,
)
.expect("should not fail") .expect("should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -36,13 +36,10 @@ impl Command for GetDF {
description: "Returns the selected column", description: "Returns the selected column",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr get a", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr get a",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -40,13 +40,10 @@ impl Command for LastDF {
description: "Create new dataframe with last rows", description: "Create new dataframe with last rows",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(3)]),
Column::new("a".to_string(), vec![Value::test_int(3)]), Column::new("b".to_string(), vec![Value::test_int(4)]),
Column::new("b".to_string(), vec![Value::test_int(4)]), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -106,7 +106,7 @@ impl Command for MeltDF {
Value::test_string("c"), Value::test_string("c"),
], ],
), ),
], None) ])
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -15,7 +15,6 @@ mod open;
mod query_df; mod query_df;
mod rename; mod rename;
mod sample; mod sample;
mod schema;
mod shape; mod shape;
mod slice; mod slice;
mod sql_context; mod sql_context;
@ -50,10 +49,10 @@ pub use melt::MeltDF;
pub use query_df::QueryDf; pub use query_df::QueryDf;
pub use rename::RenameDF; pub use rename::RenameDF;
pub use sample::SampleDF; pub use sample::SampleDF;
pub use schema::SchemaDF;
pub use shape::ShapeDF; pub use shape::ShapeDF;
pub use slice::SliceDF; pub use slice::SliceDF;
pub use sql_context::SQLContext; pub use sql_context::SQLContext;
pub use sql_expr::parse_sql_expr;
pub use summary::Summary; pub use summary::Summary;
pub use take::TakeDF; pub use take::TakeDF;
pub use to_arrow::ToArrow; pub use to_arrow::ToArrow;
@ -95,7 +94,6 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
QueryDf, QueryDf,
RenameDF, RenameDF,
SampleDF, SampleDF,
SchemaDF,
ShapeDF, ShapeDF,
SliceDF, SliceDF,
TakeDF, TakeDF,

View File

@ -1,5 +1,3 @@
use crate::dataframe::values::NuSchema;
use super::super::values::{NuDataFrame, NuLazyFrame}; use super::super::values::{NuDataFrame, NuLazyFrame};
use nu_engine::CallExt; use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
@ -72,12 +70,6 @@ impl Command for OpenDataFrame {
"Columns to be selected from csv file. CSV and Parquet file", "Columns to be selected from csv file. CSV and Parquet file",
None, None,
) )
.named(
"schema",
SyntaxShape::Record(vec![]),
r#"Polars Schema in format [{name: str}]. CSV, JSON, and JSONL files"#,
Some('s')
)
.input_output_type(Type::Any, Type::Custom("dataframe".into())) .input_output_type(Type::Any, Type::Custom("dataframe".into()))
.category(Category::Custom("dataframe".into())) .category(Category::Custom("dataframe".into()))
} }
@ -147,7 +139,7 @@ fn from_parquet(
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
if call.has_flag(engine_state, stack, "lazy")? { if call.has_flag("lazy") {
let file: String = call.req(engine_state, stack, 0)?; let file: String = call.req(engine_state, stack, 0)?;
let args = ScanArgsParquet { let args = ScanArgsParquet {
n_rows: None, n_rows: None,
@ -246,7 +238,7 @@ fn from_ipc(
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
if call.has_flag(engine_state, stack, "lazy")? { if call.has_flag("lazy") {
let file: String = call.req(engine_state, stack, 0)?; let file: String = call.req(engine_state, stack, 0)?;
let args = ScanArgsIpc { let args = ScanArgsIpc {
n_rows: None, n_rows: None,
@ -313,19 +305,10 @@ fn from_json(
help: None, help: None,
inner: vec![], inner: vec![],
})?; })?;
let maybe_schema = call
.get_flag(engine_state, stack, "schema")?
.map(|schema| NuSchema::try_from(&schema))
.transpose()?;
let buf_reader = BufReader::new(file); let buf_reader = BufReader::new(file);
let reader = JsonReader::new(buf_reader); let reader = JsonReader::new(buf_reader);
let reader = match maybe_schema {
Some(schema) => reader.with_schema(schema.into()),
None => reader,
};
let df: NuDataFrame = reader let df: NuDataFrame = reader
.finish() .finish()
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
@ -346,10 +329,6 @@ fn from_jsonl(
call: &Call, call: &Call,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?; let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
let maybe_schema = call
.get_flag(engine_state, stack, "schema")?
.map(|schema| NuSchema::try_from(&schema))
.transpose()?;
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?; let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let file = File::open(&file.item).map_err(|e| ShellError::GenericError { let file = File::open(&file.item).map_err(|e| ShellError::GenericError {
error: "Error opening file".into(), error: "Error opening file".into(),
@ -364,11 +343,6 @@ fn from_jsonl(
.with_json_format(JsonFormat::JsonLines) .with_json_format(JsonFormat::JsonLines)
.infer_schema_len(infer_schema); .infer_schema_len(infer_schema);
let reader = match maybe_schema {
Some(schema) => reader.with_schema(schema.into()),
None => reader,
};
let df: NuDataFrame = reader let df: NuDataFrame = reader
.finish() .finish()
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
@ -389,17 +363,12 @@ fn from_csv(
call: &Call, call: &Call,
) -> Result<Value, ShellError> { ) -> Result<Value, ShellError> {
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?; let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
let no_header: bool = call.has_flag(engine_state, stack, "no-header")?; let no_header: bool = call.has_flag("no-header");
let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?; let infer_schema: Option<usize> = call.get_flag(engine_state, stack, "infer-schema")?;
let skip_rows: Option<usize> = call.get_flag(engine_state, stack, "skip-rows")?; let skip_rows: Option<usize> = call.get_flag(engine_state, stack, "skip-rows")?;
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?; let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
let maybe_schema = call if call.has_flag("lazy") {
.get_flag(engine_state, stack, "schema")?
.map(|schema| NuSchema::try_from(&schema))
.transpose()?;
if call.has_flag(engine_state, stack, "lazy")? {
let file: String = call.req(engine_state, stack, 0)?; let file: String = call.req(engine_state, stack, 0)?;
let csv_reader = LazyCsvReader::new(file); let csv_reader = LazyCsvReader::new(file);
@ -426,11 +395,6 @@ fn from_csv(
let csv_reader = csv_reader.has_header(!no_header); let csv_reader = csv_reader.has_header(!no_header);
let csv_reader = match maybe_schema {
Some(schema) => csv_reader.with_schema(Some(schema.into())),
None => csv_reader,
};
let csv_reader = match infer_schema { let csv_reader = match infer_schema {
None => csv_reader, None => csv_reader,
Some(r) => csv_reader.with_infer_schema_length(Some(r)), Some(r) => csv_reader.with_infer_schema_length(Some(r)),
@ -488,11 +452,6 @@ fn from_csv(
let csv_reader = csv_reader.has_header(!no_header); let csv_reader = csv_reader.has_header(!no_header);
let csv_reader = match maybe_schema {
Some(schema) => csv_reader.with_schema(Some(schema.into())),
None => csv_reader,
};
let csv_reader = match infer_schema { let csv_reader = match infer_schema {
None => csv_reader, None => csv_reader,
Some(r) => csv_reader.infer_schema(Some(r)), Some(r) => csv_reader.infer_schema(Some(r)),

View File

@ -44,13 +44,10 @@ impl Command for QueryDf {
description: "Query dataframe using SQL", description: "Query dataframe using SQL",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr query 'select a from df'", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr query 'select a from df'",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -46,18 +46,15 @@ impl Command for RenameDF {
description: "Renames a series", description: "Renames a series",
example: "[5 6 7 8] | dfr into-df | dfr rename '0' new_name", example: "[5 6 7 8] | dfr into-df | dfr rename '0' new_name",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "new_name".to_string(),
"new_name".to_string(), vec![
vec![ Value::test_int(5),
Value::test_int(5), Value::test_int(6),
Value::test_int(6), Value::test_int(7),
Value::test_int(7), Value::test_int(8),
Value::test_int(8), ],
], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -66,19 +63,16 @@ impl Command for RenameDF {
description: "Renames a dataframe column", description: "Renames a dataframe column",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename a a_new", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename a a_new",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a_new".to_string(),
"a_new".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -87,19 +81,16 @@ impl Command for RenameDF {
description: "Renames two dataframe columns", description: "Renames two dataframe columns",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename [a b] [a_new b_new]", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename [a b] [a_new b_new]",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a_new".to_string(),
"a_new".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b_new".to_string(),
"b_new".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -88,8 +88,8 @@ fn command(
let seed: Option<u64> = call let seed: Option<u64> = call
.get_flag::<i64>(engine_state, stack, "seed")? .get_flag::<i64>(engine_state, stack, "seed")?
.map(|val| val as u64); .map(|val| val as u64);
let replace: bool = call.has_flag(engine_state, stack, "replace")?; let replace: bool = call.has_flag("replace");
let shuffle: bool = call.has_flag(engine_state, stack, "shuffle")?; let shuffle: bool = call.has_flag("shuffle");
let df = NuDataFrame::try_from_pipeline(input, call.head)?; let df = NuDataFrame::try_from_pipeline(input, call.head)?;

View File

@ -1,119 +0,0 @@
use super::super::values::NuDataFrame;
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, Record, ShellError, Signature, Span, Type, Value,
};
#[derive(Clone)]
pub struct SchemaDF;
impl Command for SchemaDF {
fn name(&self) -> &str {
"dfr schema"
}
fn usage(&self) -> &str {
"Show schema for a dataframe."
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.switch("datatype-list", "creates a lazy dataframe", Some('l'))
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("dataframe".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Dataframe schema",
example: r#"[[a b]; [1 "foo"] [3 "bar"]] | dfr into-df | dfr schema"#,
result: Some(Value::record(
Record::from_raw_cols_vals_unchecked(
vec!["a".to_string(), "b".to_string()],
vec![
Value::string("i64", Span::test_data()),
Value::string("str", Span::test_data()),
],
),
Span::test_data(),
)),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
if call.has_flag(engine_state, stack, "datatype-list")? {
Ok(PipelineData::Value(datatype_list(Span::unknown()), None))
} else {
command(engine_state, stack, call, input)
}
}
}
fn command(
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let schema = df.schema();
let value: Value = schema.into();
Ok(PipelineData::Value(value, None))
}
fn datatype_list(span: Span) -> Value {
let types: Vec<Value> = [
("null", ""),
("bool", ""),
("u8", ""),
("u16", ""),
("u32", ""),
("u64", ""),
("i8", ""),
("i16", ""),
("i32", ""),
("i64", ""),
("f32", ""),
("f64", ""),
("str", ""),
("binary", ""),
("date", ""),
("datetime<time_unit: (ms, us, ns) timezone (optional)>", "Time Unit can be: milliseconds: ms, microseconds: us, nanoseconds: ns. Timezone wildcard is *. Other Timezone examples: UTC, America/Los_Angeles."),
("duration<time_unit: (ms, us, ns)>", "Time Unit can be: milliseconds: ms, microseconds: us, nanoseconds: ns."),
("time", ""),
("object", ""),
("unknown", ""),
("list<dtype>", ""),
]
.iter()
.map(|(dtype, note)| {
Value::record(Record::from_raw_cols_vals_unchecked(
vec!["dtype".to_string(), "note".to_string()],
vec![Value::string(*dtype, span), Value::string(*note, span)],
),span)
})
.collect();
Value::list(types, span)
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new(SchemaDF {})])
}
}

View File

@ -34,13 +34,10 @@ impl Command for ShapeDF {
description: "Shows row and column shape", description: "Shows row and column shape",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr shape", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr shape",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("rows".to_string(), vec![Value::test_int(2)]),
Column::new("rows".to_string(), vec![Value::test_int(2)]), Column::new("columns".to_string(), vec![Value::test_int(2)]),
Column::new("columns".to_string(), vec![Value::test_int(2)]), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -73,7 +70,7 @@ fn command(
let rows_col = Column::new("rows".to_string(), vec![rows]); let rows_col = Column::new("rows".to_string(), vec![rows]);
let cols_col = Column::new("columns".to_string(), vec![cols]); let cols_col = Column::new("columns".to_string(), vec![cols]);
NuDataFrame::try_from_columns(vec![rows_col, cols_col], None) NuDataFrame::try_from_columns(vec![rows_col, cols_col])
.map(|df| PipelineData::Value(df.into_value(call.head), None)) .map(|df| PipelineData::Value(df.into_value(call.head), None))
} }

View File

@ -37,13 +37,10 @@ impl Command for SliceDF {
description: "Create new dataframe from a slice of the rows", description: "Create new dataframe from a slice of the rows",
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr slice 0 1", example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr slice 0 1",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(1)]),
Column::new("a".to_string(), vec![Value::test_int(1)]), Column::new("b".to_string(), vec![Value::test_int(2)]),
Column::new("b".to_string(), vec![Value::test_int(2)]), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -13,7 +13,7 @@ fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
| SQLDataType::Uuid | SQLDataType::Uuid
| SQLDataType::Clob(_) | SQLDataType::Clob(_)
| SQLDataType::Text | SQLDataType::Text
| SQLDataType::String(_) => DataType::String, | SQLDataType::String(_) => DataType::Utf8,
SQLDataType::Float(_) => DataType::Float32, SQLDataType::Float(_) => DataType::Float32,
SQLDataType::Real => DataType::Float32, SQLDataType::Real => DataType::Float32,
SQLDataType::Double => DataType::Float64, SQLDataType::Double => DataType::Float64,
@ -62,9 +62,7 @@ fn binary_op_(left: Expr, right: Expr, op: &SQLBinaryOperator) -> Result<Expr> {
SQLBinaryOperator::Multiply => left * right, SQLBinaryOperator::Multiply => left * right,
SQLBinaryOperator::Divide => left / right, SQLBinaryOperator::Divide => left / right,
SQLBinaryOperator::Modulo => left % right, SQLBinaryOperator::Modulo => left % right,
SQLBinaryOperator::StringConcat => { SQLBinaryOperator::StringConcat => left.cast(DataType::Utf8) + right.cast(DataType::Utf8),
left.cast(DataType::String) + right.cast(DataType::String)
}
SQLBinaryOperator::Gt => left.gt(right), SQLBinaryOperator::Gt => left.gt(right),
SQLBinaryOperator::Lt => left.lt(right), SQLBinaryOperator::Lt => left.lt(right),
SQLBinaryOperator::GtEq => left.gt_eq(right), SQLBinaryOperator::GtEq => left.gt_eq(right),

View File

@ -10,7 +10,7 @@ use polars::{
chunked_array::ChunkedArray, chunked_array::ChunkedArray,
prelude::{ prelude::{
AnyValue, DataFrame, DataType, Float64Type, IntoSeries, NewChunkedArray, AnyValue, DataFrame, DataType, Float64Type, IntoSeries, NewChunkedArray,
QuantileInterpolOptions, Series, StringType, QuantileInterpolOptions, Series, Utf8Type,
}, },
}; };
@ -46,56 +46,53 @@ impl Command for Summary {
description: "list dataframe descriptives", description: "list dataframe descriptives",
example: "[[a b]; [1 1] [1 1]] | dfr into-df | dfr summary", example: "[[a b]; [1 1] [1 1]] | dfr into-df | dfr summary",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "descriptor".to_string(),
"descriptor".to_string(), vec![
vec![ Value::test_string("count"),
Value::test_string("count"), Value::test_string("sum"),
Value::test_string("sum"), Value::test_string("mean"),
Value::test_string("mean"), Value::test_string("median"),
Value::test_string("median"), Value::test_string("std"),
Value::test_string("std"), Value::test_string("min"),
Value::test_string("min"), Value::test_string("25%"),
Value::test_string("25%"), Value::test_string("50%"),
Value::test_string("50%"), Value::test_string("75%"),
Value::test_string("75%"), Value::test_string("max"),
Value::test_string("max"), ],
], ),
), Column::new(
Column::new( "a (i64)".to_string(),
"a (i64)".to_string(), vec![
vec![ Value::test_float(2.0),
Value::test_float(2.0), Value::test_float(2.0),
Value::test_float(2.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(0.0),
Value::test_float(0.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), ],
], ),
), Column::new(
Column::new( "b (i64)".to_string(),
"b (i64)".to_string(), vec![
vec![ Value::test_float(2.0),
Value::test_float(2.0), Value::test_float(2.0),
Value::test_float(2.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(0.0),
Value::test_float(0.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), Value::test_float(1.0),
Value::test_float(1.0), ],
], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -174,7 +171,7 @@ fn command(
let df = NuDataFrame::try_from_pipeline(input, call.head)?; let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let names = ChunkedArray::<StringType>::from_slice_options("descriptor", &labels).into_series(); let names = ChunkedArray::<Utf8Type>::from_slice_options("descriptor", &labels).into_series();
let head = std::iter::once(names); let head = std::iter::once(names);
@ -182,50 +179,42 @@ fn command(
.as_ref() .as_ref()
.get_columns() .get_columns()
.iter() .iter()
.filter(|col| !matches!(col.dtype(), &DataType::Object("object", _))) .filter(|col| col.dtype() != &DataType::Object("object"))
.map(|col| { .map(|col| {
let count = col.len() as f64; let count = col.len() as f64;
let sum = col.sum_as_series().ok().and_then(|series| { let sum = col
series .sum_as_series()
.cast(&DataType::Float64) .cast(&DataType::Float64)
.ok() .ok()
.and_then(|ca| match ca.get(0) { .and_then(|ca| match ca.get(0) {
Ok(AnyValue::Float64(v)) => Some(v), Ok(AnyValue::Float64(v)) => Some(v),
_ => None, _ => None,
}) });
});
let mean = match col.mean_as_series().get(0) { let mean = match col.mean_as_series().get(0) {
Ok(AnyValue::Float64(v)) => Some(v), Ok(AnyValue::Float64(v)) => Some(v),
_ => None, _ => None,
}; };
let median = match col.median_as_series() { let median = match col.median_as_series().get(0) {
Ok(v) => match v.get(0) { Ok(AnyValue::Float64(v)) => Some(v),
Ok(AnyValue::Float64(v)) => Some(v),
_ => None,
},
_ => None, _ => None,
}; };
let std = match col.std_as_series(0) { let std = match col.std_as_series(0).get(0) {
Ok(v) => match v.get(0) { Ok(AnyValue::Float64(v)) => Some(v),
Ok(AnyValue::Float64(v)) => Some(v),
_ => None,
},
_ => None, _ => None,
}; };
let min = col.min_as_series().ok().and_then(|series| { let min = col
series .min_as_series()
.cast(&DataType::Float64) .cast(&DataType::Float64)
.ok() .ok()
.and_then(|ca| match ca.get(0) { .and_then(|ca| match ca.get(0) {
Ok(AnyValue::Float64(v)) => Some(v), Ok(AnyValue::Float64(v)) => Some(v),
_ => None, _ => None,
}) });
});
let mut quantiles = quantiles let mut quantiles = quantiles
.clone() .clone()
@ -241,15 +230,14 @@ fn command(
}) })
.collect::<Vec<Option<f64>>>(); .collect::<Vec<Option<f64>>>();
let max = col.max_as_series().ok().and_then(|series| { let max = col
series .max_as_series()
.cast(&DataType::Float64) .cast(&DataType::Float64)
.ok() .ok()
.and_then(|ca| match ca.get(0) { .and_then(|ca| match ca.get(0) {
Ok(AnyValue::Float64(v)) => Some(v), Ok(AnyValue::Float64(v)) => Some(v),
_ => None, _ => None,
}) });
});
let mut descriptors = vec![Some(count), sum, mean, median, std, min]; let mut descriptors = vec![Some(count), sum, mean, median, std, min];
descriptors.append(&mut quantiles); descriptors.append(&mut quantiles);

View File

@ -44,19 +44,16 @@ impl Command for TakeDF {
let indices = ([0 2] | dfr into-df); let indices = ([0 2] | dfr into-df);
$df | dfr take $indices"#, $df | dfr take $indices"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(4), Value::test_int(4)],
vec![Value::test_int(4), Value::test_int(4)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -67,13 +64,10 @@ impl Command for TakeDF {
let indices = ([0 2] | dfr into-df); let indices = ([0 2] | dfr into-df);
$series | dfr take $indices"#, $series | dfr take $indices"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![Value::test_int(4), Value::test_int(5)],
vec![Value::test_int(4), Value::test_int(5)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -70,7 +70,7 @@ fn command(
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?; let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?; let delimiter: Option<Spanned<String>> = call.get_flag(engine_state, stack, "delimiter")?;
let no_header: bool = call.has_flag(engine_state, stack, "no-header")?; let no_header: bool = call.has_flag("no-header");
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?; let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;

View File

@ -1,14 +1,10 @@
use crate::dataframe::values::NuSchema;
use super::super::values::{Column, NuDataFrame}; use super::super::values::{Column, NuDataFrame};
use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, ShellError, Signature, Span, Type, Value,
}; };
use polars::prelude::*;
#[derive(Clone)] #[derive(Clone)]
pub struct ToDataFrame; pub struct ToDataFrame;
@ -24,12 +20,6 @@ impl Command for ToDataFrame {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.named(
"schema",
SyntaxShape::Record(vec![]),
r#"Polars Schema in format [{name: str}]. CSV, JSON, and JSONL files"#,
Some('s'),
)
.input_output_type(Type::Any, Type::Custom("dataframe".into())) .input_output_type(Type::Any, Type::Custom("dataframe".into()))
.category(Category::Custom("dataframe".into())) .category(Category::Custom("dataframe".into()))
} }
@ -40,19 +30,16 @@ impl Command for ToDataFrame {
description: "Takes a dictionary and creates a dataframe", description: "Takes a dictionary and creates a dataframe",
example: "[[a b];[1 2] [3 4]] | dfr into-df", example: "[[a b];[1 2] [3 4]] | dfr into-df",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -61,27 +48,24 @@ impl Command for ToDataFrame {
description: "Takes a list of tables and creates a dataframe", description: "Takes a list of tables and creates a dataframe",
example: "[[1 2 a] [3 4 b] [5 6 c]] | dfr into-df", example: "[[1 2 a] [3 4 b] [5 6 c]] | dfr into-df",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "0".to_string(),
"0".to_string(), vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)],
vec![Value::test_int(1), Value::test_int(3), Value::test_int(5)], ),
), Column::new(
Column::new( "1".to_string(),
"1".to_string(), vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)],
vec![Value::test_int(2), Value::test_int(4), Value::test_int(6)], ),
), Column::new(
Column::new( "2".to_string(),
"2".to_string(), vec![
vec![ Value::test_string("a"),
Value::test_string("a"), Value::test_string("b"),
Value::test_string("b"), Value::test_string("c"),
Value::test_string("c"), ],
], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -90,17 +74,14 @@ impl Command for ToDataFrame {
description: "Takes a list and creates a dataframe", description: "Takes a list and creates a dataframe",
example: "[a b c] | dfr into-df", example: "[a b c] | dfr into-df",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![
vec![ Value::test_string("a"),
Value::test_string("a"), Value::test_string("b"),
Value::test_string("b"), Value::test_string("c"),
Value::test_string("c"), ],
], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -109,41 +90,14 @@ impl Command for ToDataFrame {
description: "Takes a list of booleans and creates a dataframe", description: "Takes a list of booleans and creates a dataframe",
example: "[true true false] | dfr into-df", example: "[true true false] | dfr into-df",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![
vec![ Value::test_bool(true),
Value::test_bool(true), Value::test_bool(true),
Value::test_bool(true), Value::test_bool(false),
Value::test_bool(false), ],
], )])
)],
None,
)
.expect("simple df for test should not fail")
.into_value(Span::test_data()),
),
},
Example {
description: "Convert to a dataframe and provide a schema",
example: "{a: 1, b: {a: [1 2 3]}, c: [a b c]}| dfr into-df -s {a: u8, b: {a: list<u64>}, c: list<str>}",
result: Some(
NuDataFrame::try_from_series(vec![
Series::new("a", &[1u8]),
{
let dtype = DataType::Struct(vec![Field::new("a", DataType::List(Box::new(DataType::UInt64)))]);
let vals = vec![AnyValue::StructOwned(
Box::new((vec![AnyValue::List(Series::new("a", &[1u64, 2, 3]))], vec![Field::new("a", DataType::String)]))); 1];
Series::from_any_values_and_dtype("b", &vals, &dtype, false)
.expect("Struct series should not fail")
},
{
let dtype = DataType::List(Box::new(DataType::String));
let vals = vec![AnyValue::List(Series::new("c", &["a", "b", "c"]))];
Series::from_any_values_and_dtype("c", &vals, &dtype, false)
.expect("List series should not fail")
}
], Span::test_data())
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -153,17 +107,12 @@ impl Command for ToDataFrame {
fn run( fn run(
&self, &self,
engine_state: &EngineState, _engine_state: &EngineState,
stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let maybe_schema = call NuDataFrame::try_from_iter(input.into_iter())
.get_flag(engine_state, stack, "schema")?
.map(|schema| NuSchema::try_from(&schema))
.transpose()?;
NuDataFrame::try_from_iter(input.into_iter(), maybe_schema)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
} }
} }

View File

@ -100,7 +100,7 @@ fn dataframe_command(
input: Value, input: Value,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let rows: Option<usize> = call.get_flag(engine_state, stack, "rows")?; let rows: Option<usize> = call.get_flag(engine_state, stack, "rows")?;
let tail: bool = call.has_flag(engine_state, stack, "tail")?; let tail: bool = call.has_flag("tail");
let df = NuDataFrame::try_from_value(input)?; let df = NuDataFrame::try_from_value(input)?;

View File

@ -42,23 +42,20 @@ impl Command for WithColumn {
| dfr into-df | dfr into-df
| dfr with-column ([5 6] | dfr into-df) --name c"#, | dfr with-column ([5 6] | dfr into-df) --name c"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), Column::new(
Column::new( "c".to_string(),
"c".to_string(), vec![Value::test_int(5), Value::test_int(6)],
vec![Value::test_int(5), Value::test_int(6)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -73,27 +70,24 @@ impl Command for WithColumn {
] ]
| dfr collect"#, | dfr collect"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), Column::new(
Column::new( "c".to_string(),
"c".to_string(), vec![Value::test_int(2), Value::test_int(6)],
vec![Value::test_int(2), Value::test_int(6)], ),
), Column::new(
Column::new( "d".to_string(),
"d".to_string(), vec![Value::test_int(3), Value::test_int(9)],
vec![Value::test_int(3), Value::test_int(9)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -32,13 +32,10 @@ impl Command for ExprArgWhere {
example: "let df = ([[a b]; [one 1] [two 2] [three 3]] | dfr into-df); example: "let df = ([[a b]; [one 1] [two 2] [three 3]] | dfr into-df);
$df | dfr select (dfr arg-where ((dfr col b) >= 2) | dfr as b_arg)", $df | dfr select (dfr arg-where ((dfr col b) >= 2) | dfr as b_arg)",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "b_arg".to_string(),
"b_arg".to_string(), vec![Value::test_int(1), Value::test_int(2)],
vec![Value::test_int(1), Value::test_int(2)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -41,30 +41,27 @@ impl Command for ExprConcatStr {
example: r#"let df = ([[a b c]; [one two 1] [three four 2]] | dfr into-df); example: r#"let df = ([[a b c]; [one two 1] [three four 2]] | dfr into-df);
$df | dfr with-column ((dfr concat-str "-" [(dfr col a) (dfr col b) ((dfr col c) * 2)]) | dfr as concat)"#, $df | dfr with-column ((dfr concat-str "-" [(dfr col a) (dfr col b) ((dfr col c) * 2)]) | dfr as concat)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("three")],
vec![Value::test_string("one"), Value::test_string("three")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_string("two"), Value::test_string("four")],
vec![Value::test_string("two"), Value::test_string("four")], ),
), Column::new(
Column::new( "c".to_string(),
"c".to_string(), vec![Value::test_int(1), Value::test_int(2)],
vec![Value::test_int(1), Value::test_int(2)], ),
), Column::new(
Column::new( "concat".to_string(),
"concat".to_string(), vec![
vec![ Value::test_string("one-two-2"),
Value::test_string("one-two-2"), Value::test_string("three-four-4"),
Value::test_string("three-four-4"), ],
], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -9,11 +9,6 @@ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
Value, Value,
}; };
use polars::{
datatypes::{DataType, TimeUnit},
prelude::NamedFrom,
series::Series,
};
#[derive(Clone)] #[derive(Clone)]
pub struct ExprDatePart; pub struct ExprDatePart;
@ -52,13 +47,10 @@ impl Command for ExprDatePart {
description: "Creates an expression to capture the year date part", description: "Creates an expression to capture the year date part",
example: r#"[["2021-12-30T01:02:03.123456789"]] | dfr into-df | dfr as-datetime "%Y-%m-%dT%H:%M:%S.%9f" | dfr with-column [(dfr col datetime | dfr datepart year | dfr as datetime_year )]"#, example: r#"[["2021-12-30T01:02:03.123456789"]] | dfr into-df | dfr as-datetime "%Y-%m-%dT%H:%M:%S.%9f" | dfr with-column [(dfr col datetime | dfr datepart year | dfr as datetime_year )]"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("datetime".to_string(), vec![Value::test_date(dt)]),
Column::new("datetime".to_string(), vec![Value::test_date(dt)]), Column::new("datetime_year".to_string(), vec![Value::test_int(2021)]),
Column::new("datetime_year".to_string(), vec![Value::test_int(2021)]), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -74,21 +66,16 @@ impl Command for ExprDatePart {
(dfr col datetime | dfr datepart second | dfr as datetime_second ), (dfr col datetime | dfr datepart second | dfr as datetime_second ),
(dfr col datetime | dfr datepart nanosecond | dfr as datetime_ns ) ]"#, (dfr col datetime | dfr datepart nanosecond | dfr as datetime_ns ) ]"#,
result: Some( result: Some(
NuDataFrame::try_from_series( NuDataFrame::try_from_columns(vec![
vec![ Column::new("datetime".to_string(), vec![Value::test_date(dt)]),
Series::new("datetime", &[dt.timestamp_nanos_opt()]) Column::new("datetime_year".to_string(), vec![Value::test_int(2021)]),
.cast(&DataType::Datetime(TimeUnit::Nanoseconds, None)) Column::new("datetime_month".to_string(), vec![Value::test_int(12)]),
.expect("Error casting to datetime type"), Column::new("datetime_day".to_string(), vec![Value::test_int(30)]),
Series::new("datetime_year", &[2021_i64]), // i32 was coerced to i64 Column::new("datetime_hour".to_string(), vec![Value::test_int(1)]),
Series::new("datetime_month", &[12_i8]), Column::new("datetime_minute".to_string(), vec![Value::test_int(2)]),
Series::new("datetime_day", &[30_i8]), Column::new("datetime_second".to_string(), vec![Value::test_int(3)]),
Series::new("datetime_hour", &[1_i8]), Column::new("datetime_ns".to_string(), vec![Value::test_int(123456789)]),
Series::new("datetime_minute", &[2_i8]), ])
Series::new("datetime_second", &[3_i8]),
Series::new("datetime_ns", &[123456789_i64]), // i32 was coerced to i64
],
Span::test_data(),
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -179,18 +179,7 @@ macro_rules! lazy_expr_command {
let value = input.into_value(call.head); let value = input.into_value(call.head);
if NuDataFrame::can_downcast(&value) { if NuDataFrame::can_downcast(&value) {
let lazy = NuLazyFrame::try_from_value(value)?; let lazy = NuLazyFrame::try_from_value(value)?;
let lazy = NuLazyFrame::new( let lazy = NuLazyFrame::new(lazy.from_eager, lazy.into_polars().$func());
lazy.from_eager,
lazy.into_polars()
.$func()
.map_err(|e| ShellError::GenericError {
error: "Dataframe Error".into(),
msg: e.to_string(),
help: None,
span: None,
inner: vec![],
})?,
);
Ok(PipelineData::Value(lazy.into_value(call.head)?, None)) Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
} else { } else {
@ -278,18 +267,7 @@ macro_rules! lazy_expr_command {
let value = input.into_value(call.head); let value = input.into_value(call.head);
if NuDataFrame::can_downcast(&value) { if NuDataFrame::can_downcast(&value) {
let lazy = NuLazyFrame::try_from_value(value)?; let lazy = NuLazyFrame::try_from_value(value)?;
let lazy = NuLazyFrame::new( let lazy = NuLazyFrame::new(lazy.from_eager, lazy.into_polars().$func($ddof));
lazy.from_eager,
lazy.into_polars()
.$func($ddof)
.map_err(|e| ShellError::GenericError {
error: "Dataframe Error".into(),
msg: e.to_string(),
help: None,
span: None,
inner: vec![],
})?,
);
Ok(PipelineData::Value(lazy.into_value(call.head)?, None)) Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
} else { } else {
@ -407,13 +385,10 @@ lazy_expr_command!(
description: "Max value from columns in a dataframe", description: "Max value from columns in a dataframe",
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr max", example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr max",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(6)],),
Column::new("a".to_string(), vec![Value::test_int(6)],), Column::new("b".to_string(), vec![Value::test_int(4)],),
Column::new("b".to_string(), vec![Value::test_int(4)],), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -425,19 +400,16 @@ lazy_expr_command!(
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr max)"#, | dfr agg (dfr col b | dfr max)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(4), Value::test_int(1)],
vec![Value::test_int(4), Value::test_int(1)], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -458,13 +430,10 @@ lazy_expr_command!(
description: "Min value from columns in a dataframe", description: "Min value from columns in a dataframe",
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr min", example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr min",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(1)],),
Column::new("a".to_string(), vec![Value::test_int(1)],), Column::new("b".to_string(), vec![Value::test_int(1)],),
Column::new("b".to_string(), vec![Value::test_int(1)],), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -476,19 +445,16 @@ lazy_expr_command!(
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr min)"#, | dfr agg (dfr col b | dfr min)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(1)],
vec![Value::test_int(2), Value::test_int(1)], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -509,13 +475,10 @@ lazy_expr_command!(
description: "Sums all columns in a dataframe", description: "Sums all columns in a dataframe",
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sum", example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sum",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_int(11)],),
Column::new("a".to_string(), vec![Value::test_int(11)],), Column::new("b".to_string(), vec![Value::test_int(7)],),
Column::new("b".to_string(), vec![Value::test_int(7)],), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -527,19 +490,16 @@ lazy_expr_command!(
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr sum)"#, | dfr agg (dfr col b | dfr sum)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(6), Value::test_int(1)],
vec![Value::test_int(6), Value::test_int(1)], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -560,13 +520,10 @@ lazy_expr_command!(
description: "Mean value from columns in a dataframe", description: "Mean value from columns in a dataframe",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr mean", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr mean",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_float(4.0)],),
Column::new("a".to_string(), vec![Value::test_float(4.0)],), Column::new("b".to_string(), vec![Value::test_float(2.0)],),
Column::new("b".to_string(), vec![Value::test_float(2.0)],), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -578,19 +535,16 @@ lazy_expr_command!(
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr mean)"#, | dfr agg (dfr col b | dfr mean)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_float(3.0), Value::test_float(1.0)],
vec![Value::test_float(3.0), Value::test_float(1.0)], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -613,19 +567,16 @@ expr_command!(
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr median)"#, | dfr agg (dfr col b | dfr median)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_float(3.0), Value::test_float(1.0)],
vec![Value::test_float(3.0), Value::test_float(1.0)], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -645,13 +596,10 @@ lazy_expr_command!(
description: "Std value from columns in a dataframe", description: "Std value from columns in a dataframe",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr std", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr std",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_float(2.0)],),
Column::new("a".to_string(), vec![Value::test_float(2.0)],), Column::new("b".to_string(), vec![Value::test_float(0.0)],),
Column::new("b".to_string(), vec![Value::test_float(0.0)],), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -663,19 +611,16 @@ lazy_expr_command!(
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr std)"#, | dfr agg (dfr col b | dfr std)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_float(0.0), Value::test_float(0.0)],
vec![Value::test_float(0.0), Value::test_float(0.0)], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -698,13 +643,10 @@ lazy_expr_command!(
"Var value from columns in a dataframe or aggregates columns to their var value", "Var value from columns in a dataframe or aggregates columns to their var value",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr var", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr var",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_float(4.0)],),
Column::new("a".to_string(), vec![Value::test_float(4.0)],), Column::new("b".to_string(), vec![Value::test_float(0.0)],),
Column::new("b".to_string(), vec![Value::test_float(0.0)],), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -716,19 +658,16 @@ lazy_expr_command!(
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr var)"#, | dfr agg (dfr col b | dfr var)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_float(0.0), Value::test_float(0.0)],
vec![Value::test_float(0.0), Value::test_float(0.0)], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -39,31 +39,28 @@ impl Command for ExprIsIn {
example: r#"let df = ([[a b]; [one 1] [two 2] [three 3]] | dfr into-df); example: r#"let df = ([[a b]; [one 1] [two 2] [three 3]] | dfr into-df);
$df | dfr with-column (dfr col a | dfr is-in [one two] | dfr as a_in)"#, $df | dfr with-column (dfr col a | dfr is-in [one two] | dfr as a_in)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![
vec![ Value::test_string("one"),
Value::test_string("one"), Value::test_string("two"),
Value::test_string("two"), Value::test_string("three"),
Value::test_string("three"), ],
], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(2), Value::test_int(3)], ),
), Column::new(
Column::new( "a_in".to_string(),
"a_in".to_string(), vec![
vec![ Value::test_bool(true),
Value::test_bool(true), Value::test_bool(true),
Value::test_bool(true), Value::test_bool(false),
Value::test_bool(false), ],
], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -84,8 +81,7 @@ impl Command for ExprIsIn {
let list: Vec<Value> = call.req(engine_state, stack, 0)?; let list: Vec<Value> = call.req(engine_state, stack, 0)?;
let expr = NuExpression::try_from_pipeline(input, call.head)?; let expr = NuExpression::try_from_pipeline(input, call.head)?;
let values = let values = NuDataFrame::try_from_columns(vec![Column::new("list".to_string(), list)])?;
NuDataFrame::try_from_columns(vec![Column::new("list".to_string(), list)], None)?;
let list = values.as_series(call.head)?; let list = values.as_series(call.head)?;
if matches!(list.dtype(), DataType::Object(..)) { if matches!(list.dtype(), DataType::Object(..)) {

View File

@ -54,27 +54,24 @@ impl Command for ExprOtherwise {
) )
| dfr collect"#, | dfr collect"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(6), Value::test_int(1), Value::test_int(4)],
vec![Value::test_int(6), Value::test_int(1), Value::test_int(4)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4), Value::test_int(1)],
vec![Value::test_int(2), Value::test_int(4), Value::test_int(1)], ),
), Column::new(
Column::new( "c".to_string(),
"c".to_string(), vec![Value::test_int(4), Value::test_int(5), Value::test_int(4)],
vec![Value::test_int(4), Value::test_int(5), Value::test_int(4)], ),
), Column::new(
Column::new( "d".to_string(),
"d".to_string(), vec![Value::test_int(10), Value::test_int(6), Value::test_int(0)],
vec![Value::test_int(10), Value::test_int(6), Value::test_int(0)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -41,19 +41,16 @@ impl Command for ExprQuantile {
| dfr group-by a | dfr group-by a
| dfr agg (dfr col b | dfr quantile 0.5)"#, | dfr agg (dfr col b | dfr quantile 0.5)"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_string("one"), Value::test_string("two")],
vec![Value::test_string("one"), Value::test_string("two")], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_float(4.0), Value::test_float(1.0)],
vec![Value::test_float(4.0), Value::test_float(1.0)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -62,27 +62,24 @@ impl Command for ExprWhen {
) )
| dfr collect"#, | dfr collect"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(6), Value::test_int(1), Value::test_int(4)],
vec![Value::test_int(6), Value::test_int(1), Value::test_int(4)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4), Value::test_int(1)],
vec![Value::test_int(2), Value::test_int(4), Value::test_int(1)], ),
), Column::new(
Column::new( "c".to_string(),
"c".to_string(), vec![Value::test_int(4), Value::test_int(5), Value::test_int(4)],
vec![Value::test_int(4), Value::test_int(5), Value::test_int(4)], ),
), Column::new(
Column::new( "d".to_string(),
"d".to_string(), vec![Value::test_int(10), Value::test_int(6), Value::test_int(0)],
vec![Value::test_int(10), Value::test_int(6), Value::test_int(0)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -47,27 +47,24 @@ impl Command for LazyAggregate {
(dfr col b | dfr sum | dfr as "b_sum") (dfr col b | dfr sum | dfr as "b_sum")
]"#, ]"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(2)],
vec![Value::test_int(1), Value::test_int(2)], ),
), Column::new(
Column::new( "b_min".to_string(),
"b_min".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), Column::new(
Column::new( "b_max".to_string(),
"b_max".to_string(), vec![Value::test_int(4), Value::test_int(6)],
vec![Value::test_int(4), Value::test_int(6)], ),
), Column::new(
Column::new( "b_sum".to_string(),
"b_sum".to_string(), vec![Value::test_int(6), Value::test_int(10)],
vec![Value::test_int(6), Value::test_int(10)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -84,27 +81,24 @@ impl Command for LazyAggregate {
] ]
| dfr collect"#, | dfr collect"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(2)],
vec![Value::test_int(1), Value::test_int(2)], ),
), Column::new(
Column::new( "b_min".to_string(),
"b_min".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), Column::new(
Column::new( "b_max".to_string(),
"b_max".to_string(), vec![Value::test_int(4), Value::test_int(6)],
vec![Value::test_int(4), Value::test_int(6)], ),
), Column::new(
Column::new( "b_sum".to_string(),
"b_sum".to_string(), vec![Value::test_int(6), Value::test_int(10)],
vec![Value::test_int(6), Value::test_int(10)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -166,7 +160,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
| polars::prelude::AggExpr::Last(e) | polars::prelude::AggExpr::Last(e)
| polars::prelude::AggExpr::Mean(e) | polars::prelude::AggExpr::Mean(e)
| polars::prelude::AggExpr::Implode(e) | polars::prelude::AggExpr::Implode(e)
| polars::prelude::AggExpr::Count(e, _) | polars::prelude::AggExpr::Count(e)
| polars::prelude::AggExpr::Sum(e) | polars::prelude::AggExpr::Sum(e)
| polars::prelude::AggExpr::AggGroups(e) | polars::prelude::AggExpr::AggGroups(e)
| polars::prelude::AggExpr::Std(e, _) | polars::prelude::AggExpr::Std(e, _)

View File

@ -33,19 +33,16 @@ impl Command for LazyCollect {
description: "drop duplicates", description: "drop duplicates",
example: "[[a b]; [1 2] [3 4]] | dfr into-lazy | dfr collect", example: "[[a b]; [1 2] [3 4]] | dfr into-lazy | dfr collect",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(3)],
vec![Value::test_int(1), Value::test_int(3)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -69,7 +69,7 @@ impl Command for LazyExplode {
Value::test_string("Skiing"), Value::test_string("Skiing"),
Value::test_string("Football"), Value::test_string("Football"),
]), ]),
], None).expect("simple df for test should not fail") ]).expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
) )
}, },
@ -86,7 +86,7 @@ impl Command for LazyExplode {
Value::test_string("Skiing"), Value::test_string("Skiing"),
Value::test_string("Football"), Value::test_string("Football"),
]), ]),
], None).expect("simple df for test should not fail") ]).expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
}, },

View File

@ -38,19 +38,16 @@ impl Command for LazyFetch {
description: "Fetch a rows from the dataframe", description: "Fetch a rows from the dataframe",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr fetch 2", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr fetch 2",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(6), Value::test_int(4)],
vec![Value::test_int(6), Value::test_int(4)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(2)],
vec![Value::test_int(2), Value::test_int(2)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -38,19 +38,16 @@ impl Command for LazyFillNA {
description: "Fills the NaN values with 0", description: "Fills the NaN values with 0",
example: "[1 2 NaN 3 NaN] | dfr into-df | dfr fill-nan 0", example: "[1 2 NaN 3 NaN] | dfr into-df | dfr fill-nan 0",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![
vec![ Value::test_int(1),
Value::test_int(1), Value::test_int(2),
Value::test_int(2), Value::test_int(0),
Value::test_int(0), Value::test_int(3),
Value::test_int(3), Value::test_int(0),
Value::test_int(0), ],
], )])
)],
None,
)
.expect("Df for test should not fail") .expect("Df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -59,19 +56,16 @@ impl Command for LazyFillNA {
description: "Fills the NaN values of a whole dataframe", description: "Fills the NaN values of a whole dataframe",
example: "[[a b]; [0.2 1] [0.1 NaN]] | dfr into-df | dfr fill-nan 0", example: "[[a b]; [0.2 1] [0.1 NaN]] | dfr into-df | dfr fill-nan 0",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_float(0.2), Value::test_float(0.1)],
vec![Value::test_float(0.2), Value::test_float(0.1)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(1), Value::test_int(0)],
vec![Value::test_int(1), Value::test_int(0)], ),
), ])
],
None,
)
.expect("Df for test should not fail") .expect("Df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -129,7 +123,7 @@ impl Command for LazyFillNA {
}) })
.collect::<Vec<Column>>(); .collect::<Vec<Column>>();
Ok(PipelineData::Value( Ok(PipelineData::Value(
NuDataFrame::try_from_columns(dataframe, None)?.into_value(call.head), NuDataFrame::try_from_columns(dataframe)?.into_value(call.head),
None, None,
)) ))
} }

View File

@ -37,19 +37,16 @@ impl Command for LazyFillNull {
description: "Fills the null values by 0", description: "Fills the null values by 0",
example: "[1 2 2 3 3] | dfr into-df | dfr shift 2 | dfr fill-null 0", example: "[1 2 2 3 3] | dfr into-df | dfr shift 2 | dfr fill-null 0",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![
vec![ Value::test_int(0),
Value::test_int(0), Value::test_int(0),
Value::test_int(0), Value::test_int(1),
Value::test_int(1), Value::test_int(2),
Value::test_int(2), Value::test_int(2),
Value::test_int(2), ],
], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -38,19 +38,16 @@ impl Command for LazyFilter {
description: "Filter dataframe using an expression", description: "Filter dataframe using an expression",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr filter ((dfr col a) >= 4)", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr filter ((dfr col a) >= 4)",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(6), Value::test_int(4)],
vec![Value::test_int(6), Value::test_int(4)], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(2)],
vec![Value::test_int(2), Value::test_int(2)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -71,7 +71,7 @@ Example {
Value::test_string("Skiing"), Value::test_string("Skiing"),
Value::test_string("Football"), Value::test_string("Football"),
]), ]),
], None).expect("simple df for test should not fail") ]).expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
) )
}, },
@ -88,7 +88,7 @@ Example {
Value::test_string("Skiing"), Value::test_string("Skiing"),
Value::test_string("Football"), Value::test_string("Football"),
]), ]),
], None).expect("simple df for test should not fail") ]).expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
}, },

View File

@ -46,27 +46,24 @@ impl Command for ToLazyGroupBy {
(dfr col b | dfr sum | dfr as "b_sum") (dfr col b | dfr sum | dfr as "b_sum")
]"#, ]"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(2)],
vec![Value::test_int(1), Value::test_int(2)], ),
), Column::new(
Column::new( "b_min".to_string(),
"b_min".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), Column::new(
Column::new( "b_max".to_string(),
"b_max".to_string(), vec![Value::test_int(4), Value::test_int(6)],
vec![Value::test_int(4), Value::test_int(6)], ),
), Column::new(
Column::new( "b_sum".to_string(),
"b_sum".to_string(), vec![Value::test_int(6), Value::test_int(10)],
vec![Value::test_int(6), Value::test_int(10)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -83,27 +80,24 @@ impl Command for ToLazyGroupBy {
] ]
| dfr collect"#, | dfr collect"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(1), Value::test_int(2)],
vec![Value::test_int(1), Value::test_int(2)], ),
), Column::new(
Column::new( "b_min".to_string(),
"b_min".to_string(), vec![Value::test_int(2), Value::test_int(4)],
vec![Value::test_int(2), Value::test_int(4)], ),
), Column::new(
Column::new( "b_max".to_string(),
"b_max".to_string(), vec![Value::test_int(4), Value::test_int(6)],
vec![Value::test_int(4), Value::test_int(6)], ),
), Column::new(
Column::new( "b_sum".to_string(),
"b_sum".to_string(), vec![Value::test_int(6), Value::test_int(10)],
vec![Value::test_int(6), Value::test_int(10)], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -53,56 +53,53 @@ impl Command for LazyJoin {
let df_b = ([["foo" "bar" "ham"];[1 "a" "let"] [2 "c" "var"] [3 "c" "const"]] | dfr into-lazy); let df_b = ([["foo" "bar" "ham"];[1 "a" "let"] [2 "c" "var"] [3 "c" "const"]] | dfr into-lazy);
$df_a | dfr join $df_b a foo | dfr collect"#, $df_a | dfr join $df_b a foo | dfr collect"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![
vec![ Value::test_int(1),
Value::test_int(1), Value::test_int(2),
Value::test_int(2), Value::test_int(1),
Value::test_int(1), Value::test_int(1),
Value::test_int(1), ],
], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![
vec![ Value::test_string("a"),
Value::test_string("a"), Value::test_string("b"),
Value::test_string("b"), Value::test_string("c"),
Value::test_string("c"), Value::test_string("c"),
Value::test_string("c"), ],
], ),
), Column::new(
Column::new( "c".to_string(),
"c".to_string(), vec![
vec![ Value::test_int(0),
Value::test_int(0), Value::test_int(1),
Value::test_int(1), Value::test_int(2),
Value::test_int(2), Value::test_int(3),
Value::test_int(3), ],
], ),
), Column::new(
Column::new( "bar".to_string(),
"bar".to_string(), vec![
vec![ Value::test_string("a"),
Value::test_string("a"), Value::test_string("c"),
Value::test_string("c"), Value::test_string("a"),
Value::test_string("a"), Value::test_string("a"),
Value::test_string("a"), ],
], ),
), Column::new(
Column::new( "ham".to_string(),
"ham".to_string(), vec![
vec![ Value::test_string("let"),
Value::test_string("let"), Value::test_string("var"),
Value::test_string("var"), Value::test_string("let"),
Value::test_string("let"), Value::test_string("let"),
Value::test_string("let"), ],
], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -113,56 +110,53 @@ impl Command for LazyJoin {
let df_b = ([["foo" "bar" "ham"];[1 "a" "let"] [2 "c" "var"] [3 "c" "const"]] | dfr into-lazy); let df_b = ([["foo" "bar" "ham"];[1 "a" "let"] [2 "c" "var"] [3 "c" "const"]] | dfr into-lazy);
$df_a | dfr join $df_b a foo"#, $df_a | dfr join $df_b a foo"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![
vec![ Value::test_int(1),
Value::test_int(1), Value::test_int(2),
Value::test_int(2), Value::test_int(1),
Value::test_int(1), Value::test_int(1),
Value::test_int(1), ],
], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![
vec![ Value::test_string("a"),
Value::test_string("a"), Value::test_string("b"),
Value::test_string("b"), Value::test_string("c"),
Value::test_string("c"), Value::test_string("c"),
Value::test_string("c"), ],
], ),
), Column::new(
Column::new( "c".to_string(),
"c".to_string(), vec![
vec![ Value::test_int(0),
Value::test_int(0), Value::test_int(1),
Value::test_int(1), Value::test_int(2),
Value::test_int(2), Value::test_int(3),
Value::test_int(3), ],
], ),
), Column::new(
Column::new( "bar".to_string(),
"bar".to_string(), vec![
vec![ Value::test_string("a"),
Value::test_string("a"), Value::test_string("c"),
Value::test_string("c"), Value::test_string("a"),
Value::test_string("a"), Value::test_string("a"),
Value::test_string("a"), ],
], ),
), Column::new(
Column::new( "ham".to_string(),
"ham".to_string(), vec![
vec![ Value::test_string("let"),
Value::test_string("let"), Value::test_string("var"),
Value::test_string("var"), Value::test_string("let"),
Value::test_string("let"), Value::test_string("let"),
Value::test_string("let"), ],
], ),
), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -177,14 +171,14 @@ impl Command for LazyJoin {
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let left = call.has_flag(engine_state, stack, "left")?; let left = call.has_flag("left");
let outer = call.has_flag(engine_state, stack, "outer")?; let outer = call.has_flag("outer");
let cross = call.has_flag(engine_state, stack, "cross")?; let cross = call.has_flag("cross");
let how = if left { let how = if left {
JoinType::Left JoinType::Left
} else if outer { } else if outer {
JoinType::Outer { coalesce: true } JoinType::Outer
} else if cross { } else if cross {
JoinType::Cross JoinType::Cross
} else { } else {

View File

@ -112,70 +112,6 @@ macro_rules! lazy_command {
} }
} }
}; };
($command: ident, $name: expr, $desc: expr, $examples: expr, $func: ident?, $test: ident) => {
#[derive(Clone)]
pub struct $command;
impl Command for $command {
fn name(&self) -> &str {
$name
}
fn usage(&self) -> &str {
$desc
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.input_output_type(
Type::Custom("dataframe".into()),
Type::Custom("dataframe".into()),
)
.category(Category::Custom("lazyframe".into()))
}
fn examples(&self) -> Vec<Example> {
$examples
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
let lazy = NuLazyFrame::new(
lazy.from_eager,
lazy.into_polars()
.$func()
.map_err(|e| ShellError::GenericError {
error: "Dataframe Error".into(),
msg: e.to_string(),
help: None,
span: None,
inner: vec![],
})?,
);
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
}
}
#[cfg(test)]
mod $test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
#[test]
fn test_examples() {
test_dataframe(vec![Box::new($command {})])
}
}
};
} }
// LazyReverse command // LazyReverse command
@ -188,19 +124,16 @@ lazy_command!(
description: "Reverses the dataframe.", description: "Reverses the dataframe.",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new(
Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(2), Value::test_int(4), Value::test_int(6),],
vec![Value::test_int(2), Value::test_int(4), Value::test_int(6),], ),
), Column::new(
Column::new( "b".to_string(),
"b".to_string(), vec![Value::test_int(2), Value::test_int(2), Value::test_int(2),],
vec![Value::test_int(2), Value::test_int(2), Value::test_int(2),], ),
), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -234,17 +167,14 @@ lazy_command!(
description: "Median value from columns in a dataframe", description: "Median value from columns in a dataframe",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr median", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr median",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_float(4.0)],),
Column::new("a".to_string(), vec![Value::test_float(4.0)],), Column::new("b".to_string(), vec![Value::test_float(2.0)],),
Column::new("b".to_string(), vec![Value::test_float(2.0)],), ])
],
None
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
},], },],
median?, median,
test_median test_median
); );

View File

@ -38,13 +38,10 @@ impl Command for LazyQuantile {
description: "quantile value from columns in a dataframe", description: "quantile value from columns in a dataframe",
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr quantile 0.5", example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr quantile 0.5",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![
vec![ Column::new("a".to_string(), vec![Value::test_float(4.0)]),
Column::new("a".to_string(), vec![Value::test_float(4.0)]), Column::new("b".to_string(), vec![Value::test_float(2.0)]),
Column::new("b".to_string(), vec![Value::test_float(2.0)]), ])
],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -65,14 +62,7 @@ impl Command for LazyQuantile {
let lazy = NuLazyFrame::new( let lazy = NuLazyFrame::new(
lazy.from_eager, lazy.from_eager,
lazy.into_polars() lazy.into_polars()
.quantile(lit(quantile), QuantileInterpolOptions::default()) .quantile(lit(quantile), QuantileInterpolOptions::default()),
.map_err(|e| ShellError::GenericError {
error: "Dataframe Error".into(),
msg: e.to_string(),
help: None,
span: None,
inner: vec![],
})?,
); );
Ok(PipelineData::Value(lazy.into_value(call.head)?, None)) Ok(PipelineData::Value(lazy.into_value(call.head)?, None))

View File

@ -37,13 +37,10 @@ impl Command for LazySelect {
description: "Select a column from the dataframe", description: "Select a column from the dataframe",
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr select a", example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr select a",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "a".to_string(),
"a".to_string(), vec![Value::test_int(6), Value::test_int(4), Value::test_int(2)],
vec![Value::test_int(6), Value::test_int(4), Value::test_int(2)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -60,7 +60,7 @@ impl Command for LazySortBy {
"b".to_string(), "b".to_string(),
vec![Value::test_int(4), Value::test_int(1), Value::test_int(2)], vec![Value::test_int(4), Value::test_int(1), Value::test_int(2)],
), ),
], None) ])
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -89,7 +89,7 @@ impl Command for LazySortBy {
Value::test_int(2), Value::test_int(2),
], ],
), ),
], None) ])
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -107,8 +107,8 @@ impl Command for LazySortBy {
let vals: Vec<Value> = call.rest(engine_state, stack, 0)?; let vals: Vec<Value> = call.rest(engine_state, stack, 0)?;
let value = Value::list(vals, call.head); let value = Value::list(vals, call.head);
let expressions = NuExpression::extract_exprs(value)?; let expressions = NuExpression::extract_exprs(value)?;
let nulls_last = call.has_flag(engine_state, stack, "nulls-last")?; let nulls_last = call.has_flag("nulls-last");
let maintain_order = call.has_flag(engine_state, stack, "maintain-order")?; let maintain_order = call.has_flag("maintain-order");
let reverse: Option<Vec<bool>> = call.get_flag(engine_state, stack, "reverse")?; let reverse: Option<Vec<bool>> = call.get_flag(engine_state, stack, "reverse")?;
let reverse = match reverse { let reverse = match reverse {

View File

@ -1,12 +1,9 @@
use crate::dataframe::values::NuSchema;
use super::super::values::{NuDataFrame, NuLazyFrame}; use super::super::values::{NuDataFrame, NuLazyFrame};
use nu_engine::CallExt;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, Category, Example, PipelineData, ShellError, Signature, Type, Value,
}; };
#[derive(Clone)] #[derive(Clone)]
@ -23,12 +20,6 @@ impl Command for ToLazyFrame {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build(self.name()) Signature::build(self.name())
.named(
"schema",
SyntaxShape::Record(vec![]),
r#"Polars Schema in format [{name: str}]. CSV, JSON, and JSONL files"#,
Some('s'),
)
.input_output_type(Type::Any, Type::Custom("dataframe".into())) .input_output_type(Type::Any, Type::Custom("dataframe".into()))
.category(Category::Custom("lazyframe".into())) .category(Category::Custom("lazyframe".into()))
} }
@ -43,17 +34,12 @@ impl Command for ToLazyFrame {
fn run( fn run(
&self, &self,
engine_state: &EngineState, _engine_state: &EngineState,
stack: &mut Stack, _stack: &mut Stack,
call: &Call, call: &Call,
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let maybe_schema = call let df = NuDataFrame::try_from_iter(input.into_iter())?;
.get_flag(engine_state, stack, "schema")?
.map(|schema| NuSchema::try_from(&schema))
.transpose()?;
let df = NuDataFrame::try_from_iter(input.into_iter(), maybe_schema)?;
let lazy = NuLazyFrame::from_dataframe(df); let lazy = NuLazyFrame::from_dataframe(df);
let value = Value::custom_value(Box::new(lazy), call.head); let value = Value::custom_value(Box::new(lazy), call.head);

View File

@ -33,13 +33,10 @@ impl Command for AllFalse {
description: "Returns true if all values are false", description: "Returns true if all values are false",
example: "[false false false] | dfr into-df | dfr all-false", example: "[false false false] | dfr into-df | dfr all-false",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "all_false".to_string(),
"all_false".to_string(), vec![Value::test_bool(true)],
vec![Value::test_bool(true)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -50,13 +47,10 @@ impl Command for AllFalse {
let res = ($s > 9); let res = ($s > 9);
$res | dfr all-false"#, $res | dfr all-false"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "all_false".to_string(),
"all_false".to_string(), vec![Value::test_bool(false)],
vec![Value::test_bool(false)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -94,11 +88,8 @@ fn command(
let value = Value::bool(!bool.any(), call.head); let value = Value::bool(!bool.any(), call.head);
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new("all_false".to_string(), vec![value])])
vec![Column::new("all_false".to_string(), vec![value])], .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
None,
)
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
} }
#[cfg(test)] #[cfg(test)]

View File

@ -33,13 +33,10 @@ impl Command for AllTrue {
description: "Returns true if all values are true", description: "Returns true if all values are true",
example: "[true true true] | dfr into-df | dfr all-true", example: "[true true true] | dfr into-df | dfr all-true",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "all_true".to_string(),
"all_true".to_string(), vec![Value::test_bool(true)],
vec![Value::test_bool(true)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -50,13 +47,10 @@ impl Command for AllTrue {
let res = ($s > 9); let res = ($s > 9);
$res | dfr all-true"#, $res | dfr all-true"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "all_true".to_string(),
"all_true".to_string(), vec![Value::test_bool(false)],
vec![Value::test_bool(false)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -94,7 +88,7 @@ fn command(
let value = Value::bool(bool.all(), call.head); let value = Value::bool(bool.all(), call.head);
NuDataFrame::try_from_columns(vec![Column::new("all_true".to_string(), vec![value])], None) NuDataFrame::try_from_columns(vec![Column::new("all_true".to_string(), vec![value])])
.map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None)) .map(|df| PipelineData::Value(NuDataFrame::into_value(df, call.head), None))
} }

View File

@ -37,10 +37,10 @@ impl Command for ArgMax {
description: "Returns index for max value", description: "Returns index for max value",
example: "[1 3 2] | dfr into-df | dfr arg-max", example: "[1 3 2] | dfr into-df | dfr arg-max",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new("arg_max".to_string(), vec![Value::test_int(1)])], "arg_max".to_string(),
None, vec![Value::test_int(1)],
) )])
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -37,10 +37,10 @@ impl Command for ArgMin {
description: "Returns index for min value", description: "Returns index for min value",
example: "[1 3 2] | dfr into-df | dfr arg-min", example: "[1 3 2] | dfr into-df | dfr arg-min",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new("arg_min".to_string(), vec![Value::test_int(0)])], "arg_min".to_string(),
None, vec![Value::test_int(0)],
) )])
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -69,19 +69,16 @@ impl Command for Cumulative {
description: "Cumulative sum for a series", description: "Cumulative sum for a series",
example: "[1 2 3 4 5] | dfr into-df | dfr cumulative sum", example: "[1 2 3 4 5] | dfr into-df | dfr cumulative sum",
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0_cumulative_sum".to_string(),
"0_cumulative_sum".to_string(), vec![
vec![ Value::test_int(1),
Value::test_int(1), Value::test_int(3),
Value::test_int(3), Value::test_int(6),
Value::test_int(6), Value::test_int(10),
Value::test_int(10), Value::test_int(15),
Value::test_int(15), ],
], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -106,12 +103,12 @@ fn command(
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let cum_type: Spanned<String> = call.req(engine_state, stack, 0)?; let cum_type: Spanned<String> = call.req(engine_state, stack, 0)?;
let reverse = call.has_flag(engine_state, stack, "reverse")?; let reverse = call.has_flag("reverse");
let df = NuDataFrame::try_from_pipeline(input, call.head)?; let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?; let series = df.as_series(call.head)?;
if let DataType::Object(..) = series.dtype() { if let DataType::Object(_) = series.dtype() {
return Err(ShellError::GenericError { return Err(ShellError::GenericError {
error: "Found object series".into(), error: "Found object series".into(),
msg: "Series of type object cannot be used for cumulative operation".into(), msg: "Series of type object cannot be used for cumulative operation".into(),

View File

@ -6,7 +6,7 @@ use nu_protocol::{
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type,
}; };
use polars::prelude::{IntoSeries, StringMethods}; use polars::prelude::{IntoSeries, Utf8Methods};
#[derive(Clone)] #[derive(Clone)]
pub struct AsDate; pub struct AsDate;
@ -64,11 +64,11 @@ fn command(
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let format: String = call.req(engine_state, stack, 0)?; let format: String = call.req(engine_state, stack, 0)?;
let not_exact = call.has_flag(engine_state, stack, "not-exact")?; let not_exact = call.has_flag("not-exact");
let df = NuDataFrame::try_from_pipeline(input, call.head)?; let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?; let series = df.as_series(call.head)?;
let casted = series.str().map_err(|e| ShellError::GenericError { let casted = series.utf8().map_err(|e| ShellError::GenericError {
error: "Error casting to string".into(), error: "Error casting to string".into(),
msg: e.to_string(), msg: e.to_string(),
span: Some(call.head), span: Some(call.head),

View File

@ -7,7 +7,7 @@ use nu_protocol::{
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
}; };
use polars::prelude::{IntoSeries, StringMethods, TimeUnit}; use polars::prelude::{IntoSeries, TimeUnit, Utf8Methods};
#[derive(Clone)] #[derive(Clone)]
pub struct AsDateTime; pub struct AsDateTime;
@ -53,30 +53,27 @@ impl Command for AsDateTime {
description: "Converts string to datetime", description: "Converts string to datetime",
example: r#"["2021-12-30 00:00:00" "2021-12-31 00:00:00"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S""#, example: r#"["2021-12-30 00:00:00" "2021-12-31 00:00:00"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S""#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "datetime".to_string(),
"datetime".to_string(), vec![
vec![ Value::date(
Value::date( DateTime::parse_from_str(
DateTime::parse_from_str( "2021-12-30 00:00:00 +0000",
"2021-12-30 00:00:00 +0000", "%Y-%m-%d %H:%M:%S %z",
"%Y-%m-%d %H:%M:%S %z", )
) .expect("date calculation should not fail in test"),
.expect("date calculation should not fail in test"), Span::test_data(),
Span::test_data(), ),
), Value::date(
Value::date( DateTime::parse_from_str(
DateTime::parse_from_str( "2021-12-31 00:00:00 +0000",
"2021-12-31 00:00:00 +0000", "%Y-%m-%d %H:%M:%S %z",
"%Y-%m-%d %H:%M:%S %z", )
) .expect("date calculation should not fail in test"),
.expect("date calculation should not fail in test"), Span::test_data(),
Span::test_data(), ),
), ],
], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -85,30 +82,27 @@ impl Command for AsDateTime {
description: "Converts string to datetime with high resolutions", description: "Converts string to datetime with high resolutions",
example: r#"["2021-12-30 00:00:00.123456789" "2021-12-31 00:00:00.123456789"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S.%9f""#, example: r#"["2021-12-30 00:00:00.123456789" "2021-12-31 00:00:00.123456789"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S.%9f""#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "datetime".to_string(),
"datetime".to_string(), vec![
vec![ Value::date(
Value::date( DateTime::parse_from_str(
DateTime::parse_from_str( "2021-12-30 00:00:00.123456789 +0000",
"2021-12-30 00:00:00.123456789 +0000", "%Y-%m-%d %H:%M:%S.%9f %z",
"%Y-%m-%d %H:%M:%S.%9f %z", )
) .expect("date calculation should not fail in test"),
.expect("date calculation should not fail in test"), Span::test_data(),
Span::test_data(), ),
), Value::date(
Value::date( DateTime::parse_from_str(
DateTime::parse_from_str( "2021-12-31 00:00:00.123456789 +0000",
"2021-12-31 00:00:00.123456789 +0000", "%Y-%m-%d %H:%M:%S.%9f %z",
"%Y-%m-%d %H:%M:%S.%9f %z", )
) .expect("date calculation should not fail in test"),
.expect("date calculation should not fail in test"), Span::test_data(),
Span::test_data(), ),
), ],
], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),
@ -134,11 +128,11 @@ fn command(
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let format: String = call.req(engine_state, stack, 0)?; let format: String = call.req(engine_state, stack, 0)?;
let not_exact = call.has_flag(engine_state, stack, "not-exact")?; let not_exact = call.has_flag("not-exact");
let df = NuDataFrame::try_from_pipeline(input, call.head)?; let df = NuDataFrame::try_from_pipeline(input, call.head)?;
let series = df.as_series(call.head)?; let series = df.as_series(call.head)?;
let casted = series.str().map_err(|e| ShellError::GenericError { let casted = series.utf8().map_err(|e| ShellError::GenericError {
error: "Error casting to string".into(), error: "Error casting to string".into(),
msg: e.to_string(), msg: e.to_string(),
span: Some(call.head), span: Some(call.head),

View File

@ -35,13 +35,10 @@ impl Command for GetDay {
let df = ([$dt $dt] | dfr into-df); let df = ([$dt $dt] | dfr into-df);
$df | dfr get-day"#, $df | dfr get-day"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![Value::test_int(4), Value::test_int(4)],
vec![Value::test_int(4), Value::test_int(4)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -35,13 +35,10 @@ impl Command for GetHour {
let df = ([$dt $dt] | dfr into-df); let df = ([$dt $dt] | dfr into-df);
$df | dfr get-hour"#, $df | dfr get-hour"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![Value::test_int(16), Value::test_int(16)],
vec![Value::test_int(16), Value::test_int(16)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -35,13 +35,10 @@ impl Command for GetMinute {
let df = ([$dt $dt] | dfr into-df); let df = ([$dt $dt] | dfr into-df);
$df | dfr get-minute"#, $df | dfr get-minute"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![Value::test_int(39), Value::test_int(39)],
vec![Value::test_int(39), Value::test_int(39)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -35,13 +35,10 @@ impl Command for GetMonth {
let df = ([$dt $dt] | dfr into-df); let df = ([$dt $dt] | dfr into-df);
$df | dfr get-month"#, $df | dfr get-month"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![Value::test_int(8), Value::test_int(8)],
vec![Value::test_int(8), Value::test_int(8)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

View File

@ -35,13 +35,10 @@ impl Command for GetNanosecond {
let df = ([$dt $dt] | dfr into-df); let df = ([$dt $dt] | dfr into-df);
$df | dfr get-nanosecond"#, $df | dfr get-nanosecond"#,
result: Some( result: Some(
NuDataFrame::try_from_columns( NuDataFrame::try_from_columns(vec![Column::new(
vec![Column::new( "0".to_string(),
"0".to_string(), vec![Value::test_int(0), Value::test_int(0)],
vec![Value::test_int(0), Value::test_int(0)], )])
)],
None,
)
.expect("simple df for test should not fail") .expect("simple df for test should not fail")
.into_value(Span::test_data()), .into_value(Span::test_data()),
), ),

Some files were not shown because too many files have changed in this diff Show More