Merge branch 'main' into fix-run-external-quoting

This commit is contained in:
Devyn Cairns 2024-06-07 02:53:42 -07:00
commit 8dcbe1da14
No known key found for this signature in database
40 changed files with 756 additions and 1093 deletions

View File

@ -18,6 +18,14 @@ updates:
ignore: ignore:
- dependency-name: "*" - dependency-name: "*"
update-types: ["version-update:semver-patch"] update-types: ["version-update:semver-patch"]
groups:
# Only update polars as a whole as there are many subcrates that need to
# be updated at once. We explicitly depend on some of them, so batch their
# updates to not take up dependabot PR slots with dysfunctional PRs
polars:
patterns:
- "polars"
- "polars-*"
- package-ecosystem: "github-actions" - package-ecosystem: "github-actions"
directory: "/" directory: "/"
schedule: schedule:

View File

@ -10,4 +10,4 @@ jobs:
uses: actions/checkout@v4.1.6 uses: actions/checkout@v4.1.6
- name: Check spelling - name: Check spelling
uses: crate-ci/typos@v1.22.0 uses: crate-ci/typos@v1.22.1

26
CITATION.cff Normal file
View File

@ -0,0 +1,26 @@
cff-version: 1.2.0
title: 'Nushell'
message: >-
If you use this software and wish to cite it,
you can use the metadata from this file.
type: software
authors:
- name: "The Nushell Project Team"
identifiers:
- type: url
value: 'https://github.com/nushell/nushell'
description: Repository
repository-code: 'https://github.com/nushell/nushell'
url: 'https://www.nushell.sh/'
abstract: >-
The goal of the Nushell project is to take the Unix
philosophy of shells, where pipes connect simple commands
together, and bring it to the modern style of development.
Thus, rather than being either a shell, or a programming
language, Nushell connects both by bringing a rich
programming language and a full-featured shell together
into one package.
keywords:
- nushell
- shell
license: MIT

161
Cargo.lock generated
View File

@ -478,17 +478,6 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ada7f35ca622a86a4d6c27be2633fc6c243ecc834859628fcce0681d8e76e1c8" checksum = "ada7f35ca622a86a4d6c27be2633fc6c243ecc834859628fcce0681d8e76e1c8"
[[package]]
name = "brotli"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
"brotli-decompressor 2.5.1",
]
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "5.0.0" version = "5.0.0"
@ -497,17 +486,7 @@ checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67"
dependencies = [ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
"alloc-stdlib", "alloc-stdlib",
"brotli-decompressor 4.0.0", "brotli-decompressor",
]
[[package]]
name = "brotli-decompressor"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
] ]
[[package]] [[package]]
@ -871,7 +850,7 @@ checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
dependencies = [ dependencies = [
"crossterm", "crossterm",
"strum", "strum",
"strum_macros 0.26.2", "strum_macros",
"unicode-width", "unicode-width",
] ]
@ -1295,6 +1274,9 @@ name = "either"
version = "1.11.0" version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "eml-parser" name = "eml-parser"
@ -1794,6 +1776,7 @@ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"allocator-api2", "allocator-api2",
"rayon", "rayon",
"serde",
] ]
[[package]] [[package]]
@ -2935,7 +2918,7 @@ dependencies = [
"alphanumeric-sort", "alphanumeric-sort",
"base64 0.22.1", "base64 0.22.1",
"bracoxide", "bracoxide",
"brotli 5.0.0", "brotli",
"byteorder", "byteorder",
"bytesize", "bytesize",
"calamine", "calamine",
@ -3222,7 +3205,7 @@ dependencies = [
name = "nu-protocol" name = "nu-protocol"
version = "0.94.3" version = "0.94.3"
dependencies = [ dependencies = [
"brotli 5.0.0", "brotli",
"byte-unit", "byte-unit",
"chrono", "chrono",
"chrono-humanize", "chrono-humanize",
@ -3243,7 +3226,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"strum", "strum",
"strum_macros 0.26.2", "strum_macros",
"tempfile", "tempfile",
"thiserror", "thiserror",
"typetag", "typetag",
@ -3404,7 +3387,7 @@ dependencies = [
"polars-plan", "polars-plan",
"polars-utils", "polars-utils",
"serde", "serde",
"sqlparser 0.45.0", "sqlparser 0.47.0",
"tempfile", "tempfile",
"typetag", "typetag",
"uuid", "uuid",
@ -3731,9 +3714,9 @@ dependencies = [
[[package]] [[package]]
name = "os_pipe" name = "os_pipe"
version = "1.1.5" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -4014,9 +3997,9 @@ dependencies = [
[[package]] [[package]]
name = "polars" name = "polars"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea21b858b16b9c0e17a12db2800d11aa5b4bd182be6b3022eb537bbfc1f2db5" checksum = "e148396dca5496566880fa19374f3f789a29db94e3eb458afac1497b4bac5442"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"polars-arrow", "polars-arrow",
@ -4034,9 +4017,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-arrow" name = "polars-arrow"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "725b09f2b5ef31279b66e27bbab63c58d49d8f6696b66b1f46c7eaab95e80f75" checksum = "1cb5e11cd0752ae022fa6ca3afa50a14b0301b7ce53c0135828fbb0f4fa8303e"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"atoi", "atoi",
@ -4082,9 +4065,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-compute" name = "polars-compute"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a796945b14b14fbb79b91ef0406e6fddca2be636e889f81ea5d6ee7d36efb4fe" checksum = "89fc4578f826234cdecb782952aa9c479dc49373f81694a7b439c70b6f609ba0"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"either", "either",
@ -4098,9 +4081,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-core" name = "polars-core"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465f70d3e96b6d0b1a43c358ba451286b8c8bd56696feff020d65702aa33e35c" checksum = "e490c6bace1366a558feea33d1846f749a8ca90bd72a6748752bc65bb4710b2a"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bitflags 2.5.0", "bitflags 2.5.0",
@ -4132,9 +4115,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-error" name = "polars-error"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5224d5d05e6b8a6f78b75951ae1b5f82c8ab1979e11ffaf5fd41941e3d5b0757" checksum = "08888f58e61599b00f5ea0c2ccdc796b54b9859559cc0d4582733509451fa01a"
dependencies = [ dependencies = [
"avro-schema", "avro-schema",
"polars-arrow-format", "polars-arrow-format",
@ -4144,10 +4127,30 @@ dependencies = [
] ]
[[package]] [[package]]
name = "polars-io" name = "polars-expr"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2c8589e418cbe4a48228d64b2a8a40284a82ec3c98817c0c2bcc0267701338b" checksum = "4173591920fe56ad55af025f92eb0d08421ca85705c326a640c43856094e3484"
dependencies = [
"ahash 0.8.11",
"bitflags 2.5.0",
"once_cell",
"polars-arrow",
"polars-core",
"polars-io",
"polars-ops",
"polars-plan",
"polars-time",
"polars-utils",
"rayon",
"smartstring",
]
[[package]]
name = "polars-io"
version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5842896aea46d975b425d63f156f412aed3cfde4c257b64fb1f43ceea288074e"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"async-trait", "async-trait",
@ -4186,9 +4189,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-json" name = "polars-json"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81224492a649a12b668480c0cf219d703f432509765d2717e72fe32ad16fc701" checksum = "160cbad0145b93ac6a88639aadfa6f7d7c769d05a8674f9b7e895b398cae9901"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"chrono", "chrono",
@ -4207,9 +4210,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-lazy" name = "polars-lazy"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2632b1af668e2058d5f8f916d8fbde3cac63d03ae29a705f598e41dcfeb7f" checksum = "e805ea2ebbc6b7749b0afb31b7fc5d32b42b57ba29b984549d43d3a16114c4a5"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bitflags 2.5.0", "bitflags 2.5.0",
@ -4217,6 +4220,7 @@ dependencies = [
"once_cell", "once_cell",
"polars-arrow", "polars-arrow",
"polars-core", "polars-core",
"polars-expr",
"polars-io", "polars-io",
"polars-json", "polars-json",
"polars-ops", "polars-ops",
@ -4231,13 +4235,13 @@ dependencies = [
[[package]] [[package]]
name = "polars-ops" name = "polars-ops"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efdbdb4d9a92109bc2e0ce8e17af5ae8ab643bb5b7ee9d1d74f0aeffd1fbc95f" checksum = "7b0aed7e169c81b98457641cf82b251f52239a668916c2e683abd1f38df00d58"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"argminmax", "argminmax",
"base64 0.21.7", "base64 0.22.1",
"bytemuck", "bytemuck",
"chrono", "chrono",
"chrono-tz 0.8.6", "chrono-tz 0.8.6",
@ -4267,14 +4271,14 @@ dependencies = [
[[package]] [[package]]
name = "polars-parquet" name = "polars-parquet"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b421d2196f786fdfe162db614c8485f8308fe41575d4de634a39bbe460d1eb6a" checksum = "c70670a9e51cac66d0e77fd20b5cc957dbcf9f2660d410633862bb72f846d5b8"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"async-stream", "async-stream",
"base64 0.21.7", "base64 0.22.1",
"brotli 3.5.0", "brotli",
"ethnum", "ethnum",
"flate2", "flate2",
"futures", "futures",
@ -4293,9 +4297,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-pipe" name = "polars-pipe"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48700f1d5bd56a15451e581f465c09541492750360f18637b196f995470a015c" checksum = "0a40ae1b3c74ee07e2d1f7cbf56c5d6e15969e45d9b6f0903bd2acaf783ba436"
dependencies = [ dependencies = [
"crossbeam-channel", "crossbeam-channel",
"crossbeam-queue", "crossbeam-queue",
@ -4305,6 +4309,7 @@ dependencies = [
"polars-arrow", "polars-arrow",
"polars-compute", "polars-compute",
"polars-core", "polars-core",
"polars-expr",
"polars-io", "polars-io",
"polars-ops", "polars-ops",
"polars-plan", "polars-plan",
@ -4318,13 +4323,14 @@ dependencies = [
[[package]] [[package]]
name = "polars-plan" name = "polars-plan"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fb8e2302e20c44defd5be8cad9c96e75face63c3a5f609aced8c4ec3b3ac97d" checksum = "8daa3541ae7e9af311a4389bc2b21f83349c34c723cc67fa524cdefdaa172d90"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bytemuck", "bytemuck",
"chrono-tz 0.8.6", "chrono-tz 0.8.6",
"either",
"hashbrown 0.14.5", "hashbrown 0.14.5",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
@ -4341,15 +4347,15 @@ dependencies = [
"regex", "regex",
"serde", "serde",
"smartstring", "smartstring",
"strum_macros 0.25.3", "strum_macros",
"version_check", "version_check",
] ]
[[package]] [[package]]
name = "polars-row" name = "polars-row"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a515bdc68c2ae3702e3de70d89601f3b71ca8137e282a226dddb53ee4bacfa2e" checksum = "deb285f2f3a65b00dd06bef16bb9f712dbb5478f941dab5cf74f9f016d382e40"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"polars-arrow", "polars-arrow",
@ -4359,11 +4365,12 @@ dependencies = [
[[package]] [[package]]
name = "polars-sql" name = "polars-sql"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4bb7cc1c04c3023d1953b2f1dec50515e8fd8169a5a2bf4967b3b082232db7" checksum = "a724f699d194cb02c25124d3832f7d4d77f387f1a89ee42f6b9e88ec561d4ad9"
dependencies = [ dependencies = [
"hex", "hex",
"once_cell",
"polars-arrow", "polars-arrow",
"polars-core", "polars-core",
"polars-error", "polars-error",
@ -4377,11 +4384,12 @@ dependencies = [
[[package]] [[package]]
name = "polars-time" name = "polars-time"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efc18e3ad92eec55db89d88f16c22d436559ba7030cf76f86f6ed7a754b673f1" checksum = "87ebec238d8b6200d9f0c3ce411c8441e950bd5a7df7806b8172d06c1d5a4b97"
dependencies = [ dependencies = [
"atoi", "atoi",
"bytemuck",
"chrono", "chrono",
"chrono-tz 0.8.6", "chrono-tz 0.8.6",
"now", "now",
@ -4398,9 +4406,9 @@ dependencies = [
[[package]] [[package]]
name = "polars-utils" name = "polars-utils"
version = "0.39.2" version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c760b6c698cfe2fbbbd93d6cfb408db14ececfe1d92445dae2229ce1b5b21ae8" checksum = "34e1a907c63abf71e5f21467e2e4ff748896c28196746f631c6c25512ec6102c"
dependencies = [ dependencies = [
"ahash 0.8.11", "ahash 0.8.11",
"bytemuck", "bytemuck",
@ -4834,7 +4842,7 @@ dependencies = [
"serde_json", "serde_json",
"strip-ansi-escapes", "strip-ansi-escapes",
"strum", "strum",
"strum_macros 0.26.2", "strum_macros",
"thiserror", "thiserror",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
@ -5562,9 +5570,9 @@ dependencies = [
[[package]] [[package]]
name = "sqlparser" name = "sqlparser"
version = "0.45.0" version = "0.47.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7bbffee862a796d67959a89859d6b1046bb5016d63e23835ad0da182777bbe0" checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25"
dependencies = [ dependencies = [
"log", "log",
] ]
@ -5678,20 +5686,7 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
dependencies = [ dependencies = [
"strum_macros 0.26.2", "strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.60",
] ]
[[package]] [[package]]

View File

@ -118,7 +118,7 @@ num-traits = "0.2"
omnipath = "0.1" omnipath = "0.1"
once_cell = "1.18" once_cell = "1.18"
open = "5.1" open = "5.1"
os_pipe = { version = "1.1", features = ["io_safety"] } os_pipe = { version = "1.2", features = ["io_safety"] }
pathdiff = "0.2" pathdiff = "0.2"
percent-encoding = "2" percent-encoding = "2"
pretty_assertions = "1.4" pretty_assertions = "1.4"

View File

@ -12,14 +12,17 @@ impl Command for StorInsert {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("stor insert") Signature::build("stor insert")
.input_output_types(vec![(Type::Nothing, Type::table())]) .input_output_types(vec![
(Type::Nothing, Type::table()),
(Type::record(), Type::table()),
])
.required_named( .required_named(
"table-name", "table-name",
SyntaxShape::String, SyntaxShape::String,
"name of the table you want to insert into", "name of the table you want to insert into",
Some('t'), Some('t'),
) )
.required_named( .named(
"data-record", "data-record",
SyntaxShape::Record(vec![]), SyntaxShape::Record(vec![]),
"a record of column names and column values to insert into the specified table", "a record of column names and column values to insert into the specified table",
@ -39,10 +42,16 @@ impl Command for StorInsert {
fn examples(&self) -> Vec<Example> { fn examples(&self) -> Vec<Example> {
vec![Example { vec![Example {
description: "Insert data the in-memory sqlite database using a data-record of column-name and column-value pairs", description: "Insert data the in-memory sqlite database using a data-record of column-name and column-value pairs",
example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}", example: "stor insert --table-name nudb --data-record {bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17}",
result: None, result: None,
}] },
Example {
description: "Insert data through pipeline input as a record of column-name and column-value pairs",
example: "{bool1: true, int1: 5, float1: 1.1, str1: fdncred, datetime1: 2023-04-17} | stor insert --table-name nudb",
result: None,
},
]
} }
fn run( fn run(
@ -50,25 +59,79 @@ impl Command for StorInsert {
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 span = call.head; let span = call.head;
let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?; let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?;
let columns: Option<Record> = call.get_flag(engine_state, stack, "data-record")?; let data_record: Option<Record> = call.get_flag(engine_state, stack, "data-record")?;
// let config = engine_state.get_config(); // let config = engine_state.get_config();
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
// Check if the record is being passed as input or using the data record parameter
let columns = handle(span, data_record, input)?;
process(table_name, span, &db, columns)?; process(table_name, span, &db, columns)?;
Ok(Value::custom(db, span).into_pipeline_data()) Ok(Value::custom(db, span).into_pipeline_data())
} }
} }
fn handle(
span: Span,
data_record: Option<Record>,
input: PipelineData,
) -> Result<Record, ShellError> {
match input {
PipelineData::Empty => data_record.ok_or_else(|| ShellError::MissingParameter {
param_name: "requires a record".into(),
span,
}),
PipelineData::Value(value, ..) => {
// Since input is being used, check if the data record parameter is used too
if data_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--data-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
match value {
Value::Record { val, .. } => Ok(val.into_owned()),
val => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: val.get_type().to_string(),
dst_span: Span::unknown(),
src_span: val.span(),
}),
}
}
_ => {
if data_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--data-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: "".into(),
dst_span: span,
src_span: span,
})
}
}
}
fn process( fn process(
table_name: Option<String>, table_name: Option<String>,
span: Span, span: Span,
db: &SQLiteDatabase, db: &SQLiteDatabase,
columns: Option<Record>, record: Record,
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
if table_name.is_none() { if table_name.is_none() {
return Err(ShellError::MissingParameter { return Err(ShellError::MissingParameter {
@ -77,54 +140,45 @@ fn process(
}); });
} }
let new_table_name = table_name.unwrap_or("table".into()); let new_table_name = table_name.unwrap_or("table".into());
if let Ok(conn) = db.open_connection() { if let Ok(conn) = db.open_connection() {
match columns { let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name);
Some(record) => { let cols = record.columns();
let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name); cols.for_each(|col| {
let cols = record.columns(); create_stmt.push_str(&format!("{}, ", col));
cols.for_each(|col| { });
create_stmt.push_str(&format!("{}, ", col)); if create_stmt.ends_with(", ") {
}); create_stmt.pop();
if create_stmt.ends_with(", ") { create_stmt.pop();
create_stmt.pop(); }
create_stmt.pop();
}
// Values are set as placeholders. // Values are set as placeholders.
create_stmt.push_str(") VALUES ( "); create_stmt.push_str(") VALUES ( ");
for (index, _) in record.columns().enumerate() { for (index, _) in record.columns().enumerate() {
create_stmt.push_str(&format!("?{}, ", index + 1)); create_stmt.push_str(&format!("?{}, ", index + 1));
} }
if create_stmt.ends_with(", ") { if create_stmt.ends_with(", ") {
create_stmt.pop(); create_stmt.pop();
create_stmt.pop(); create_stmt.pop();
} }
create_stmt.push(')'); create_stmt.push(')');
// dbg!(&create_stmt); // dbg!(&create_stmt);
// Get the params from the passed values // Get the params from the passed values
let params = values_to_sql(record.values().cloned())?; let params = values_to_sql(record.values().cloned())?;
conn.execute(&create_stmt, params_from_iter(params)) conn.execute(&create_stmt, params_from_iter(params))
.map_err(|err| ShellError::GenericError { .map_err(|err| ShellError::GenericError {
error: "Failed to open SQLite connection in memory from insert".into(), error: "Failed to open SQLite connection in memory from insert".into(),
msg: err.to_string(), msg: err.to_string(),
span: Some(Span::test_data()), span: Some(Span::test_data()),
help: None, help: None,
inner: vec![], inner: vec![],
})?; })?;
} };
None => {
return Err(ShellError::MissingParameter {
param_name: "requires at least one column".into(),
span,
});
}
};
}
// dbg!(db.clone()); // dbg!(db.clone());
Ok(()) Ok(())
} }
@ -176,7 +230,7 @@ mod test {
), ),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -201,7 +255,7 @@ mod test {
Value::test_string("String With Spaces".to_string()), Value::test_string("String With Spaces".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -226,7 +280,7 @@ mod test {
Value::test_string("ThisIsALongString".to_string()), Value::test_string("ThisIsALongString".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
// SQLite uses dynamic typing, making any length acceptable for a varchar column // SQLite uses dynamic typing, making any length acceptable for a varchar column
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -251,7 +305,7 @@ mod test {
Value::test_string("ThisIsTheWrongType".to_string()), Value::test_string("ThisIsTheWrongType".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
// SQLite uses dynamic typing, making any type acceptable for a column // SQLite uses dynamic typing, making any type acceptable for a column
assert!(result.is_ok()); assert!(result.is_ok());
} }
@ -276,7 +330,7 @@ mod test {
Value::test_string("ThisIsALongString".to_string()), Value::test_string("ThisIsALongString".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_err()); assert!(result.is_err());
} }
@ -293,7 +347,7 @@ mod test {
Value::test_string("ThisIsALongString".to_string()), Value::test_string("ThisIsALongString".to_string()),
); );
let result = process(table_name, span, &db, Some(columns)); let result = process(table_name, span, &db, columns);
assert!(result.is_err()); assert!(result.is_err());
} }

View File

@ -11,14 +11,17 @@ impl Command for StorUpdate {
fn signature(&self) -> Signature { fn signature(&self) -> Signature {
Signature::build("stor update") Signature::build("stor update")
.input_output_types(vec![(Type::Nothing, Type::table())]) .input_output_types(vec![
(Type::Nothing, Type::table()),
(Type::record(), Type::table()),
])
.required_named( .required_named(
"table-name", "table-name",
SyntaxShape::String, SyntaxShape::String,
"name of the table you want to insert into", "name of the table you want to insert into",
Some('t'), Some('t'),
) )
.required_named( .named(
"update-record", "update-record",
SyntaxShape::Record(vec![]), SyntaxShape::Record(vec![]),
"a record of column names and column values to update in the specified table", "a record of column names and column values to update in the specified table",
@ -54,6 +57,11 @@ impl Command for StorUpdate {
example: "stor update --table-name nudb --update-record {str1: nushell datetime1: 2020-04-17} --where-clause \"bool1 = 1\"", example: "stor update --table-name nudb --update-record {str1: nushell datetime1: 2020-04-17} --where-clause \"bool1 = 1\"",
result: None, result: None,
}, },
Example {
description: "Update the in-memory sqlite database through pipeline input",
example: "{str1: nushell datetime1: 2020-04-17} | stor update --table-name nudb",
result: None,
},
] ]
} }
@ -62,91 +70,147 @@ impl Command for StorUpdate {
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 span = call.head; let span = call.head;
let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?; let table_name: Option<String> = call.get_flag(engine_state, stack, "table-name")?;
let columns: Option<Record> = call.get_flag(engine_state, stack, "update-record")?; let update_record: Option<Record> = call.get_flag(engine_state, stack, "update-record")?;
let where_clause_opt: Option<Spanned<String>> = let where_clause_opt: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "where-clause")?; call.get_flag(engine_state, stack, "where-clause")?;
// Open the in-mem database // Open the in-mem database
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None)); let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
if table_name.is_none() { // Check if the record is being passed as input or using the update record parameter
return Err(ShellError::MissingParameter { let columns = handle(span, update_record, input)?;
param_name: "requires at table name".into(),
span,
});
}
let new_table_name = table_name.unwrap_or("table".into());
if let Ok(conn) = db.open_connection() {
match columns {
Some(record) => {
let mut update_stmt = format!("UPDATE {} ", new_table_name);
update_stmt.push_str("SET "); process(table_name, span, &db, columns, where_clause_opt)?;
let vals = record.iter();
vals.for_each(|(key, val)| match val {
Value::Int { val, .. } => {
update_stmt.push_str(&format!("{} = {}, ", key, val));
}
Value::Float { val, .. } => {
update_stmt.push_str(&format!("{} = {}, ", key, val));
}
Value::String { val, .. } => {
update_stmt.push_str(&format!("{} = '{}', ", key, val));
}
Value::Date { val, .. } => {
update_stmt.push_str(&format!("{} = '{}', ", key, val));
}
Value::Bool { val, .. } => {
update_stmt.push_str(&format!("{} = {}, ", key, val));
}
_ => {
// return Err(ShellError::UnsupportedInput {
// msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item),
// input: "value originates from here".to_string(),
// msg_span: span,
// input_span: val.span(),
// });
}
});
if update_stmt.ends_with(", ") {
update_stmt.pop();
update_stmt.pop();
}
// Yup, this is a bit janky, but I'm not sure a better way to do this without having
// --and and --or flags as well as supporting ==, !=, <>, is null, is not null, etc.
// and other sql syntax. So, for now, just type a sql where clause as a string.
if let Some(where_clause) = where_clause_opt {
update_stmt.push_str(&format!(" WHERE {}", where_clause.item));
}
// dbg!(&update_stmt);
conn.execute(&update_stmt, [])
.map_err(|err| ShellError::GenericError {
error: "Failed to open SQLite connection in memory from update".into(),
msg: err.to_string(),
span: Some(Span::test_data()),
help: None,
inner: vec![],
})?;
}
None => {
return Err(ShellError::MissingParameter {
param_name: "requires at least one column".into(),
span: call.head,
});
}
};
}
// dbg!(db.clone());
Ok(Value::custom(db, span).into_pipeline_data()) Ok(Value::custom(db, span).into_pipeline_data())
} }
} }
fn handle(
span: Span,
update_record: Option<Record>,
input: PipelineData,
) -> Result<Record, ShellError> {
match input {
PipelineData::Empty => update_record.ok_or_else(|| ShellError::MissingParameter {
param_name: "requires a record".into(),
span,
}),
PipelineData::Value(value, ..) => {
// Since input is being used, check if the data record parameter is used too
if update_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--update-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
match value {
Value::Record { val, .. } => Ok(val.into_owned()),
val => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: val.get_type().to_string(),
dst_span: Span::unknown(),
src_span: val.span(),
}),
}
}
_ => {
if update_record.is_some() {
return Err(ShellError::GenericError {
error: "Pipeline and Flag both being used".into(),
msg: "Use either pipeline input or '--update-record' parameter".into(),
span: Some(span),
help: None,
inner: vec![],
});
}
Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "record".into(),
wrong_type: "".into(),
dst_span: span,
src_span: span,
})
}
}
}
fn process(
table_name: Option<String>,
span: Span,
db: &SQLiteDatabase,
record: Record,
where_clause_opt: Option<Spanned<String>>,
) -> Result<(), ShellError> {
if table_name.is_none() {
return Err(ShellError::MissingParameter {
param_name: "requires at table name".into(),
span,
});
}
let new_table_name = table_name.unwrap_or("table".into());
if let Ok(conn) = db.open_connection() {
let mut update_stmt = format!("UPDATE {} ", new_table_name);
update_stmt.push_str("SET ");
let vals = record.iter();
vals.for_each(|(key, val)| match val {
Value::Int { val, .. } => {
update_stmt.push_str(&format!("{} = {}, ", key, val));
}
Value::Float { val, .. } => {
update_stmt.push_str(&format!("{} = {}, ", key, val));
}
Value::String { val, .. } => {
update_stmt.push_str(&format!("{} = '{}', ", key, val));
}
Value::Date { val, .. } => {
update_stmt.push_str(&format!("{} = '{}', ", key, val));
}
Value::Bool { val, .. } => {
update_stmt.push_str(&format!("{} = {}, ", key, val));
}
_ => {
// return Err(ShellError::UnsupportedInput {
// msg: format!("{} is not a valid datepart, expected one of year, month, day, hour, minute, second, millisecond, microsecond, nanosecond", part.item),
// input: "value originates from here".to_string(),
// msg_span: span,
// input_span: val.span(),
// });
}
});
if update_stmt.ends_with(", ") {
update_stmt.pop();
update_stmt.pop();
}
// Yup, this is a bit janky, but I'm not sure a better way to do this without having
// --and and --or flags as well as supporting ==, !=, <>, is null, is not null, etc.
// and other sql syntax. So, for now, just type a sql where clause as a string.
if let Some(where_clause) = where_clause_opt {
update_stmt.push_str(&format!(" WHERE {}", where_clause.item));
}
// dbg!(&update_stmt);
conn.execute(&update_stmt, [])
.map_err(|err| ShellError::GenericError {
error: "Failed to open SQLite connection in memory from update".into(),
msg: err.to_string(),
span: Some(Span::test_data()),
help: None,
inner: vec![],
})?;
}
// dbg!(db.clone());
Ok(())
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View File

@ -27,7 +27,7 @@ impl Command for FormatDate {
SyntaxShape::String, SyntaxShape::String,
"The desired format date.", "The desired format date.",
) )
.category(Category::Date) .category(Category::Strings)
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {

View File

@ -6,6 +6,7 @@ use nu_protocol::{
}; };
use nu_system::ForegroundChild; use nu_system::ForegroundChild;
use nu_utils::IgnoreCaseExt; use nu_utils::IgnoreCaseExt;
use pathdiff::diff_paths;
use std::{ use std::{
borrow::Cow, borrow::Cow,
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
@ -300,57 +301,54 @@ fn expand_glob(
// We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct // We must use `nu_engine::glob_from` here, in order to ensure we get paths from the correct
// dir // dir
let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span); let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span);
let Ok((_prefix, paths)) = nu_engine::glob_from(&glob, cwd, span, None) else { if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) {
// If an error occurred, return the original input let mut result: Vec<OsString> = vec![];
return Ok(vec![arg.into()]);
};
// If the first component of the original `arg` string path was '.', that should be preserved for m in matches {
let relative_to_dot = Path::new(arg).starts_with("."); if nu_utils::ctrl_c::was_pressed(interrupt) {
return Err(ShellError::InterruptedByUser { span: Some(span) });
let paths = paths
// Skip over glob failures. These are usually just inaccessible paths.
.flat_map(|path_result| match path_result {
Ok(path) => Some(path),
Err(err) => {
// But internally log them just in case we need to debug this.
log::warn!("Error in run_external::expand_glob(): {}", err);
None
} }
}) if let Ok(arg) = m {
// Make the paths relative to the cwd let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd);
.map(|path| { result.push(arg.into());
path.strip_prefix(cwd)
.map(|path| path.to_owned())
.unwrap_or(path)
})
// Add './' to relative paths if the original pattern had it
.map(|path| {
if relative_to_dot && path.is_relative() {
Path::new(".").join(path)
} else { } else {
path result.push(arg.into());
} }
}) }
.map(OsString::from)
// Abandon if ctrl-c is pressed
.map(|path| {
if !nu_utils::ctrl_c::was_pressed(interrupt) {
Ok(path)
} else {
Err(ShellError::InterruptedByUser { span: Some(span) })
}
})
.collect::<Result<Vec<OsString>, ShellError>>()?;
if !paths.is_empty() { // FIXME: do we want to special-case this further? We might accidentally expand when they don't
Ok(paths) // intend to
if result.is_empty() {
result.push(arg.into());
}
Ok(result)
} else { } else {
// If we failed to match, return the original input
Ok(vec![arg.into()]) Ok(vec![arg.into()])
} }
} }
fn resolve_globbed_path_to_cwd_relative(
path: PathBuf,
prefix: Option<&PathBuf>,
cwd: &Path,
) -> PathBuf {
if let Some(prefix) = prefix {
if let Ok(remainder) = path.strip_prefix(prefix) {
let new_prefix = if let Some(pfx) = diff_paths(prefix, cwd) {
pfx
} else {
prefix.to_path_buf()
};
new_prefix.join(remainder)
} else {
path
}
} else {
path
}
}
/// Write `PipelineData` into `writer`. If `PipelineData` is not binary, it is /// Write `PipelineData` into `writer`. If `PipelineData` is not binary, it is
/// first rendered using the `table` command. /// first rendered using the `table` command.
/// ///
@ -601,10 +599,6 @@ mod test {
assert_eq!(actual, expected); assert_eq!(actual, expected);
let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap(); let actual = expand_glob("./*.txt", cwd, Span::unknown(), &None).unwrap();
let expected: Vec<OsString> = vec![
Path::new(".").join("a.txt").into(),
Path::new(".").join("b.txt").into(),
];
assert_eq!(actual, expected); assert_eq!(actual, expected);
let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap(); let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap();

View File

@ -4,7 +4,6 @@ use crate::{
views::{Orientation, RecordView}, views::{Orientation, RecordView},
}; };
use anyhow::Result; use anyhow::Result;
use nu_ansi_term::Style;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
Value, Value,
@ -19,12 +18,6 @@ pub struct TableCmd {
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
struct TableSettings { struct TableSettings {
orientation: Option<Orientation>, orientation: Option<Orientation>,
split_line_s: Option<Style>,
selected_cell_s: Option<Style>,
selected_row_s: Option<Style>,
selected_column_s: Option<Style>,
padding_column_left: Option<usize>,
padding_column_right: Option<usize>,
turn_on_cursor_mode: bool, turn_on_cursor_mode: bool,
} }
@ -64,8 +57,6 @@ impl ViewCommand for TableCmd {
let mut view = RecordView::new(columns, data); let mut view = RecordView::new(columns, data);
// todo: use setup instead ????
if is_record { if is_record {
view.set_orientation_current(Orientation::Left); view.set_orientation_current(Orientation::Left);
} }
@ -74,32 +65,6 @@ impl ViewCommand for TableCmd {
view.set_orientation_current(o); view.set_orientation_current(o);
} }
if let Some(style) = self.settings.selected_cell_s {
view.set_style_selected_cell(style);
}
if let Some(style) = self.settings.selected_column_s {
view.set_style_selected_column(style);
}
if let Some(style) = self.settings.selected_row_s {
view.set_style_selected_row(style);
}
if let Some(style) = self.settings.split_line_s {
view.set_style_split_line(style);
}
if let Some(p) = self.settings.padding_column_left {
let c = view.get_padding_column();
view.set_padding_column((p, c.1))
}
if let Some(p) = self.settings.padding_column_right {
let c = view.get_padding_column();
view.set_padding_column((c.0, p))
}
if self.settings.turn_on_cursor_mode { if self.settings.turn_on_cursor_mode {
view.set_cursor_mode(); view.set_cursor_mode();
} }

View File

@ -1,5 +1,5 @@
use super::ViewCommand; use super::ViewCommand;
use crate::views::InteractiveView; use crate::views::TryView;
use anyhow::Result; use anyhow::Result;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
@ -22,7 +22,7 @@ impl TryCmd {
} }
impl ViewCommand for TryCmd { impl ViewCommand for TryCmd {
type View = InteractiveView<'static>; type View = TryView<'static>;
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
Self::NAME Self::NAME
@ -45,7 +45,7 @@ impl ViewCommand for TryCmd {
value: Option<Value>, value: Option<Value>,
) -> Result<Self::View> { ) -> Result<Self::View> {
let value = value.unwrap_or_default(); let value = value.unwrap_or_default();
let mut view = InteractiveView::new(value); let mut view = TryView::new(value);
view.init(self.command.clone()); view.init(self.command.clone());
view.try_run(engine_state, stack)?; view.try_run(engine_state, stack)?;

View File

@ -1,13 +1,12 @@
use crate::{ use crate::{
run_pager, run_pager,
util::{create_lscolors, create_map, map_into_value}, util::{create_lscolors, create_map},
PagerConfig, StyleConfig, PagerConfig,
}; };
use nu_ansi_term::{Color, Style}; use nu_ansi_term::{Color, Style};
use nu_color_config::{get_color_map, StyleComputer}; use nu_color_config::{get_color_map, StyleComputer};
use nu_engine::command_prelude::*; use nu_engine::command_prelude::*;
use nu_protocol::Config;
use std::collections::HashMap;
/// A `less` like program to render a [`Value`] as a table. /// A `less` like program to render a [`Value`] as a table.
#[derive(Clone)] #[derive(Clone)]
@ -68,19 +67,21 @@ impl Command for Explore {
let nu_config = engine_state.get_config(); let nu_config = engine_state.get_config();
let style_computer = StyleComputer::from_config(engine_state, stack); let style_computer = StyleComputer::from_config(engine_state, stack);
let mut config = nu_config.explore.clone(); let mut explore_config = ExploreConfig::from_nu_config(nu_config);
include_nu_config(&mut config, &style_computer); explore_config.table.show_header = show_head;
update_config(&mut config, show_index, show_head); explore_config.table.show_index = show_index;
prepare_default_config(&mut config); explore_config.table.separator_style = lookup_color(&style_computer, "separator");
let style = style_from_config(&config);
let lscolors = create_lscolors(engine_state, stack); let lscolors = create_lscolors(engine_state, stack);
let mut config = PagerConfig::new(nu_config, &style_computer, &lscolors, config); let config = PagerConfig::new(
config.style = style; nu_config,
config.peek_value = peek_value; &explore_config,
config.tail = tail; &style_computer,
&lscolors,
peek_value,
tail,
);
let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config); let result = run_pager(engine_state, &mut stack.clone(), ctrlc, input, config);
@ -134,153 +135,118 @@ impl Command for Explore {
} }
} }
fn update_config(config: &mut HashMap<String, Value>, show_index: bool, show_head: bool) { #[derive(Debug, Clone)]
let mut hm = config.get("table").and_then(create_map).unwrap_or_default(); pub struct ExploreConfig {
if show_index { pub table: TableConfig,
insert_bool(&mut hm, "show_index", show_index); pub selected_cell: Style,
} pub status_info: Style,
pub status_success: Style,
if show_head { pub status_warn: Style,
insert_bool(&mut hm, "show_head", show_head); pub status_error: Style,
} pub status_bar_background: Style,
pub status_bar_text: Style,
config.insert(String::from("table"), map_into_value(hm)); pub cmd_bar_text: Style,
pub cmd_bar_background: Style,
pub highlight: Style,
/// if true, the explore view will immediately try to run the command as it is typed
pub try_reactive: bool,
} }
fn style_from_config(config: &HashMap<String, Value>) -> StyleConfig { impl Default for ExploreConfig {
let mut style = StyleConfig::default(); fn default() -> Self {
Self {
let colors = get_color_map(config); table: TableConfig::default(),
selected_cell: color(None, Some(Color::LightBlue)),
if let Some(s) = colors.get("status_bar_text") { status_info: color(None, None),
style.status_bar_text = *s; status_success: color(Some(Color::Black), Some(Color::Green)),
} status_warn: color(None, None),
status_error: color(Some(Color::White), Some(Color::Red)),
if let Some(s) = colors.get("status_bar_background") { status_bar_background: color(
style.status_bar_background = *s; Some(Color::Rgb(29, 31, 33)),
} Some(Color::Rgb(196, 201, 198)),
),
if let Some(s) = colors.get("command_bar_text") { status_bar_text: color(None, None),
style.cmd_bar_text = *s; cmd_bar_text: color(Some(Color::Rgb(196, 201, 198)), None),
} cmd_bar_background: color(None, None),
highlight: color(Some(Color::Black), Some(Color::Yellow)),
if let Some(s) = colors.get("command_bar_background") { try_reactive: false,
style.cmd_bar_background = *s;
}
if let Some(hm) = config.get("status").and_then(create_map) {
let colors = get_color_map(&hm);
if let Some(s) = colors.get("info") {
style.status_info = *s;
}
if let Some(s) = colors.get("success") {
style.status_success = *s;
}
if let Some(s) = colors.get("warn") {
style.status_warn = *s;
}
if let Some(s) = colors.get("error") {
style.status_error = *s;
} }
} }
style
} }
impl ExploreConfig {
/// take the default explore config and update it with relevant values from the nu config
pub fn from_nu_config(config: &Config) -> Self {
let mut ret = Self::default();
fn prepare_default_config(config: &mut HashMap<String, Value>) { ret.table.column_padding_left = config.table_indent.left;
const STATUS_BAR: Style = color( ret.table.column_padding_right = config.table_indent.right;
Some(Color::Rgb(29, 31, 33)),
Some(Color::Rgb(196, 201, 198)),
);
const INPUT_BAR: Style = color(Some(Color::Rgb(196, 201, 198)), None);
const HIGHLIGHT: Style = color(Some(Color::Black), Some(Color::Yellow)); let explore_cfg_hash_map = config.explore.clone();
let colors = get_color_map(&explore_cfg_hash_map);
const STATUS_ERROR: Style = color(Some(Color::White), Some(Color::Red)); if let Some(s) = colors.get("status_bar_text") {
const STATUS_INFO: Style = color(None, None); ret.status_bar_text = *s;
const STATUS_SUCCESS: Style = color(Some(Color::Black), Some(Color::Green)); }
const STATUS_WARN: Style = color(None, None);
const TABLE_SPLIT_LINE: Style = color(Some(Color::Rgb(64, 64, 64)), None); if let Some(s) = colors.get("status_bar_background") {
const TABLE_SELECT_CELL: Style = color(None, None); ret.status_bar_background = *s;
const TABLE_SELECT_ROW: Style = color(None, None); }
const TABLE_SELECT_COLUMN: Style = color(None, None);
const HEXDUMP_INDEX: Style = color(Some(Color::Cyan), None); if let Some(s) = colors.get("command_bar_text") {
const HEXDUMP_SEGMENT: Style = color(Some(Color::Cyan), None).bold(); ret.cmd_bar_text = *s;
const HEXDUMP_SEGMENT_ZERO: Style = color(Some(Color::Purple), None).bold(); }
const HEXDUMP_SEGMENT_UNKNOWN: Style = color(Some(Color::Green), None).bold();
const HEXDUMP_ASCII: Style = color(Some(Color::Cyan), None).bold();
const HEXDUMP_ASCII_ZERO: Style = color(Some(Color::Purple), None).bold();
const HEXDUMP_ASCII_UNKNOWN: Style = color(Some(Color::Green), None).bold();
insert_style(config, "status_bar_background", STATUS_BAR); if let Some(s) = colors.get("command_bar_background") {
insert_style(config, "command_bar_text", INPUT_BAR); ret.cmd_bar_background = *s;
insert_style(config, "highlight", HIGHLIGHT); }
// because how config works we need to parse a string into Value::Record if let Some(s) = colors.get("command_bar_background") {
ret.cmd_bar_background = *s;
}
{ if let Some(s) = colors.get("selected_cell") {
let mut hm = config ret.selected_cell = *s;
.get("status") }
.and_then(parse_hash_map)
.unwrap_or_default();
insert_style(&mut hm, "info", STATUS_INFO); if let Some(hm) = explore_cfg_hash_map.get("status").and_then(create_map) {
insert_style(&mut hm, "success", STATUS_SUCCESS); let colors = get_color_map(&hm);
insert_style(&mut hm, "warn", STATUS_WARN);
insert_style(&mut hm, "error", STATUS_ERROR);
config.insert(String::from("status"), map_into_value(hm)); if let Some(s) = colors.get("info") {
} ret.status_info = *s;
}
{ if let Some(s) = colors.get("success") {
let mut hm = config ret.status_success = *s;
.get("table") }
.and_then(parse_hash_map)
.unwrap_or_default();
insert_style(&mut hm, "split_line", TABLE_SPLIT_LINE); if let Some(s) = colors.get("warn") {
insert_style(&mut hm, "selected_cell", TABLE_SELECT_CELL); ret.status_warn = *s;
insert_style(&mut hm, "selected_row", TABLE_SELECT_ROW); }
insert_style(&mut hm, "selected_column", TABLE_SELECT_COLUMN);
config.insert(String::from("table"), map_into_value(hm)); if let Some(s) = colors.get("error") {
} ret.status_error = *s;
}
}
{ if let Some(hm) = explore_cfg_hash_map.get("try").and_then(create_map) {
let mut hm = config if let Some(reactive) = hm.get("reactive") {
.get("hex-dump") if let Ok(b) = reactive.as_bool() {
.and_then(create_map) ret.try_reactive = b;
.unwrap_or_default(); }
}
}
insert_style(&mut hm, "color_index", HEXDUMP_INDEX); ret
insert_style(&mut hm, "color_segment", HEXDUMP_SEGMENT);
insert_style(&mut hm, "color_segment_zero", HEXDUMP_SEGMENT_ZERO);
insert_style(&mut hm, "color_segment_unknown", HEXDUMP_SEGMENT_UNKNOWN);
insert_style(&mut hm, "color_ascii", HEXDUMP_ASCII);
insert_style(&mut hm, "color_ascii_zero", HEXDUMP_ASCII_ZERO);
insert_style(&mut hm, "color_ascii_unknown", HEXDUMP_ASCII_UNKNOWN);
insert_int(&mut hm, "segment_size", 2);
insert_int(&mut hm, "count_segments", 8);
insert_bool(&mut hm, "split", true);
config.insert(String::from("hex-dump"), map_into_value(hm));
} }
} }
fn parse_hash_map(value: &Value) -> Option<HashMap<String, Value>> { #[derive(Debug, Default, Clone, Copy)]
value.as_record().ok().map(|val| { pub struct TableConfig {
val.iter() pub separator_style: Style,
.map(|(col, val)| (col.clone(), val.clone())) pub show_index: bool,
.collect::<HashMap<_, _>>() pub show_header: bool,
}) pub column_padding_left: usize,
pub column_padding_right: usize,
} }
const fn color(foreground: Option<Color>, background: Option<Color>) -> Style { const fn color(foreground: Option<Color>, background: Option<Color>) -> Style {
@ -299,49 +265,6 @@ const fn color(foreground: Option<Color>, background: Option<Color>) -> Style {
} }
} }
fn insert_style(map: &mut HashMap<String, Value>, key: &str, value: Style) {
if map.contains_key(key) {
return;
}
if value == Style::default() {
return;
}
let value = nu_color_config::NuStyle::from(value);
if let Ok(val) = nu_json::to_string_raw(&value) {
map.insert(String::from(key), Value::string(val, Span::unknown()));
}
}
fn insert_bool(map: &mut HashMap<String, Value>, key: &str, value: bool) {
if map.contains_key(key) {
return;
}
map.insert(String::from(key), Value::bool(value, Span::unknown()));
}
fn insert_int(map: &mut HashMap<String, Value>, key: &str, value: i64) {
if map.contains_key(key) {
return;
}
map.insert(String::from(key), Value::int(value, Span::unknown()));
}
fn include_nu_config(config: &mut HashMap<String, Value>, style_computer: &StyleComputer) {
let line_color = lookup_color(style_computer, "separator");
if line_color != nu_ansi_term::Style::default() {
let mut map = config
.get("table")
.and_then(parse_hash_map)
.unwrap_or_default();
insert_style(&mut map, "split_line", line_color);
config.insert(String::from("table"), map_into_value(map));
}
}
fn lookup_color(style_computer: &StyleComputer, key: &str) -> nu_ansi_term::Style { fn lookup_color(style_computer: &StyleComputer, key: &str) -> nu_ansi_term::Style {
style_computer.compute(key, &Value::nothing(Span::unknown())) style_computer.compute(key, &Value::nothing(Span::unknown()))
} }

View File

@ -15,13 +15,13 @@ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
PipelineData, Value, PipelineData, Value,
}; };
use pager::{Page, Pager, PagerConfig, StyleConfig}; use pager::{Page, Pager, PagerConfig};
use registry::CommandRegistry; use registry::CommandRegistry;
use terminal_size::{Height, Width}; use terminal_size::{Height, Width};
use views::{BinaryView, Orientation, Preview, RecordView}; use views::{BinaryView, Orientation, Preview, RecordView};
mod util { mod util {
pub use super::nu_common::{create_lscolors, create_map, map_into_value}; pub use super::nu_common::{create_lscolors, create_map};
} }
fn run_pager( fn run_pager(

View File

@ -18,7 +18,7 @@ pub use command::run_command_with_value;
pub use lscolor::{create_lscolors, lscolorize}; pub use lscolor::{create_lscolors, lscolorize};
pub use string::{string_width, truncate_str}; pub use string::{string_width, truncate_str};
pub use table::try_build_table; pub use table::try_build_table;
pub use value::{collect_input, collect_pipeline, create_map, map_into_value}; pub use value::{collect_input, collect_pipeline, create_map};
pub fn has_simple_value(data: &[Vec<Value>]) -> Option<&Value> { pub fn has_simple_value(data: &[Vec<Value>]) -> Option<&Value> {
if data.len() == 1 if data.len() == 1

View File

@ -176,10 +176,6 @@ pub fn create_map(value: &Value) -> Option<HashMap<String, Value>> {
) )
} }
pub fn map_into_value(hm: HashMap<String, Value>) -> Value {
Value::record(hm.into_iter().collect(), NuSpan::unknown())
}
fn unknown_error_value() -> Value { fn unknown_error_value() -> Value {
Value::string(String::from(""), NuSpan::unknown()) Value::string(String::from(""), NuSpan::unknown())
} }

View File

@ -10,9 +10,9 @@ use self::{
}; };
use super::views::{Layout, View}; use super::views::{Layout, View};
use crate::{ use crate::{
nu_common::{CtrlC, NuColor, NuConfig, NuSpan, NuStyle}, explore::ExploreConfig,
nu_common::{CtrlC, NuColor, NuConfig, NuStyle},
registry::{Command, CommandRegistry}, registry::{Command, CommandRegistry},
util::map_into_value,
views::{util::nu_style_to_tui, ViewConfig}, views::{util::nu_style_to_tui, ViewConfig},
}; };
use anyhow::Result; use anyhow::Result;
@ -26,15 +26,14 @@ use crossterm::{
}; };
use events::UIEvents; use events::UIEvents;
use lscolors::LsColors; use lscolors::LsColors;
use nu_color_config::{lookup_ansi_color_style, StyleComputer}; use nu_color_config::StyleComputer;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
Record, Value, Value,
}; };
use ratatui::{backend::CrosstermBackend, layout::Rect, widgets::Block}; use ratatui::{backend::CrosstermBackend, layout::Rect, widgets::Block};
use std::{ use std::{
cmp::min, cmp::min,
collections::HashMap,
io::{self, Stdout}, io::{self, Stdout},
result, result,
sync::atomic::Ordering, sync::atomic::Ordering,
@ -42,7 +41,6 @@ use std::{
pub type Frame<'a> = ratatui::Frame<'a>; pub type Frame<'a> = ratatui::Frame<'a>;
pub type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>; pub type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
pub type ConfigMap = HashMap<String, Value>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Pager<'a> { pub struct Pager<'a> {
@ -73,19 +71,6 @@ struct CommandBuf {
cmd_exec_info: Option<String>, cmd_exec_info: Option<String>,
} }
#[derive(Debug, Default, Clone)]
pub struct StyleConfig {
pub status_info: NuStyle,
pub status_success: NuStyle,
pub status_warn: NuStyle,
pub status_error: NuStyle,
pub status_bar_background: NuStyle,
pub status_bar_text: NuStyle,
pub cmd_bar_text: NuStyle,
pub cmd_bar_background: NuStyle,
pub highlight: NuStyle,
}
impl<'a> Pager<'a> { impl<'a> Pager<'a> {
pub fn new(config: PagerConfig<'a>) -> Self { pub fn new(config: PagerConfig<'a>) -> Self {
Self { Self {
@ -100,27 +85,6 @@ impl<'a> Pager<'a> {
self.message = Some(text.into()); self.message = Some(text.into());
} }
pub fn set_config(&mut self, path: &[String], value: Value) -> bool {
let path = path.iter().map(|s| s.as_str()).collect::<Vec<_>>();
match &path[..] {
["status_bar_text"] => value_as_style(&mut self.config.style.status_bar_text, &value),
["status_bar_background"] => {
value_as_style(&mut self.config.style.status_bar_background, &value)
}
["command_bar_text"] => value_as_style(&mut self.config.style.cmd_bar_text, &value),
["command_bar_background"] => {
value_as_style(&mut self.config.style.cmd_bar_background, &value)
}
["highlight"] => value_as_style(&mut self.config.style.highlight, &value),
["status", "info"] => value_as_style(&mut self.config.style.status_info, &value),
["status", "success"] => value_as_style(&mut self.config.style.status_success, &value),
["status", "warn"] => value_as_style(&mut self.config.style.status_warn, &value),
["status", "error"] => value_as_style(&mut self.config.style.status_error, &value),
path => set_config(&mut self.config.config, path, value),
}
}
pub fn run( pub fn run(
&mut self, &mut self,
engine_state: &EngineState, engine_state: &EngineState,
@ -132,8 +96,8 @@ impl<'a> Pager<'a> {
if let Some(page) = &mut view { if let Some(page) = &mut view {
page.view.setup(ViewConfig::new( page.view.setup(ViewConfig::new(
self.config.nu_config, self.config.nu_config,
self.config.explore_config,
self.config.style_computer, self.config.style_computer,
&self.config.config,
self.config.lscolors, self.config.lscolors,
)) ))
} }
@ -153,10 +117,9 @@ pub enum Transition {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PagerConfig<'a> { pub struct PagerConfig<'a> {
pub nu_config: &'a NuConfig, pub nu_config: &'a NuConfig,
pub explore_config: &'a ExploreConfig,
pub style_computer: &'a StyleComputer<'a>, pub style_computer: &'a StyleComputer<'a>,
pub lscolors: &'a LsColors, pub lscolors: &'a LsColors,
pub config: ConfigMap,
pub style: StyleConfig,
// If true, when quitting output the value of the cell the cursor was on // If true, when quitting output the value of the cell the cursor was on
pub peek_value: bool, pub peek_value: bool,
pub tail: bool, pub tail: bool,
@ -165,18 +128,19 @@ pub struct PagerConfig<'a> {
impl<'a> PagerConfig<'a> { impl<'a> PagerConfig<'a> {
pub fn new( pub fn new(
nu_config: &'a NuConfig, nu_config: &'a NuConfig,
explore_config: &'a ExploreConfig,
style_computer: &'a StyleComputer, style_computer: &'a StyleComputer,
lscolors: &'a LsColors, lscolors: &'a LsColors,
config: ConfigMap, peek_value: bool,
tail: bool,
) -> Self { ) -> Self {
Self { Self {
nu_config, nu_config,
explore_config,
style_computer, style_computer,
config,
lscolors, lscolors,
peek_value: false, peek_value,
tail: false, tail,
style: StyleConfig::default(),
} }
} }
} }
@ -401,7 +365,7 @@ fn draw_frame(
draw_info(f, pager, info); draw_info(f, pager, info);
highlight_search_results(f, pager, layout, pager.config.style.highlight); highlight_search_results(f, pager, layout, pager.config.explore_config.highlight);
set_cursor_cmd_bar(f, area, pager); set_cursor_cmd_bar(f, area, pager);
} }
@ -411,19 +375,24 @@ fn draw_info(f: &mut Frame, pager: &mut Pager<'_>, info: ViewInfo) {
if let Some(report) = info.status { if let Some(report) = info.status {
let last_2nd_line = area.bottom().saturating_sub(2); let last_2nd_line = area.bottom().saturating_sub(2);
let area = Rect::new(area.left(), last_2nd_line, area.width, 1); let area = Rect::new(area.left(), last_2nd_line, area.width, 1);
render_status_bar(f, area, report, &pager.config.style); render_status_bar(f, area, report, pager.config.explore_config);
} }
{ {
let last_line = area.bottom().saturating_sub(1); let last_line = area.bottom().saturating_sub(1);
let area = Rect::new(area.left(), last_line, area.width, 1); let area = Rect::new(area.left(), last_line, area.width, 1);
render_cmd_bar(f, area, pager, info.report, &pager.config.style); render_cmd_bar(f, area, pager, info.report, pager.config.explore_config);
} }
} }
fn create_view_config<'a>(pager: &'a Pager<'_>) -> ViewConfig<'a> { fn create_view_config<'a>(pager: &'a Pager<'_>) -> ViewConfig<'a> {
let cfg = &pager.config; let cfg = &pager.config;
ViewConfig::new(cfg.nu_config, cfg.style_computer, &cfg.config, cfg.lscolors) ViewConfig::new(
cfg.nu_config,
cfg.explore_config,
cfg.style_computer,
cfg.lscolors,
)
} }
fn pager_run_command( fn pager_run_command(
@ -463,16 +432,7 @@ fn run_command(
let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit()); let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit());
let transition = command.react(engine_state, stack, pager, value)?; let transition = command.react(engine_state, stack, pager, value)?;
match transition { match transition {
Transition::Ok => { Transition::Ok => Ok(CmdResult::new(false, false, String::new())),
// so we basically allow a change of a config inside a command,
// and cause of this we wanna update all of our views.
//
// THOUGH: MOST LIKELY IT WON'T BE CHANGED AND WE DO A WASTE.......
update_view_stack_setup(view_stack, &pager.config);
Ok(CmdResult::new(false, false, String::new()))
}
Transition::Exit => Ok(CmdResult::new(true, false, String::new())), Transition::Exit => Ok(CmdResult::new(true, false, String::new())),
Transition::Cmd { .. } => todo!("not used so far"), Transition::Cmd { .. } => todo!("not used so far"),
} }
@ -487,7 +447,8 @@ fn run_command(
} }
} }
update_view_setup(&mut new_view, &pager.config); setup_view(&mut new_view, &pager.config);
view_stack.curr_view = Some(Page::raw(new_view, stackable)); view_stack.curr_view = Some(Page::raw(new_view, stackable));
Ok(CmdResult::new(false, true, cmd.name().to_owned())) Ok(CmdResult::new(false, true, cmd.name().to_owned()))
@ -495,18 +456,13 @@ fn run_command(
} }
} }
fn update_view_stack_setup(view_stack: &mut ViewStack, cfg: &PagerConfig<'_>) { fn setup_view(view: &mut Box<dyn View>, cfg: &PagerConfig<'_>) {
if let Some(page) = view_stack.curr_view.as_mut() { let cfg = ViewConfig::new(
update_view_setup(&mut page.view, cfg); cfg.nu_config,
} cfg.explore_config,
cfg.style_computer,
for page in &mut view_stack.stack { cfg.lscolors,
update_view_setup(&mut page.view, cfg); );
}
}
fn update_view_setup(view: &mut Box<dyn View>, cfg: &PagerConfig<'_>) {
let cfg = ViewConfig::new(cfg.nu_config, cfg.style_computer, &cfg.config, cfg.lscolors);
view.setup(cfg); view.setup(cfg);
} }
@ -528,7 +484,7 @@ fn set_cursor_cmd_bar(f: &mut Frame, area: Rect, pager: &Pager) {
} }
} }
fn render_status_bar(f: &mut Frame, area: Rect, report: Report, theme: &StyleConfig) { fn render_status_bar(f: &mut Frame, area: Rect, report: Report, theme: &ExploreConfig) {
let msg_style = report_msg_style(&report, theme, theme.status_bar_text); let msg_style = report_msg_style(&report, theme, theme.status_bar_text);
let mut status_bar = create_status_bar(report); let mut status_bar = create_status_bar(report);
status_bar.set_background_style(theme.status_bar_background); status_bar.set_background_style(theme.status_bar_background);
@ -549,11 +505,11 @@ fn create_status_bar(report: Report) -> StatusBar {
) )
} }
fn report_msg_style(report: &Report, theme: &StyleConfig, style: NuStyle) -> NuStyle { fn report_msg_style(report: &Report, config: &ExploreConfig, style: NuStyle) -> NuStyle {
if matches!(report.level, Severity::Info) { if matches!(report.level, Severity::Info) {
style style
} else { } else {
report_level_style(report.level, theme) report_level_style(report.level, config)
} }
} }
@ -562,15 +518,15 @@ fn render_cmd_bar(
area: Rect, area: Rect,
pager: &Pager, pager: &Pager,
report: Option<Report>, report: Option<Report>,
theme: &StyleConfig, config: &ExploreConfig,
) { ) {
if let Some(report) = report { if let Some(report) = report {
let style = report_msg_style(&report, theme, theme.cmd_bar_text); let style = report_msg_style(&report, config, config.cmd_bar_text);
let bar = CommandBar::new( let bar = CommandBar::new(
&report.message, &report.message,
&report.context1, &report.context1,
style, style,
theme.cmd_bar_background, config.cmd_bar_background,
); );
f.render_widget(bar, area); f.render_widget(bar, area);
@ -578,16 +534,16 @@ fn render_cmd_bar(
} }
if pager.cmd_buf.is_cmd_input { if pager.cmd_buf.is_cmd_input {
render_cmd_bar_cmd(f, area, pager, theme); render_cmd_bar_cmd(f, area, pager, config);
return; return;
} }
if pager.search_buf.is_search_input || !pager.search_buf.buf_cmd_input.is_empty() { if pager.search_buf.is_search_input || !pager.search_buf.buf_cmd_input.is_empty() {
render_cmd_bar_search(f, area, pager, theme); render_cmd_bar_search(f, area, pager, config);
} }
} }
fn render_cmd_bar_search(f: &mut Frame, area: Rect, pager: &Pager<'_>, theme: &StyleConfig) { fn render_cmd_bar_search(f: &mut Frame, area: Rect, pager: &Pager<'_>, config: &ExploreConfig) {
if pager.search_buf.search_results.is_empty() && !pager.search_buf.is_search_input { if pager.search_buf.search_results.is_empty() && !pager.search_buf.is_search_input {
let message = format!("Pattern not found: {}", pager.search_buf.buf_cmd_input); let message = format!("Pattern not found: {}", pager.search_buf.buf_cmd_input);
let style = NuStyle { let style = NuStyle {
@ -596,7 +552,7 @@ fn render_cmd_bar_search(f: &mut Frame, area: Rect, pager: &Pager<'_>, theme: &S
..Default::default() ..Default::default()
}; };
let bar = CommandBar::new(&message, "", style, theme.cmd_bar_background); let bar = CommandBar::new(&message, "", style, config.cmd_bar_background);
f.render_widget(bar, area); f.render_widget(bar, area);
return; return;
} }
@ -615,11 +571,11 @@ fn render_cmd_bar_search(f: &mut Frame, area: Rect, pager: &Pager<'_>, theme: &S
format!("[{index}/{total}]") format!("[{index}/{total}]")
}; };
let bar = CommandBar::new(&text, &info, theme.cmd_bar_text, theme.cmd_bar_background); let bar = CommandBar::new(&text, &info, config.cmd_bar_text, config.cmd_bar_background);
f.render_widget(bar, area); f.render_widget(bar, area);
} }
fn render_cmd_bar_cmd(f: &mut Frame, area: Rect, pager: &Pager, theme: &StyleConfig) { fn render_cmd_bar_cmd(f: &mut Frame, area: Rect, pager: &Pager, config: &ExploreConfig) {
let mut input = pager.cmd_buf.buf_cmd2.as_str(); let mut input = pager.cmd_buf.buf_cmd2.as_str();
if input.len() > area.width as usize + 1 { if input.len() > area.width as usize + 1 {
// in such case we take last max_cmd_len chars // in such case we take last max_cmd_len chars
@ -637,7 +593,7 @@ fn render_cmd_bar_cmd(f: &mut Frame, area: Rect, pager: &Pager, theme: &StyleCon
let prefix = ':'; let prefix = ':';
let text = format!("{prefix}{input}"); let text = format!("{prefix}{input}");
let bar = CommandBar::new(&text, "", theme.cmd_bar_text, theme.cmd_bar_background); let bar = CommandBar::new(&text, "", config.cmd_bar_text, config.cmd_bar_background);
f.render_widget(bar, area); f.render_widget(bar, area);
} }
@ -998,72 +954,12 @@ fn cmd_input_key_event(buf: &mut CommandBuf, key: &KeyEvent) -> bool {
} }
} }
fn value_as_style(style: &mut nu_ansi_term::Style, value: &Value) -> bool { fn report_level_style(level: Severity, config: &ExploreConfig) -> NuStyle {
match value.coerce_str() {
Ok(s) => {
*style = lookup_ansi_color_style(&s);
true
}
Err(_) => false,
}
}
fn set_config(hm: &mut HashMap<String, Value>, path: &[&str], value: Value) -> bool {
if path.is_empty() {
return false;
}
let key = path[0];
if !hm.contains_key(key) {
hm.insert(
key.to_string(),
Value::record(Record::new(), NuSpan::unknown()),
);
}
let val = hm.get_mut(key).expect("...");
if path.len() == 1 {
*val = value;
return true;
}
match val {
Value::Record { val: record, .. } => {
if path.len() == 2 {
let key = path[1];
record.to_mut().insert(key, value);
} else {
let mut hm2: HashMap<String, Value> = HashMap::new();
for (k, v) in record.iter() {
hm2.insert(k.to_string(), v.clone());
}
let result = set_config(&mut hm2, &path[1..], value);
if !result {
*val = map_into_value(hm2);
}
if path.len() == 2 {
} else {
return false;
}
}
true
}
_ => false,
}
}
fn report_level_style(level: Severity, theme: &StyleConfig) -> NuStyle {
match level { match level {
Severity::Info => theme.status_info, Severity::Info => config.status_info,
Severity::Success => theme.status_success, Severity::Success => config.status_success,
Severity::Warn => theme.status_warn, Severity::Warn => config.status_warn,
Severity::Err => theme.status_error, Severity::Err => config.status_error,
} }
} }

View File

@ -3,7 +3,6 @@
mod binary_widget; mod binary_widget;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use nu_color_config::get_color_map;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
Value, Value,
@ -11,12 +10,12 @@ use nu_protocol::{
use ratatui::layout::Rect; use ratatui::layout::Rect;
use crate::{ use crate::{
explore::ExploreConfig,
nu_common::NuText, nu_common::NuText,
pager::{ pager::{
report::{Report, Severity}, report::{Report, Severity},
ConfigMap, Frame, Transition, ViewInfo, Frame, Transition, ViewInfo,
}, },
util::create_map,
views::cursor::Position, views::cursor::Position,
}; };
@ -90,12 +89,7 @@ impl View for BinaryView {
} }
fn setup(&mut self, cfg: ViewConfig<'_>) { fn setup(&mut self, cfg: ViewConfig<'_>) {
let hm = match cfg.config.get("hex-dump").and_then(create_map) { self.settings = settings_from_config(cfg.explore_config);
Some(hm) => hm,
None => return,
};
self.settings = settings_from_config(&hm);
let count_rows = let count_rows =
BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines(); BinaryWidget::new(&self.data, self.settings.opts, Default::default()).count_lines();
@ -184,30 +178,18 @@ fn handle_event_view_mode(view: &mut BinaryView, key: &KeyEvent) -> Option<Trans
} }
} }
fn settings_from_config(config: &ConfigMap) -> Settings { fn settings_from_config(config: &ExploreConfig) -> Settings {
let colors = get_color_map(config); // Most of this is hardcoded for now, add it to the config later if needed
Settings { Settings {
opts: BinarySettings::new( opts: BinarySettings::new(2, 8),
config_get_usize(config, "segment_size", 2),
config_get_usize(config, "count_segments", 8),
),
style: BinaryStyle::new( style: BinaryStyle::new(
colors.get("color_index").cloned(), None,
config_get_usize(config, "column_padding_left", 1) as u16, config.table.column_padding_left as u16,
config_get_usize(config, "column_padding_right", 1) as u16, config.table.column_padding_right as u16,
), ),
} }
} }
fn config_get_usize(config: &ConfigMap, key: &str, default: usize) -> usize {
config
.get(key)
.and_then(|v| v.coerce_str().ok())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(default)
}
fn create_report(cursor: WindowCursor2D) -> Report { fn create_report(cursor: WindowCursor2D) -> Report {
let covered_percent = report_row_position(cursor); let covered_percent = report_row_position(cursor);
let cursor = report_cursor_position(cursor); let cursor = report_cursor_position(cursor);

View File

@ -28,11 +28,10 @@ struct Cursor {
impl Cursor { impl Cursor {
/// Constructor to create a new Cursor /// Constructor to create a new Cursor
pub fn new(size: usize) -> Result<Self> { pub fn new(size: usize) -> Self {
if size == 0 { // In theory we should not be able to create a cursor with size 0, but in practice
bail!("Size cannot be zero"); // it's easier to allow that for empty lists etc. instead of propagating errors
} Cursor { position: 0, size }
Ok(Cursor { position: 0, size })
} }
/// The max position the cursor can be at /// The max position the cursor can be at
@ -88,7 +87,7 @@ mod tests {
#[test] #[test]
fn test_cursor_set_position() { fn test_cursor_set_position() {
// from 0 to 9 // from 0 to 9
let mut cursor = Cursor::new(10).unwrap(); let mut cursor = Cursor::new(10);
cursor.set_position(5); cursor.set_position(5);
assert_eq!(cursor.position, 5); assert_eq!(cursor.position, 5);
@ -99,7 +98,7 @@ mod tests {
#[test] #[test]
fn test_cursor_move_forward() { fn test_cursor_move_forward() {
// from 0 to 9 // from 0 to 9
let mut cursor = Cursor::new(10).unwrap(); let mut cursor = Cursor::new(10);
assert_eq!(cursor.position, 0); assert_eq!(cursor.position, 0);
cursor.move_forward(3); cursor.move_forward(3);
assert_eq!(cursor.position, 3); assert_eq!(cursor.position, 3);
@ -111,7 +110,7 @@ mod tests {
#[test] #[test]
fn test_cursor_move_backward() { fn test_cursor_move_backward() {
// from 0 to 9 // from 0 to 9
let mut cursor = Cursor::new(10).unwrap(); let mut cursor = Cursor::new(10);
cursor.move_backward(3); cursor.move_backward(3);
assert_eq!(cursor.position, 0); assert_eq!(cursor.position, 0);

View File

@ -42,8 +42,8 @@ impl WindowCursor {
} }
Ok(Self { Ok(Self {
view: Cursor::new(view_size)?, view: Cursor::new(view_size),
window: Cursor::new(window_size)?, window: Cursor::new(window_size),
}) })
} }

View File

@ -1,16 +1,16 @@
mod binary; mod binary;
mod colored_text_widget; mod colored_text_widget;
mod cursor; mod cursor;
mod interactive;
mod preview; mod preview;
mod record; mod record;
mod r#try;
pub mod util; pub mod util;
use super::{ use super::{
nu_common::NuText, nu_common::NuText,
pager::{Frame, Transition, ViewInfo}, pager::{Frame, Transition, ViewInfo},
}; };
use crate::{nu_common::NuConfig, pager::ConfigMap}; use crate::{explore::ExploreConfig, nu_common::NuConfig};
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use lscolors::LsColors; use lscolors::LsColors;
use nu_color_config::StyleComputer; use nu_color_config::StyleComputer;
@ -21,8 +21,8 @@ use nu_protocol::{
use ratatui::layout::Rect; use ratatui::layout::Rect;
pub use binary::BinaryView; pub use binary::BinaryView;
pub use interactive::InteractiveView;
pub use preview::Preview; pub use preview::Preview;
pub use r#try::TryView;
pub use record::{Orientation, RecordView}; pub use record::{Orientation, RecordView};
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -55,22 +55,22 @@ impl ElementInfo {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct ViewConfig<'a> { pub struct ViewConfig<'a> {
pub nu_config: &'a NuConfig, pub nu_config: &'a NuConfig,
pub explore_config: &'a ExploreConfig,
pub style_computer: &'a StyleComputer<'a>, pub style_computer: &'a StyleComputer<'a>,
pub config: &'a ConfigMap,
pub lscolors: &'a LsColors, pub lscolors: &'a LsColors,
} }
impl<'a> ViewConfig<'a> { impl<'a> ViewConfig<'a> {
pub fn new( pub fn new(
nu_config: &'a NuConfig, nu_config: &'a NuConfig,
explore_config: &'a ExploreConfig,
style_computer: &'a StyleComputer<'a>, style_computer: &'a StyleComputer<'a>,
config: &'a ConfigMap,
lscolors: &'a LsColors, lscolors: &'a LsColors,
) -> Self { ) -> Self {
Self { Self {
nu_config, nu_config,
explore_config,
style_computer, style_computer,
config,
lscolors, lscolors,
} }
} }

View File

@ -1,26 +1,26 @@
mod table_widget; mod table_widget;
use self::table_widget::{TableStyle, TableWidget, TableWidgetState}; use self::table_widget::{TableWidget, TableWidgetState};
use super::{ use super::{
cursor::{Position, WindowCursor2D}, cursor::{Position, WindowCursor2D},
util::{make_styled_string, nu_style_to_tui}, util::{make_styled_string, nu_style_to_tui},
Layout, View, ViewConfig, Layout, View, ViewConfig,
}; };
use crate::{ use crate::{
nu_common::{collect_input, lscolorize, NuConfig, NuSpan, NuStyle, NuText}, explore::ExploreConfig,
nu_common::{collect_input, lscolorize, NuSpan, NuText},
pager::{ pager::{
report::{Report, Severity}, report::{Report, Severity},
ConfigMap, Frame, Transition, ViewInfo, Frame, Transition, ViewInfo,
}, },
util::create_map,
views::ElementInfo, views::ElementInfo,
}; };
use anyhow::Result; use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use nu_color_config::{get_color_map, StyleComputer}; use nu_color_config::StyleComputer;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
Record, Span, Value, Config, Record, Span, Value,
}; };
use ratatui::{layout::Rect, widgets::Block}; use ratatui::{layout::Rect, widgets::Block};
use std::{borrow::Cow, collections::HashMap}; use std::{borrow::Cow, collections::HashMap};
@ -32,7 +32,7 @@ pub struct RecordView<'a> {
layer_stack: Vec<RecordLayer<'a>>, layer_stack: Vec<RecordLayer<'a>>,
mode: UIMode, mode: UIMode,
orientation: Orientation, orientation: Orientation,
theme: TableTheme, cfg: ExploreConfig,
} }
impl<'a> RecordView<'a> { impl<'a> RecordView<'a> {
@ -44,52 +44,18 @@ impl<'a> RecordView<'a> {
layer_stack: vec![RecordLayer::new(columns, records)], layer_stack: vec![RecordLayer::new(columns, records)],
mode: UIMode::View, mode: UIMode::View,
orientation: Orientation::Top, orientation: Orientation::Top,
theme: TableTheme::default(), // TODO: It's kind of gross how this temporarily has an incorrect/default config.
// See if we can pass correct config in through the constructor
cfg: ExploreConfig::default(),
} }
} }
pub fn tail(&mut self, width: u16, height: u16) { pub fn tail(&mut self, width: u16, height: u16) {
let page_size = let page_size =
estimate_page_size(Rect::new(0, 0, width, height), self.theme.table.show_header); estimate_page_size(Rect::new(0, 0, width, height), self.cfg.table.show_header);
tail_data(self, page_size as usize); tail_data(self, page_size as usize);
} }
pub fn set_style_split_line(&mut self, style: NuStyle) {
self.theme.table.splitline_style = style
}
pub fn set_style_selected_cell(&mut self, style: NuStyle) {
self.theme.cursor.selected_cell = Some(style)
}
pub fn set_style_selected_row(&mut self, style: NuStyle) {
self.theme.cursor.selected_row = Some(style)
}
pub fn set_style_selected_column(&mut self, style: NuStyle) {
self.theme.cursor.selected_column = Some(style)
}
pub fn set_padding_column(&mut self, (left, right): (usize, usize)) {
self.theme.table.column_padding_left = left;
self.theme.table.column_padding_right = right;
}
pub fn get_padding_column(&self) -> (usize, usize) {
(
self.theme.table.column_padding_left,
self.theme.table.column_padding_right,
)
}
pub fn get_theme(&self) -> &TableTheme {
&self.theme
}
pub fn set_theme(&mut self, theme: TableTheme) {
self.theme = theme;
}
pub fn transpose(&mut self) { pub fn transpose(&mut self) {
let layer = self.get_layer_last_mut(); let layer = self.get_layer_last_mut();
transpose_table(layer); transpose_table(layer);
@ -193,7 +159,7 @@ impl<'a> RecordView<'a> {
style_computer, style_computer,
row, row,
column, column,
self.theme.table, self.cfg.table,
layer.orientation, layer.orientation,
) )
} }
@ -252,11 +218,11 @@ impl View for RecordView<'_> {
column, column,
table_layout.count_rows, table_layout.count_rows,
self.get_layer_last().orientation, self.get_layer_last().orientation,
self.theme.table.show_header, self.cfg.table.show_header,
); );
if let Some(info) = info { if let Some(info) = info {
highlight_cell(f, area, info.clone(), &self.theme.cursor); highlight_selected_cell(f, info.clone(), &self.cfg);
} }
} }
} }
@ -300,7 +266,7 @@ impl View for RecordView<'_> {
let data = convert_records_to_string( let data = convert_records_to_string(
&self.get_layer_last().records, &self.get_layer_last().records,
&NuConfig::default(), &nu_protocol::Config::default(),
&style_computer, &style_computer,
); );
@ -338,22 +304,7 @@ impl View for RecordView<'_> {
// todo: move the method to Command? // todo: move the method to Command?
fn setup(&mut self, cfg: ViewConfig<'_>) { fn setup(&mut self, cfg: ViewConfig<'_>) {
if let Some(hm) = cfg.config.get("table").and_then(create_map) { self.cfg = cfg.explore_config.clone();
self.theme = theme_from_config(&hm);
if let Some(orientation) = hm.get("orientation").and_then(|v| v.coerce_str().ok()) {
let orientation = match orientation.as_ref() {
"left" => Some(Orientation::Left),
"top" => Some(Orientation::Top),
_ => None,
};
if let Some(orientation) = orientation {
self.set_orientation(orientation);
self.set_orientation_current(orientation);
}
}
}
} }
} }
@ -660,7 +611,7 @@ fn tail_data(state: &mut RecordView<'_>, page_size: usize) {
fn convert_records_to_string( fn convert_records_to_string(
records: &[Vec<Value>], records: &[Vec<Value>],
cfg: &NuConfig, cfg: &Config,
style_computer: &StyleComputer, style_computer: &StyleComputer,
) -> Vec<Vec<NuText>> { ) -> Vec<Vec<NuText>> {
records records
@ -678,31 +629,8 @@ fn convert_records_to_string(
.collect::<Vec<_>>() .collect::<Vec<_>>()
} }
fn highlight_cell(f: &mut Frame, area: Rect, info: ElementInfo, theme: &CursorStyle) { fn highlight_selected_cell(f: &mut Frame, info: ElementInfo, cfg: &ExploreConfig) {
// highlight selected column let cell_style = cfg.selected_cell;
if let Some(style) = theme.selected_column {
let highlight_block = Block::default().style(nu_style_to_tui(style));
let area = Rect::new(info.area.x, area.y, info.area.width, area.height);
f.render_widget(highlight_block.clone(), area);
}
// highlight selected row
if let Some(style) = theme.selected_row {
let highlight_block = Block::default().style(nu_style_to_tui(style));
let area = Rect::new(area.x, info.area.y, area.width, 1);
f.render_widget(highlight_block.clone(), area);
}
// highlight selected cell
let cell_style = match theme.selected_cell {
Some(s) => s,
None => {
let mut style = nu_ansi_term::Style::new();
// light blue chosen somewhat arbitrarily, looks OK but I'm not set on it
style.background = Some(nu_ansi_term::Color::LightBlue);
style
}
};
let highlight_block = Block::default().style(nu_style_to_tui(cell_style)); let highlight_block = Block::default().style(nu_style_to_tui(cell_style));
let area = Rect::new(info.area.x, info.area.y, info.area.width, 1); let area = Rect::new(info.area.x, info.area.y, info.area.width, 1);
f.render_widget(highlight_block.clone(), area) f.render_widget(highlight_block.clone(), area)
@ -842,53 +770,3 @@ fn _transpose_table(
data data
} }
fn theme_from_config(config: &ConfigMap) -> TableTheme {
let mut theme = TableTheme::default();
let colors = get_color_map(config);
if let Some(s) = colors.get("split_line") {
theme.table.splitline_style = *s;
}
theme.cursor.selected_cell = colors.get("selected_cell").cloned();
theme.cursor.selected_row = colors.get("selected_row").cloned();
theme.cursor.selected_column = colors.get("selected_column").cloned();
theme.table.show_header = config_get_bool(config, "show_head", true);
theme.table.show_index = config_get_bool(config, "show_index", false);
theme.table.column_padding_left = config_get_usize(config, "column_padding_left", 1);
theme.table.column_padding_right = config_get_usize(config, "column_padding_right", 1);
theme
}
fn config_get_bool(config: &ConfigMap, key: &str, default: bool) -> bool {
config
.get(key)
.and_then(|v| v.as_bool().ok())
.unwrap_or(default)
}
fn config_get_usize(config: &ConfigMap, key: &str, default: usize) -> usize {
config
.get(key)
.and_then(|v| v.coerce_str().ok())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(default)
}
#[derive(Debug, Default, Clone)]
pub struct TableTheme {
table: TableStyle,
cursor: CursorStyle,
}
#[derive(Debug, Default, Clone)]
struct CursorStyle {
selected_cell: Option<NuStyle>,
selected_column: Option<NuStyle>,
selected_row: Option<NuStyle>,
}

View File

@ -1,5 +1,6 @@
use super::Layout; use super::Layout;
use crate::{ use crate::{
explore::TableConfig,
nu_common::{truncate_str, NuStyle, NuText}, nu_common::{truncate_str, NuStyle, NuText},
views::util::{nu_style_to_tui, text_style_to_tui_style}, views::util::{nu_style_to_tui, text_style_to_tui_style},
}; };
@ -23,7 +24,7 @@ pub struct TableWidget<'a> {
data: Cow<'a, [Vec<NuText>]>, data: Cow<'a, [Vec<NuText>]>,
index_row: usize, index_row: usize,
index_column: usize, index_column: usize,
style: TableStyle, config: TableConfig,
header_position: Orientation, header_position: Orientation,
style_computer: &'a StyleComputer<'a>, style_computer: &'a StyleComputer<'a>,
} }
@ -35,15 +36,6 @@ pub enum Orientation {
Left, Left,
} }
#[derive(Debug, Default, Clone, Copy)]
pub struct TableStyle {
pub splitline_style: NuStyle,
pub show_index: bool,
pub show_header: bool,
pub column_padding_left: usize,
pub column_padding_right: usize,
}
impl<'a> TableWidget<'a> { impl<'a> TableWidget<'a> {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
@ -52,7 +44,7 @@ impl<'a> TableWidget<'a> {
style_computer: &'a StyleComputer<'a>, style_computer: &'a StyleComputer<'a>,
index_row: usize, index_row: usize,
index_column: usize, index_column: usize,
style: TableStyle, config: TableConfig,
header_position: Orientation, header_position: Orientation,
) -> Self { ) -> Self {
Self { Self {
@ -61,7 +53,7 @@ impl<'a> TableWidget<'a> {
style_computer, style_computer,
index_row, index_row,
index_column, index_column,
style, config,
header_position, header_position,
} }
} }
@ -100,13 +92,13 @@ impl StatefulWidget for TableWidget<'_> {
// todo: refactoring these to methods as they have quite a bit in common. // todo: refactoring these to methods as they have quite a bit in common.
impl<'a> TableWidget<'a> { impl<'a> TableWidget<'a> {
fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) { fn render_table_horizontal(self, area: Rect, buf: &mut Buffer, state: &mut TableWidgetState) {
let padding_l = self.style.column_padding_left as u16; let padding_l = self.config.column_padding_left as u16;
let padding_r = self.style.column_padding_right as u16; let padding_r = self.config.column_padding_right as u16;
let show_index = self.style.show_index; let show_index = self.config.show_index;
let show_head = self.style.show_header; let show_head = self.config.show_header;
let splitline_s = self.style.splitline_style; let separator_s = self.config.separator_style;
let mut data_height = area.height; let mut data_height = area.height;
let mut data_y = area.y; let mut data_y = area.y;
@ -137,7 +129,7 @@ impl<'a> TableWidget<'a> {
} }
if show_head { if show_head {
render_header_borders(buf, area, 1, splitline_s); render_header_borders(buf, area, 1, separator_s);
} }
if show_index { if show_index {
@ -158,7 +150,7 @@ impl<'a> TableWidget<'a> {
data_height, data_height,
show_head, show_head,
false, false,
splitline_s, separator_s,
); );
} }
@ -250,7 +242,7 @@ impl<'a> TableWidget<'a> {
data_height, data_height,
show_head, show_head,
false, false,
splitline_s, separator_s,
); );
} }
@ -268,12 +260,12 @@ impl<'a> TableWidget<'a> {
return; return;
} }
let padding_l = self.style.column_padding_left as u16; let padding_l = self.config.column_padding_left as u16;
let padding_r = self.style.column_padding_right as u16; let padding_r = self.config.column_padding_right as u16;
let show_index = self.style.show_index; let show_index = self.config.show_index;
let show_head = self.style.show_header; let show_head = self.config.show_header;
let splitline_s = self.style.splitline_style; let separator_s = self.config.separator_style;
let mut left_w = 0; let mut left_w = 0;
@ -295,7 +287,7 @@ impl<'a> TableWidget<'a> {
area.height, area.height,
false, false,
false, false,
splitline_s, separator_s,
); );
} }
@ -327,7 +319,7 @@ impl<'a> TableWidget<'a> {
area.height, area.height,
false, false,
false, false,
splitline_s, separator_s,
); );
} }
@ -352,7 +344,7 @@ impl<'a> TableWidget<'a> {
area.height, area.height,
false, false,
false, false,
splitline_s, separator_s,
); );
} }

View File

@ -1,16 +1,10 @@
use super::{ use super::{record::RecordView, util::nu_style_to_tui, Layout, Orientation, View, ViewConfig};
record::{RecordView, TableTheme},
util::{lookup_tui_color, nu_style_to_tui},
Layout, Orientation, View, ViewConfig,
};
use crate::{ use crate::{
nu_common::{collect_pipeline, run_command_with_value}, nu_common::{collect_pipeline, run_command_with_value},
pager::{report::Report, Frame, Transition, ViewInfo}, pager::{report::Report, Frame, Transition, ViewInfo},
util::create_map,
}; };
use anyhow::Result; use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent}; use crossterm::event::{KeyCode, KeyEvent};
use nu_color_config::get_color_map;
use nu_protocol::{ use nu_protocol::{
engine::{EngineState, Stack}, engine::{EngineState, Stack},
PipelineData, Value, PipelineData, Value,
@ -22,26 +16,22 @@ use ratatui::{
}; };
use std::cmp::min; use std::cmp::min;
pub struct InteractiveView<'a> { pub struct TryView<'a> {
input: Value, input: Value,
command: String, command: String,
immediate: bool, reactive: bool,
table: Option<RecordView<'a>>, table: Option<RecordView<'a>>,
table_theme: TableTheme,
view_mode: bool, view_mode: bool,
border_color: Style, border_color: Style,
highlighted_color: Style,
} }
impl<'a> InteractiveView<'a> { impl<'a> TryView<'a> {
pub fn new(input: Value) -> Self { pub fn new(input: Value) -> Self {
Self { Self {
input, input,
table: None, table: None,
immediate: false, reactive: false,
table_theme: TableTheme::default(),
border_color: Style::default(), border_color: Style::default(),
highlighted_color: Style::default(),
view_mode: false, view_mode: false,
command: String::new(), command: String::new(),
} }
@ -52,18 +42,15 @@ impl<'a> InteractiveView<'a> {
} }
pub fn try_run(&mut self, engine_state: &EngineState, stack: &mut Stack) -> Result<()> { pub fn try_run(&mut self, engine_state: &EngineState, stack: &mut Stack) -> Result<()> {
let mut view = run_command(&self.command, &self.input, engine_state, stack)?; let view = run_command(&self.command, &self.input, engine_state, stack)?;
view.set_theme(self.table_theme.clone());
self.table = Some(view); self.table = Some(view);
Ok(()) Ok(())
} }
} }
impl View for InteractiveView<'_> { impl View for TryView<'_> {
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) { fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
let border_color = self.border_color; let border_color = self.border_color;
let highlighted_color = self.highlighted_color;
let cmd_block = ratatui::widgets::Block::default() let cmd_block = ratatui::widgets::Block::default()
.borders(Borders::ALL) .borders(Borders::ALL)
@ -77,7 +64,7 @@ impl View for InteractiveView<'_> {
cmd_block cmd_block
.border_style(Style::default().add_modifier(Modifier::BOLD)) .border_style(Style::default().add_modifier(Modifier::BOLD))
.border_type(BorderType::Double) .border_type(BorderType::Double)
.border_style(highlighted_color) .border_style(border_color)
}; };
f.render_widget(cmd_block, cmd_area); f.render_widget(cmd_block, cmd_area);
@ -127,7 +114,7 @@ impl View for InteractiveView<'_> {
table_block table_block
.border_style(Style::default().add_modifier(Modifier::BOLD)) .border_style(Style::default().add_modifier(Modifier::BOLD))
.border_type(BorderType::Double) .border_type(BorderType::Double)
.border_style(highlighted_color) .border_style(border_color)
} else { } else {
table_block table_block
}; };
@ -135,6 +122,7 @@ impl View for InteractiveView<'_> {
f.render_widget(table_block, table_area); f.render_widget(table_block, table_area);
if let Some(table) = &mut self.table { if let Some(table) = &mut self.table {
table.setup(cfg);
let area = Rect::new( let area = Rect::new(
area.x + 2, area.x + 2,
area.y + 4, area.y + 4,
@ -190,7 +178,7 @@ impl View for InteractiveView<'_> {
if !self.command.is_empty() { if !self.command.is_empty() {
self.command.pop(); self.command.pop();
if self.immediate { if self.reactive {
match self.try_run(engine_state, stack) { match self.try_run(engine_state, stack) {
Ok(_) => info.report = Some(Report::default()), Ok(_) => info.report = Some(Report::default()),
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))), Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
@ -203,7 +191,7 @@ impl View for InteractiveView<'_> {
KeyCode::Char(c) => { KeyCode::Char(c) => {
self.command.push(*c); self.command.push(*c);
if self.immediate { if self.reactive {
match self.try_run(engine_state, stack) { match self.try_run(engine_state, stack) {
Ok(_) => info.report = Some(Report::default()), Ok(_) => info.report = Some(Report::default()),
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))), Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
@ -246,31 +234,14 @@ impl View for InteractiveView<'_> {
} }
fn setup(&mut self, config: ViewConfig<'_>) { fn setup(&mut self, config: ViewConfig<'_>) {
self.border_color = lookup_tui_color(config.style_computer, "separator"); self.border_color = nu_style_to_tui(config.explore_config.table.separator_style);
self.reactive = config.explore_config.try_reactive;
if let Some(hm) = config.config.get("try").and_then(create_map) {
let colors = get_color_map(&hm);
if let Some(color) = colors.get("highlighted_color").copied() {
self.highlighted_color = nu_style_to_tui(color);
}
if self.border_color != Style::default() && self.highlighted_color == Style::default() {
self.highlighted_color = self.border_color;
}
if let Some(val) = hm.get("reactive").and_then(|v| v.as_bool().ok()) {
self.immediate = val;
}
}
let mut r = RecordView::new(vec![], vec![]); let mut r = RecordView::new(vec![], vec![]);
r.setup(config); r.setup(config);
self.table_theme = r.get_theme().clone();
if let Some(view) = &mut self.table { if let Some(view) = &mut self.table {
view.set_theme(self.table_theme.clone()); view.setup(config);
view.set_orientation(r.get_orientation_current()); view.set_orientation(r.get_orientation_current());
view.set_orientation_current(r.get_orientation_current()); view.set_orientation_current(r.get_orientation_current());
} }

View File

@ -31,11 +31,6 @@ pub fn set_span(
text_width as u16 text_width as u16
} }
pub fn lookup_tui_color(style_computer: &StyleComputer, key: &str) -> Style {
let nu_style = style_computer.compute(key, &Value::nothing(nu_protocol::Span::unknown()));
nu_style_to_tui(nu_style)
}
pub fn nu_style_to_tui(style: NuStyle) -> ratatui::style::Style { pub fn nu_style_to_tui(style: NuStyle) -> ratatui::style::Style {
let mut out = ratatui::style::Style::default(); let mut out = ratatui::style::Style::default();
if let Some(clr) = style.background { if let Some(clr) = style.background {

View File

@ -189,12 +189,7 @@ $env.config = {
warn: {} warn: {}
info: {} info: {}
}, },
table: { selected_cell: { bg: light_blue },
split_line: { fg: "#404040" },
selected_cell: { bg: light_blue },
selected_row: {},
selected_column: {},
},
} }
history: { history: {

View File

@ -29,12 +29,12 @@ indexmap = { version = "2.2" }
mimalloc = { version = "0.1.42" } mimalloc = { version = "0.1.42" }
num = {version = "0.4"} num = {version = "0.4"}
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sqlparser = { version = "0.45"} sqlparser = { version = "0.47"}
polars-io = { version = "0.39", features = ["avro"]} polars-io = { version = "0.40", features = ["avro"]}
polars-arrow = { version = "0.39"} polars-arrow = { version = "0.40"}
polars-ops = { version = "0.39"} polars-ops = { version = "0.40"}
polars-plan = { version = "0.39", features = ["regex"]} polars-plan = { version = "0.40", features = ["regex"]}
polars-utils = { version = "0.39"} polars-utils = { version = "0.40"}
typetag = "0.2" typetag = "0.2"
uuid = { version = "1.7", features = ["v4", "serde"] } uuid = { version = "1.7", features = ["v4", "serde"] }
@ -70,7 +70,7 @@ features = [
"to_dummies", "to_dummies",
] ]
optional = false optional = false
version = "0.39" version = "0.40"
[dev-dependencies] [dev-dependencies]
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" } nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }

View File

@ -16,14 +16,17 @@ use std::{
fs::File, fs::File,
io::BufReader, io::BufReader,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc,
}; };
use polars::prelude::{ use polars::prelude::{
CsvEncoding, CsvReader, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader, CsvEncoding, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader, LazyFrame,
LazyFrame, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
}; };
use polars_io::{avro::AvroReader, prelude::ParallelStrategy, HiveOptions}; use polars_io::{
avro::AvroReader, csv::read::CsvReadOptions, prelude::ParallelStrategy, HiveOptions,
};
#[derive(Clone)] #[derive(Clone)]
pub struct OpenDataFrame; pub struct OpenDataFrame;
@ -175,6 +178,7 @@ fn from_parquet(
cloud_options: None, cloud_options: None,
use_statistics: false, use_statistics: false,
hive_options: HiveOptions::default(), hive_options: HiveOptions::default(),
glob: true,
}; };
let df: NuLazyFrame = LazyFrame::scan_parquet(file, args) let df: NuLazyFrame = LazyFrame::scan_parquet(file, args)
@ -445,7 +449,7 @@ fn from_csv(
} }
}; };
let csv_reader = csv_reader.has_header(!no_header); let csv_reader = csv_reader.with_has_header(!no_header);
let csv_reader = match maybe_schema { let csv_reader = match maybe_schema {
Some(schema) => csv_reader.with_schema(Some(schema.into())), Some(schema) => csv_reader.with_schema(Some(schema.into())),
@ -475,7 +479,23 @@ fn from_csv(
df.cache_and_to_value(plugin, engine, call.head) df.cache_and_to_value(plugin, engine, call.head)
} else { } else {
let csv_reader = CsvReader::from_path(file_path) let df = CsvReadOptions::default()
.with_has_header(!no_header)
.with_infer_schema_length(infer_schema)
.with_skip_rows(skip_rows.unwrap_or_default())
.with_schema(maybe_schema.map(|s| s.into()))
.with_columns(columns.map(Arc::new))
.map_parse_options(|options| {
options
.with_separator(
delimiter
.as_ref()
.and_then(|d| d.item.chars().next().map(|c| c as u8))
.unwrap_or(b','),
)
.with_encoding(CsvEncoding::LossyUtf8)
})
.try_into_reader_with_file_path(Some(file_path.to_path_buf()))
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
error: "Error creating CSV reader".into(), error: "Error creating CSV reader".into(),
msg: e.to_string(), msg: e.to_string(),
@ -483,52 +503,6 @@ fn from_csv(
help: None, help: None,
inner: vec![], inner: vec![],
})? })?
.with_encoding(CsvEncoding::LossyUtf8);
let csv_reader = match delimiter {
None => csv_reader,
Some(d) => {
if d.item.len() != 1 {
return Err(ShellError::GenericError {
error: "Incorrect delimiter".into(),
msg: "Delimiter has to be one character".into(),
span: Some(d.span),
help: None,
inner: vec![],
});
} else {
let delimiter = match d.item.chars().next() {
Some(d) => d as u8,
None => unreachable!(),
};
csv_reader.with_separator(delimiter)
}
}
};
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 {
None => csv_reader,
Some(r) => csv_reader.infer_schema(Some(r)),
};
let csv_reader = match skip_rows {
None => csv_reader,
Some(r) => csv_reader.with_skip_rows(r),
};
let csv_reader = match columns {
None => csv_reader,
Some(columns) => csv_reader.with_columns(Some(columns)),
};
let df: NuDataFrame = csv_reader
.finish() .finish()
.map_err(|e| ShellError::GenericError { .map_err(|e| ShellError::GenericError {
error: "CSV reader error".into(), error: "CSV reader error".into(),
@ -536,9 +510,8 @@ fn from_csv(
span: Some(call.head), span: Some(call.head),
help: None, help: None,
inner: vec![], inner: vec![],
})? })?;
.into(); let df = NuDataFrame::new(false, df);
df.cache_and_to_value(plugin, engine, call.head) df.cache_and_to_value(plugin, engine, call.head)
} }
} }

View File

@ -3,7 +3,8 @@ use polars::prelude::{col, lit, DataType, Expr, LiteralValue, PolarsResult as Re
use sqlparser::ast::{ use sqlparser::ast::{
ArrayElemTypeDef, BinaryOperator as SQLBinaryOperator, DataType as SQLDataType, ArrayElemTypeDef, BinaryOperator as SQLBinaryOperator, DataType as SQLDataType,
Expr as SqlExpr, Function as SQLFunction, Value as SqlValue, WindowType, DuplicateTreatment, Expr as SqlExpr, Function as SQLFunction, FunctionArguments,
Value as SqlValue, WindowType,
}; };
fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> { fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
@ -33,7 +34,7 @@ fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
SQLDataType::Interval => DataType::Duration(TimeUnit::Microseconds), SQLDataType::Interval => DataType::Duration(TimeUnit::Microseconds),
SQLDataType::Array(array_type_def) => match array_type_def { SQLDataType::Array(array_type_def) => match array_type_def {
ArrayElemTypeDef::AngleBracket(inner_type) ArrayElemTypeDef::AngleBracket(inner_type)
| ArrayElemTypeDef::SquareBracket(inner_type) => { | ArrayElemTypeDef::SquareBracket(inner_type, _) => {
DataType::List(Box::new(map_sql_polars_datatype(inner_type)?)) DataType::List(Box::new(map_sql_polars_datatype(inner_type)?))
} }
_ => { _ => {
@ -120,9 +121,7 @@ pub fn parse_sql_expr(expr: &SqlExpr) -> Result<Expr> {
} }
SqlExpr::Function(sql_function) => parse_sql_function(sql_function)?, SqlExpr::Function(sql_function) => parse_sql_function(sql_function)?,
SqlExpr::Cast { SqlExpr::Cast {
expr, expr, data_type, ..
data_type,
format: _,
} => cast_(parse_sql_expr(expr)?, data_type)?, } => cast_(parse_sql_expr(expr)?, data_type)?,
SqlExpr::Nested(expr) => parse_sql_expr(expr)?, SqlExpr::Nested(expr) => parse_sql_expr(expr)?,
SqlExpr::Value(value) => literal_expr(value)?, SqlExpr::Value(value) => literal_expr(value)?,
@ -162,8 +161,17 @@ fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
use sqlparser::ast::{FunctionArg, FunctionArgExpr}; use sqlparser::ast::{FunctionArg, FunctionArgExpr};
// Function name mostly do not have name space, so it mostly take the first args // Function name mostly do not have name space, so it mostly take the first args
let function_name = sql_function.name.0[0].value.to_ascii_lowercase(); let function_name = sql_function.name.0[0].value.to_ascii_lowercase();
let args = sql_function
.args // One day this should support the additional argument types supported with 0.40
let (args, distinct) = match &sql_function.args {
FunctionArguments::List(list) => (
list.args.clone(),
list.duplicate_treatment == Some(DuplicateTreatment::Distinct),
),
_ => (vec![], false),
};
let args = args
.iter() .iter()
.map(|arg| match arg { .map(|arg| match arg {
FunctionArg::Named { arg, .. } => arg, FunctionArg::Named { arg, .. } => arg,
@ -174,15 +182,15 @@ fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
match ( match (
function_name.as_str(), function_name.as_str(),
args.as_slice(), args.as_slice(),
sql_function.distinct, distinct,
) { ) {
("sum", [FunctionArgExpr::Expr(expr)], false) => { ("sum", [FunctionArgExpr::Expr(ref expr)], false) => {
apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.sum() apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.sum()
} }
("count", [FunctionArgExpr::Expr(expr)], false) => { ("count", [FunctionArgExpr::Expr(ref expr)], false) => {
apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.count() apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.count()
} }
("count", [FunctionArgExpr::Expr(expr)], true) => { ("count", [FunctionArgExpr::Expr(ref expr)], true) => {
apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.n_unique() apply_window_spec(parse_sql_expr(expr)?, sql_function.over.as_ref())?.n_unique()
} }
// Special case for wildcard args to count function. // Special case for wildcard args to count function.

View File

@ -189,53 +189,19 @@ fn command(
.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.sum::<f64>().ok();
series let mean = col.mean();
.cast(&DataType::Float64) let median = col.median();
.ok() let std = col.std(0);
.and_then(|ca| match ca.get(0) { let min = col.min::<f64>().ok().flatten();
Ok(AnyValue::Float64(v)) => Some(v),
_ => None,
})
});
let mean = match col.mean_as_series().get(0) {
Ok(AnyValue::Float64(v)) => Some(v),
_ => None,
};
let median = match col.median_as_series() {
Ok(v) => match v.get(0) {
Ok(AnyValue::Float64(v)) => Some(v),
_ => None,
},
_ => None,
};
let std = match col.std_as_series(0) {
Ok(v) => match v.get(0) {
Ok(AnyValue::Float64(v)) => Some(v),
_ => None,
},
_ => None,
};
let min = col.min_as_series().ok().and_then(|series| {
series
.cast(&DataType::Float64)
.ok()
.and_then(|ca| match ca.get(0) {
Ok(AnyValue::Float64(v)) => Some(v),
_ => None,
})
});
let mut quantiles = quantiles let mut quantiles = quantiles
.clone() .clone()
.into_iter() .into_iter()
.map(|q| { .map(|q| {
col.quantile_as_series(q, QuantileInterpolOptions::default()) col.quantile_reduce(q, QuantileInterpolOptions::default())
.ok() .ok()
.map(|s| s.into_series("quantile"))
.and_then(|ca| ca.cast(&DataType::Float64).ok()) .and_then(|ca| ca.cast(&DataType::Float64).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),
@ -244,15 +210,7 @@ fn command(
}) })
.collect::<Vec<Option<f64>>>(); .collect::<Vec<Option<f64>>>();
let max = col.max_as_series().ok().and_then(|series| { let max = col.max::<f64>().ok().flatten();
series
.cast(&DataType::Float64)
.ok()
.and_then(|ca| match ca.get(0) {
Ok(AnyValue::Float64(v)) => Some(v),
_ => 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

@ -5,9 +5,7 @@ use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
use crate::values::CustomValueSupport; use crate::values::CustomValueSupport;
use crate::PolarsPlugin; use crate::PolarsPlugin;
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{ use nu_protocol::{Category, Example, LabeledError, PipelineData, Signature, Span, Type, Value};
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Type, Value,
};
// The structs defined in this file are structs that form part of other commands // The structs defined in this file are structs that form part of other commands
// since they share a similar name // since they share a similar name
@ -60,6 +58,7 @@ macro_rules! expr_command {
mod $test { mod $test {
use super::*; use super::*;
use crate::test::test_polars_plugin_command; use crate::test::test_polars_plugin_command;
use nu_protocol::ShellError;
#[test] #[test]
fn test_examples() -> Result<(), ShellError> { fn test_examples() -> Result<(), ShellError> {
@ -163,19 +162,7 @@ macro_rules! lazy_expr_command {
if NuDataFrame::can_downcast(&value) || NuLazyFrame::can_downcast(&value) { if NuDataFrame::can_downcast(&value) || NuLazyFrame::can_downcast(&value) {
let lazy = NuLazyFrame::try_from_value_coerce(plugin, &value) let lazy = NuLazyFrame::try_from_value_coerce(plugin, &value)
.map_err(LabeledError::from)?; .map_err(LabeledError::from)?;
let lazy = NuLazyFrame::new( let lazy = NuLazyFrame::new(lazy.from_eager, lazy.to_polars().$func());
lazy.from_eager,
lazy.to_polars()
.$func()
.map_err(|e| ShellError::GenericError {
error: "Dataframe Error".into(),
msg: e.to_string(),
help: None,
span: None,
inner: vec![],
})
.map_err(LabeledError::from)?,
);
lazy.to_pipeline_data(plugin, engine, call.head) lazy.to_pipeline_data(plugin, engine, call.head)
.map_err(LabeledError::from) .map_err(LabeledError::from)
} else { } else {
@ -192,6 +179,7 @@ macro_rules! lazy_expr_command {
mod $test { mod $test {
use super::*; use super::*;
use crate::test::test_polars_plugin_command; use crate::test::test_polars_plugin_command;
use nu_protocol::ShellError;
#[test] #[test]
fn test_examples() -> Result<(), ShellError> { fn test_examples() -> Result<(), ShellError> {
@ -244,19 +232,7 @@ macro_rules! lazy_expr_command {
if NuDataFrame::can_downcast(&value) || NuLazyFrame::can_downcast(&value) { if NuDataFrame::can_downcast(&value) || NuLazyFrame::can_downcast(&value) {
let lazy = NuLazyFrame::try_from_value_coerce(plugin, &value) let lazy = NuLazyFrame::try_from_value_coerce(plugin, &value)
.map_err(LabeledError::from)?; .map_err(LabeledError::from)?;
let lazy = NuLazyFrame::new( let lazy = NuLazyFrame::new(lazy.from_eager, lazy.to_polars().$func($ddof));
lazy.from_eager,
lazy.to_polars()
.$func($ddof)
.map_err(|e| ShellError::GenericError {
error: "Dataframe Error".into(),
msg: e.to_string(),
help: None,
span: None,
inner: vec![],
})
.map_err(LabeledError::from)?,
);
lazy.to_pipeline_data(plugin, engine, call.head) lazy.to_pipeline_data(plugin, engine, call.head)
.map_err(LabeledError::from) .map_err(LabeledError::from)
} else { } else {
@ -272,6 +248,7 @@ macro_rules! lazy_expr_command {
mod $test { mod $test {
use super::*; use super::*;
use crate::test::test_polars_plugin_command; use crate::test::test_polars_plugin_command;
use nu_protocol::ShellError;
#[test] #[test]
fn test_examples() -> Result<(), ShellError> { fn test_examples() -> Result<(), ShellError> {

View File

@ -35,7 +35,7 @@ impl PluginCommand for ExprLit {
example: "polars lit 2 | polars into-nu", example: "polars lit 2 | polars into-nu",
result: Some(Value::test_record(record! { result: Some(Value::test_record(record! {
"expr" => Value::test_string("literal"), "expr" => Value::test_string("literal"),
"value" => Value::test_string("2"), "value" => Value::test_string("dyn int: 2"),
})), })),
}] }]
} }

View File

@ -195,6 +195,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
| Expr::Len | Expr::Len
| Expr::Nth(_) | Expr::Nth(_)
| Expr::SubPlan(_, _) | Expr::SubPlan(_, _)
| Expr::IndexColumn(_)
| Expr::Selector(_) => None, | Expr::Selector(_) => None,
} }
} }

View File

@ -189,7 +189,7 @@ impl PluginCommand for LazyJoin {
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

@ -116,16 +116,7 @@ fn command(
call: &EvaluatedCall, call: &EvaluatedCall,
lazy: NuLazyFrame, lazy: NuLazyFrame,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let polars_lazy = lazy let polars_lazy = lazy.to_polars().median();
.to_polars()
.median()
.map_err(|e| ShellError::GenericError {
error: format!("Error in median operation: {e}"),
msg: "".into(),
help: None,
span: None,
inner: vec![],
})?;
let lazy = NuLazyFrame::new(lazy.from_eager, polars_lazy); let lazy = NuLazyFrame::new(lazy.from_eager, polars_lazy);
lazy.to_pipeline_data(plugin, engine, call.head) lazy.to_pipeline_data(plugin, engine, call.head)
} }

View File

@ -134,14 +134,7 @@ fn command(
let lazy = NuLazyFrame::new( let lazy = NuLazyFrame::new(
lazy.from_eager, lazy.from_eager,
lazy.to_polars() lazy.to_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![],
})?,
); );
lazy.to_pipeline_data(plugin, engine, call.head) lazy.to_pipeline_data(plugin, engine, call.head)

View File

@ -7,7 +7,7 @@ use nu_protocol::{
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned, Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned,
SyntaxShape, Type, Value, SyntaxShape, Type, Value,
}; };
use polars::prelude::{DataType, Duration, IntoSeries, RollingOptionsImpl, SeriesOpsTime}; use polars::prelude::{DataType, IntoSeries, RollingOptionsFixedWindow, SeriesOpsTime};
enum RollType { enum RollType {
Min, Min,
@ -131,7 +131,7 @@ fn command(
input: PipelineData, input: PipelineData,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let roll_type: Spanned<String> = call.req(0)?; let roll_type: Spanned<String> = call.req(0)?;
let window_size: i64 = call.req(1)?; let window_size: usize = call.req(1)?;
let df = NuDataFrame::try_from_pipeline_coerce(plugin, input, call.head)?; let df = NuDataFrame::try_from_pipeline_coerce(plugin, input, call.head)?;
let series = df.as_series(call.head)?; let series = df.as_series(call.head)?;
@ -148,17 +148,12 @@ fn command(
let roll_type = RollType::from_str(&roll_type.item, roll_type.span)?; let roll_type = RollType::from_str(&roll_type.item, roll_type.span)?;
let rolling_opts = RollingOptionsImpl { let rolling_opts = RollingOptionsFixedWindow {
window_size: Duration::new(window_size), window_size,
min_periods: window_size as usize, min_periods: window_size,
weights: None, ..RollingOptionsFixedWindow::default()
center: false,
by: None,
closed_window: None,
tu: None,
tz: None,
fn_params: None,
}; };
let res = match roll_type { let res = match roll_type {
RollType::Max => series.rolling_max(rolling_opts), RollType::Max => series.rolling_max(rolling_opts),
RollType::Min => series.rolling_min(rolling_opts), RollType::Min => series.rolling_min(rolling_opts),

View File

@ -155,7 +155,10 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Result<Value, ShellError> {
span, span,
)), )),
Expr::Columns(columns) => { Expr::Columns(columns) => {
let value = columns.iter().map(|col| Value::string(col, span)).collect(); let value = columns
.iter()
.map(|col| Value::string(col.to_string(), span))
.collect();
Ok(Value::record( Ok(Value::record(
record! { record! {
"expr" => Value::string("columns", span), "expr" => Value::string("columns", span),
@ -415,6 +418,12 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Result<Value, ShellError> {
msg_span: span, msg_span: span,
input_span: Span::unknown(), input_span: Span::unknown(),
}), }),
Expr::IndexColumn(_) => Err(ShellError::UnsupportedInput {
msg: "Expressions of type IndexColumn to Nu Values is not yet supported".to_string(),
input: format!("Expression is {expr:?}"),
msg_span: span,
input_span: Span::unknown(),
}),
} }
} }

View File

@ -160,7 +160,15 @@ impl CustomValueSupport for NuLazyFrame {
.unwrap_or_else(|_| "<NOT AVAILABLE>".to_string()); .unwrap_or_else(|_| "<NOT AVAILABLE>".to_string());
Ok(Value::record( Ok(Value::record(
record! { record! {
"plan" => Value::string(self.lazy.describe_plan(), span), "plan" => Value::string(
self.lazy.describe_plan().map_err(|e| ShellError::GenericError {
error: "Error getting plan".into(),
msg: e.to_string(),
span: Some(span),
help: None,
inner: vec![],
})?,
span),
"optimized_plan" => Value::string(optimized_plan, span), "optimized_plan" => Value::string(optimized_plan, span),
}, },
span, span,

View File

@ -1,7 +1,10 @@
use std::sync::Arc; use std::sync::Arc;
use nu_protocol::{ShellError, Span, Value}; use nu_protocol::{ShellError, Span, Value};
use polars::prelude::{DataType, Field, Schema, SchemaRef, TimeUnit}; use polars::{
datatypes::UnknownKind,
prelude::{DataType, Field, Schema, SchemaRef, TimeUnit},
};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct NuSchema { pub struct NuSchema {
@ -104,7 +107,7 @@ pub fn str_to_dtype(dtype: &str, span: Span) -> Result<DataType, ShellError> {
"date" => Ok(DataType::Date), "date" => Ok(DataType::Date),
"time" => Ok(DataType::Time), "time" => Ok(DataType::Time),
"null" => Ok(DataType::Null), "null" => Ok(DataType::Null),
"unknown" => Ok(DataType::Unknown), "unknown" => Ok(DataType::Unknown(UnknownKind::Any)),
"object" => Ok(DataType::Object("unknown", None)), "object" => Ok(DataType::Object("unknown", None)),
_ if dtype.starts_with("list") => { _ if dtype.starts_with("list") => {
let dtype = dtype let dtype = dtype
@ -299,7 +302,7 @@ mod test {
let dtype = "unknown"; let dtype = "unknown";
let schema = str_to_dtype(dtype, Span::unknown()).unwrap(); let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
let expected = DataType::Unknown; let expected = DataType::Unknown(UnknownKind::Any);
assert_eq!(schema, expected); assert_eq!(schema, expected);
let dtype = "object"; let dtype = "object";

View File

@ -590,4 +590,18 @@ mod external_command_arguments {
assert_eq!(actual.out, "a;&$(hello)"); assert_eq!(actual.out, "a;&$(hello)");
} }
#[test]
fn remove_quotes_in_shell_arguments() {
let actual = nu!("nu --testbin cococo expression='-r -w'");
assert_eq!(actual.out, "expression=-r -w");
let actual = nu!(r#"nu --testbin cococo expression="-r -w""#);
assert_eq!(actual.out, "expression=-r -w");
let actual = nu!("nu --testbin cococo expression='-r -w'");
assert_eq!(actual.out, "expression=-r -w");
let actual = nu!(r#"nu --testbin cococo expression="-r\" -w""#);
assert_eq!(actual.out, r#"expression=-r" -w"#);
let actual = nu!(r#"nu --testbin cococo expression='-r\" -w'"#);
assert_eq!(actual.out, r#"expression=-r\" -w"#);
}
} }