Merge branch 'main' into fix-run-external-quoting
This commit is contained in:
commit
8dcbe1da14
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
|
@ -18,6 +18,14 @@ updates:
|
|||
ignore:
|
||||
- dependency-name: "*"
|
||||
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"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
|
2
.github/workflows/typos.yml
vendored
2
.github/workflows/typos.yml
vendored
|
@ -10,4 +10,4 @@ jobs:
|
|||
uses: actions/checkout@v4.1.6
|
||||
|
||||
- name: Check spelling
|
||||
uses: crate-ci/typos@v1.22.0
|
||||
uses: crate-ci/typos@v1.22.1
|
||||
|
|
26
CITATION.cff
Normal file
26
CITATION.cff
Normal 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
161
Cargo.lock
generated
|
@ -478,17 +478,6 @@ version = "0.1.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "brotli"
|
||||
version = "5.0.0"
|
||||
|
@ -497,17 +486,7 @@ checksum = "19483b140a7ac7174d34b5a581b406c64f84da5409d3e09cf4fff604f9270e67"
|
|||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
"brotli-decompressor 4.0.0",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"brotli-decompressor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -871,7 +850,7 @@ checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7"
|
|||
dependencies = [
|
||||
"crossterm",
|
||||
"strum",
|
||||
"strum_macros 0.26.2",
|
||||
"strum_macros",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
|
@ -1295,6 +1274,9 @@ name = "either"
|
|||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eml-parser"
|
||||
|
@ -1794,6 +1776,7 @@ dependencies = [
|
|||
"ahash 0.8.11",
|
||||
"allocator-api2",
|
||||
"rayon",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2935,7 +2918,7 @@ dependencies = [
|
|||
"alphanumeric-sort",
|
||||
"base64 0.22.1",
|
||||
"bracoxide",
|
||||
"brotli 5.0.0",
|
||||
"brotli",
|
||||
"byteorder",
|
||||
"bytesize",
|
||||
"calamine",
|
||||
|
@ -3222,7 +3205,7 @@ dependencies = [
|
|||
name = "nu-protocol"
|
||||
version = "0.94.3"
|
||||
dependencies = [
|
||||
"brotli 5.0.0",
|
||||
"brotli",
|
||||
"byte-unit",
|
||||
"chrono",
|
||||
"chrono-humanize",
|
||||
|
@ -3243,7 +3226,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"strum_macros 0.26.2",
|
||||
"strum_macros",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"typetag",
|
||||
|
@ -3404,7 +3387,7 @@ dependencies = [
|
|||
"polars-plan",
|
||||
"polars-utils",
|
||||
"serde",
|
||||
"sqlparser 0.45.0",
|
||||
"sqlparser 0.47.0",
|
||||
"tempfile",
|
||||
"typetag",
|
||||
"uuid",
|
||||
|
@ -3731,9 +3714,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.1.5"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9"
|
||||
checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
|
@ -4014,9 +3997,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea21b858b16b9c0e17a12db2800d11aa5b4bd182be6b3022eb537bbfc1f2db5"
|
||||
checksum = "e148396dca5496566880fa19374f3f789a29db94e3eb458afac1497b4bac5442"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"polars-arrow",
|
||||
|
@ -4034,9 +4017,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-arrow"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "725b09f2b5ef31279b66e27bbab63c58d49d8f6696b66b1f46c7eaab95e80f75"
|
||||
checksum = "1cb5e11cd0752ae022fa6ca3afa50a14b0301b7ce53c0135828fbb0f4fa8303e"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"atoi",
|
||||
|
@ -4082,9 +4065,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-compute"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a796945b14b14fbb79b91ef0406e6fddca2be636e889f81ea5d6ee7d36efb4fe"
|
||||
checksum = "89fc4578f826234cdecb782952aa9c479dc49373f81694a7b439c70b6f609ba0"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"either",
|
||||
|
@ -4098,9 +4081,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-core"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "465f70d3e96b6d0b1a43c358ba451286b8c8bd56696feff020d65702aa33e35c"
|
||||
checksum = "e490c6bace1366a558feea33d1846f749a8ca90bd72a6748752bc65bb4710b2a"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bitflags 2.5.0",
|
||||
|
@ -4132,9 +4115,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-error"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5224d5d05e6b8a6f78b75951ae1b5f82c8ab1979e11ffaf5fd41941e3d5b0757"
|
||||
checksum = "08888f58e61599b00f5ea0c2ccdc796b54b9859559cc0d4582733509451fa01a"
|
||||
dependencies = [
|
||||
"avro-schema",
|
||||
"polars-arrow-format",
|
||||
|
@ -4144,10 +4127,30 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars-io"
|
||||
version = "0.39.2"
|
||||
name = "polars-expr"
|
||||
version = "0.40.0"
|
||||
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 = [
|
||||
"ahash 0.8.11",
|
||||
"async-trait",
|
||||
|
@ -4186,9 +4189,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-json"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81224492a649a12b668480c0cf219d703f432509765d2717e72fe32ad16fc701"
|
||||
checksum = "160cbad0145b93ac6a88639aadfa6f7d7c769d05a8674f9b7e895b398cae9901"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"chrono",
|
||||
|
@ -4207,9 +4210,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-lazy"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b2632b1af668e2058d5f8f916d8fbde3cac63d03ae29a705f598e41dcfeb7f"
|
||||
checksum = "e805ea2ebbc6b7749b0afb31b7fc5d32b42b57ba29b984549d43d3a16114c4a5"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bitflags 2.5.0",
|
||||
|
@ -4217,6 +4220,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"polars-arrow",
|
||||
"polars-core",
|
||||
"polars-expr",
|
||||
"polars-io",
|
||||
"polars-json",
|
||||
"polars-ops",
|
||||
|
@ -4231,13 +4235,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-ops"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efdbdb4d9a92109bc2e0ce8e17af5ae8ab643bb5b7ee9d1d74f0aeffd1fbc95f"
|
||||
checksum = "7b0aed7e169c81b98457641cf82b251f52239a668916c2e683abd1f38df00d58"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"argminmax",
|
||||
"base64 0.21.7",
|
||||
"base64 0.22.1",
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
"chrono-tz 0.8.6",
|
||||
|
@ -4267,14 +4271,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-parquet"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b421d2196f786fdfe162db614c8485f8308fe41575d4de634a39bbe460d1eb6a"
|
||||
checksum = "c70670a9e51cac66d0e77fd20b5cc957dbcf9f2660d410633862bb72f846d5b8"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"async-stream",
|
||||
"base64 0.21.7",
|
||||
"brotli 3.5.0",
|
||||
"base64 0.22.1",
|
||||
"brotli",
|
||||
"ethnum",
|
||||
"flate2",
|
||||
"futures",
|
||||
|
@ -4293,9 +4297,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-pipe"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48700f1d5bd56a15451e581f465c09541492750360f18637b196f995470a015c"
|
||||
checksum = "0a40ae1b3c74ee07e2d1f7cbf56c5d6e15969e45d9b6f0903bd2acaf783ba436"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-queue",
|
||||
|
@ -4305,6 +4309,7 @@ dependencies = [
|
|||
"polars-arrow",
|
||||
"polars-compute",
|
||||
"polars-core",
|
||||
"polars-expr",
|
||||
"polars-io",
|
||||
"polars-ops",
|
||||
"polars-plan",
|
||||
|
@ -4318,13 +4323,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-plan"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fb8e2302e20c44defd5be8cad9c96e75face63c3a5f609aced8c4ec3b3ac97d"
|
||||
checksum = "8daa3541ae7e9af311a4389bc2b21f83349c34c723cc67fa524cdefdaa172d90"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bytemuck",
|
||||
"chrono-tz 0.8.6",
|
||||
"either",
|
||||
"hashbrown 0.14.5",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
|
@ -4341,15 +4347,15 @@ dependencies = [
|
|||
"regex",
|
||||
"serde",
|
||||
"smartstring",
|
||||
"strum_macros 0.25.3",
|
||||
"strum_macros",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polars-row"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a515bdc68c2ae3702e3de70d89601f3b71ca8137e282a226dddb53ee4bacfa2e"
|
||||
checksum = "deb285f2f3a65b00dd06bef16bb9f712dbb5478f941dab5cf74f9f016d382e40"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"polars-arrow",
|
||||
|
@ -4359,11 +4365,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-sql"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4bb7cc1c04c3023d1953b2f1dec50515e8fd8169a5a2bf4967b3b082232db7"
|
||||
checksum = "a724f699d194cb02c25124d3832f7d4d77f387f1a89ee42f6b9e88ec561d4ad9"
|
||||
dependencies = [
|
||||
"hex",
|
||||
"once_cell",
|
||||
"polars-arrow",
|
||||
"polars-core",
|
||||
"polars-error",
|
||||
|
@ -4377,11 +4384,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-time"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efc18e3ad92eec55db89d88f16c22d436559ba7030cf76f86f6ed7a754b673f1"
|
||||
checksum = "87ebec238d8b6200d9f0c3ce411c8441e950bd5a7df7806b8172d06c1d5a4b97"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
"chrono-tz 0.8.6",
|
||||
"now",
|
||||
|
@ -4398,9 +4406,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "polars-utils"
|
||||
version = "0.39.2"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c760b6c698cfe2fbbbd93d6cfb408db14ececfe1d92445dae2229ce1b5b21ae8"
|
||||
checksum = "34e1a907c63abf71e5f21467e2e4ff748896c28196746f631c6c25512ec6102c"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"bytemuck",
|
||||
|
@ -4834,7 +4842,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"strip-ansi-escapes",
|
||||
"strum",
|
||||
"strum_macros 0.26.2",
|
||||
"strum_macros",
|
||||
"thiserror",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
|
@ -5562,9 +5570,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "sqlparser"
|
||||
version = "0.45.0"
|
||||
version = "0.47.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7bbffee862a796d67959a89859d6b1046bb5016d63e23835ad0da182777bbe0"
|
||||
checksum = "295e9930cd7a97e58ca2a070541a3ca502b17f5d1fa7157376d0fabd85324f25"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
@ -5678,20 +5686,7 @@ version = "0.26.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
|
||||
dependencies = [
|
||||
"strum_macros 0.26.2",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -118,7 +118,7 @@ num-traits = "0.2"
|
|||
omnipath = "0.1"
|
||||
once_cell = "1.18"
|
||||
open = "5.1"
|
||||
os_pipe = { version = "1.1", features = ["io_safety"] }
|
||||
os_pipe = { version = "1.2", features = ["io_safety"] }
|
||||
pathdiff = "0.2"
|
||||
percent-encoding = "2"
|
||||
pretty_assertions = "1.4"
|
||||
|
|
|
@ -12,14 +12,17 @@ impl Command for StorInsert {
|
|||
|
||||
fn signature(&self) -> Signature {
|
||||
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(
|
||||
"table-name",
|
||||
SyntaxShape::String,
|
||||
"name of the table you want to insert into",
|
||||
Some('t'),
|
||||
)
|
||||
.required_named(
|
||||
.named(
|
||||
"data-record",
|
||||
SyntaxShape::Record(vec![]),
|
||||
"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> {
|
||||
vec![Example {
|
||||
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}",
|
||||
result: None,
|
||||
}]
|
||||
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}",
|
||||
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(
|
||||
|
@ -50,25 +59,79 @@ impl Command for StorInsert {
|
|||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
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 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)?;
|
||||
|
||||
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(
|
||||
table_name: Option<String>,
|
||||
span: Span,
|
||||
db: &SQLiteDatabase,
|
||||
columns: Option<Record>,
|
||||
record: Record,
|
||||
) -> Result<(), ShellError> {
|
||||
if table_name.is_none() {
|
||||
return Err(ShellError::MissingParameter {
|
||||
|
@ -77,54 +140,45 @@ fn process(
|
|||
});
|
||||
}
|
||||
let new_table_name = table_name.unwrap_or("table".into());
|
||||
|
||||
if let Ok(conn) = db.open_connection() {
|
||||
match columns {
|
||||
Some(record) => {
|
||||
let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name);
|
||||
let cols = record.columns();
|
||||
cols.for_each(|col| {
|
||||
create_stmt.push_str(&format!("{}, ", col));
|
||||
});
|
||||
if create_stmt.ends_with(", ") {
|
||||
create_stmt.pop();
|
||||
create_stmt.pop();
|
||||
}
|
||||
let mut create_stmt = format!("INSERT INTO {} ( ", new_table_name);
|
||||
let cols = record.columns();
|
||||
cols.for_each(|col| {
|
||||
create_stmt.push_str(&format!("{}, ", col));
|
||||
});
|
||||
if create_stmt.ends_with(", ") {
|
||||
create_stmt.pop();
|
||||
create_stmt.pop();
|
||||
}
|
||||
|
||||
// Values are set as placeholders.
|
||||
create_stmt.push_str(") VALUES ( ");
|
||||
for (index, _) in record.columns().enumerate() {
|
||||
create_stmt.push_str(&format!("?{}, ", index + 1));
|
||||
}
|
||||
// Values are set as placeholders.
|
||||
create_stmt.push_str(") VALUES ( ");
|
||||
for (index, _) in record.columns().enumerate() {
|
||||
create_stmt.push_str(&format!("?{}, ", index + 1));
|
||||
}
|
||||
|
||||
if create_stmt.ends_with(", ") {
|
||||
create_stmt.pop();
|
||||
create_stmt.pop();
|
||||
}
|
||||
if create_stmt.ends_with(", ") {
|
||||
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
|
||||
let params = values_to_sql(record.values().cloned())?;
|
||||
// Get the params from the passed values
|
||||
let params = values_to_sql(record.values().cloned())?;
|
||||
|
||||
conn.execute(&create_stmt, params_from_iter(params))
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: "Failed to open SQLite connection in memory from insert".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,
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
conn.execute(&create_stmt, params_from_iter(params))
|
||||
.map_err(|err| ShellError::GenericError {
|
||||
error: "Failed to open SQLite connection in memory from insert".into(),
|
||||
msg: err.to_string(),
|
||||
span: Some(Span::test_data()),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
};
|
||||
// dbg!(db.clone());
|
||||
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());
|
||||
}
|
||||
|
@ -201,7 +255,7 @@ mod test {
|
|||
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());
|
||||
}
|
||||
|
@ -226,7 +280,7 @@ mod test {
|
|||
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
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
@ -251,7 +305,7 @@ mod test {
|
|||
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
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
@ -276,7 +330,7 @@ mod test {
|
|||
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());
|
||||
}
|
||||
|
@ -293,7 +347,7 @@ mod test {
|
|||
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());
|
||||
}
|
||||
|
|
|
@ -11,14 +11,17 @@ impl Command for StorUpdate {
|
|||
|
||||
fn signature(&self) -> Signature {
|
||||
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(
|
||||
"table-name",
|
||||
SyntaxShape::String,
|
||||
"name of the table you want to insert into",
|
||||
Some('t'),
|
||||
)
|
||||
.required_named(
|
||||
.named(
|
||||
"update-record",
|
||||
SyntaxShape::Record(vec![]),
|
||||
"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\"",
|
||||
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,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
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>> =
|
||||
call.get_flag(engine_state, stack, "where-clause")?;
|
||||
|
||||
// Open the in-mem database
|
||||
let db = Box::new(SQLiteDatabase::new(std::path::Path::new(MEMORY_DB), None));
|
||||
|
||||
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() {
|
||||
match columns {
|
||||
Some(record) => {
|
||||
let mut update_stmt = format!("UPDATE {} ", new_table_name);
|
||||
// Check if the record is being passed as input or using the update record parameter
|
||||
let columns = handle(span, update_record, input)?;
|
||||
|
||||
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();
|
||||
}
|
||||
process(table_name, span, &db, columns, where_clause_opt)?;
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
|
|
@ -27,7 +27,7 @@ impl Command for FormatDate {
|
|||
SyntaxShape::String,
|
||||
"The desired format date.",
|
||||
)
|
||||
.category(Category::Date)
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
|
|
@ -6,6 +6,7 @@ use nu_protocol::{
|
|||
};
|
||||
use nu_system::ForegroundChild;
|
||||
use nu_utils::IgnoreCaseExt;
|
||||
use pathdiff::diff_paths;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
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
|
||||
// dir
|
||||
let glob = NuGlob::Expand(arg.to_owned()).into_spanned(span);
|
||||
let Ok((_prefix, paths)) = nu_engine::glob_from(&glob, cwd, span, None) else {
|
||||
// If an error occurred, return the original input
|
||||
return Ok(vec![arg.into()]);
|
||||
};
|
||||
if let Ok((prefix, matches)) = nu_engine::glob_from(&glob, cwd, span, None) {
|
||||
let mut result: Vec<OsString> = vec![];
|
||||
|
||||
// If the first component of the original `arg` string path was '.', that should be preserved
|
||||
let relative_to_dot = Path::new(arg).starts_with(".");
|
||||
|
||||
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
|
||||
for m in matches {
|
||||
if nu_utils::ctrl_c::was_pressed(interrupt) {
|
||||
return Err(ShellError::InterruptedByUser { span: Some(span) });
|
||||
}
|
||||
})
|
||||
// Make the paths relative to the cwd
|
||||
.map(|path| {
|
||||
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)
|
||||
if let Ok(arg) = m {
|
||||
let arg = resolve_globbed_path_to_cwd_relative(arg, prefix.as_ref(), cwd);
|
||||
result.push(arg.into());
|
||||
} 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() {
|
||||
Ok(paths)
|
||||
// FIXME: do we want to special-case this further? We might accidentally expand when they don't
|
||||
// intend to
|
||||
if result.is_empty() {
|
||||
result.push(arg.into());
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
} else {
|
||||
// If we failed to match, return the original input
|
||||
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
|
||||
/// first rendered using the `table` command.
|
||||
///
|
||||
|
@ -601,10 +599,6 @@ mod test {
|
|||
assert_eq!(actual, expected);
|
||||
|
||||
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);
|
||||
|
||||
let actual = expand_glob("'*.txt'", cwd, Span::unknown(), &None).unwrap();
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::{
|
|||
views::{Orientation, RecordView},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use nu_ansi_term::Style;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Value,
|
||||
|
@ -19,12 +18,6 @@ pub struct TableCmd {
|
|||
#[derive(Debug, Default, Clone)]
|
||||
struct TableSettings {
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -64,8 +57,6 @@ impl ViewCommand for TableCmd {
|
|||
|
||||
let mut view = RecordView::new(columns, data);
|
||||
|
||||
// todo: use setup instead ????
|
||||
|
||||
if is_record {
|
||||
view.set_orientation_current(Orientation::Left);
|
||||
}
|
||||
|
@ -74,32 +65,6 @@ impl ViewCommand for TableCmd {
|
|||
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 {
|
||||
view.set_cursor_mode();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::ViewCommand;
|
||||
use crate::views::InteractiveView;
|
||||
use crate::views::TryView;
|
||||
use anyhow::Result;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
|
@ -22,7 +22,7 @@ impl TryCmd {
|
|||
}
|
||||
|
||||
impl ViewCommand for TryCmd {
|
||||
type View = InteractiveView<'static>;
|
||||
type View = TryView<'static>;
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
Self::NAME
|
||||
|
@ -45,7 +45,7 @@ impl ViewCommand for TryCmd {
|
|||
value: Option<Value>,
|
||||
) -> Result<Self::View> {
|
||||
let value = value.unwrap_or_default();
|
||||
let mut view = InteractiveView::new(value);
|
||||
let mut view = TryView::new(value);
|
||||
view.init(self.command.clone());
|
||||
view.try_run(engine_state, stack)?;
|
||||
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use crate::{
|
||||
run_pager,
|
||||
util::{create_lscolors, create_map, map_into_value},
|
||||
PagerConfig, StyleConfig,
|
||||
util::{create_lscolors, create_map},
|
||||
PagerConfig,
|
||||
};
|
||||
use nu_ansi_term::{Color, Style};
|
||||
use nu_color_config::{get_color_map, StyleComputer};
|
||||
use nu_engine::command_prelude::*;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use nu_protocol::Config;
|
||||
|
||||
/// A `less` like program to render a [`Value`] as a table.
|
||||
#[derive(Clone)]
|
||||
|
@ -68,19 +67,21 @@ impl Command for Explore {
|
|||
let nu_config = engine_state.get_config();
|
||||
let style_computer = StyleComputer::from_config(engine_state, stack);
|
||||
|
||||
let mut config = nu_config.explore.clone();
|
||||
include_nu_config(&mut config, &style_computer);
|
||||
update_config(&mut config, show_index, show_head);
|
||||
prepare_default_config(&mut config);
|
||||
|
||||
let style = style_from_config(&config);
|
||||
let mut explore_config = ExploreConfig::from_nu_config(nu_config);
|
||||
explore_config.table.show_header = show_head;
|
||||
explore_config.table.show_index = show_index;
|
||||
explore_config.table.separator_style = lookup_color(&style_computer, "separator");
|
||||
|
||||
let lscolors = create_lscolors(engine_state, stack);
|
||||
|
||||
let mut config = PagerConfig::new(nu_config, &style_computer, &lscolors, config);
|
||||
config.style = style;
|
||||
config.peek_value = peek_value;
|
||||
config.tail = tail;
|
||||
let config = PagerConfig::new(
|
||||
nu_config,
|
||||
&explore_config,
|
||||
&style_computer,
|
||||
&lscolors,
|
||||
peek_value,
|
||||
tail,
|
||||
);
|
||||
|
||||
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) {
|
||||
let mut hm = config.get("table").and_then(create_map).unwrap_or_default();
|
||||
if show_index {
|
||||
insert_bool(&mut hm, "show_index", show_index);
|
||||
}
|
||||
|
||||
if show_head {
|
||||
insert_bool(&mut hm, "show_head", show_head);
|
||||
}
|
||||
|
||||
config.insert(String::from("table"), map_into_value(hm));
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExploreConfig {
|
||||
pub table: TableConfig,
|
||||
pub selected_cell: Style,
|
||||
pub status_info: Style,
|
||||
pub status_success: Style,
|
||||
pub status_warn: Style,
|
||||
pub status_error: Style,
|
||||
pub status_bar_background: Style,
|
||||
pub status_bar_text: Style,
|
||||
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 {
|
||||
let mut style = StyleConfig::default();
|
||||
|
||||
let colors = get_color_map(config);
|
||||
|
||||
if let Some(s) = colors.get("status_bar_text") {
|
||||
style.status_bar_text = *s;
|
||||
}
|
||||
|
||||
if let Some(s) = colors.get("status_bar_background") {
|
||||
style.status_bar_background = *s;
|
||||
}
|
||||
|
||||
if let Some(s) = colors.get("command_bar_text") {
|
||||
style.cmd_bar_text = *s;
|
||||
}
|
||||
|
||||
if let Some(s) = colors.get("command_bar_background") {
|
||||
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;
|
||||
impl Default for ExploreConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
table: TableConfig::default(),
|
||||
selected_cell: color(None, Some(Color::LightBlue)),
|
||||
status_info: color(None, None),
|
||||
status_success: color(Some(Color::Black), Some(Color::Green)),
|
||||
status_warn: color(None, None),
|
||||
status_error: color(Some(Color::White), Some(Color::Red)),
|
||||
status_bar_background: color(
|
||||
Some(Color::Rgb(29, 31, 33)),
|
||||
Some(Color::Rgb(196, 201, 198)),
|
||||
),
|
||||
status_bar_text: color(None, None),
|
||||
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)),
|
||||
try_reactive: false,
|
||||
}
|
||||
}
|
||||
|
||||
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>) {
|
||||
const STATUS_BAR: Style = color(
|
||||
Some(Color::Rgb(29, 31, 33)),
|
||||
Some(Color::Rgb(196, 201, 198)),
|
||||
);
|
||||
const INPUT_BAR: Style = color(Some(Color::Rgb(196, 201, 198)), None);
|
||||
ret.table.column_padding_left = config.table_indent.left;
|
||||
ret.table.column_padding_right = config.table_indent.right;
|
||||
|
||||
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));
|
||||
const STATUS_INFO: Style = color(None, None);
|
||||
const STATUS_SUCCESS: Style = color(Some(Color::Black), Some(Color::Green));
|
||||
const STATUS_WARN: Style = color(None, None);
|
||||
if let Some(s) = colors.get("status_bar_text") {
|
||||
ret.status_bar_text = *s;
|
||||
}
|
||||
|
||||
const TABLE_SPLIT_LINE: Style = color(Some(Color::Rgb(64, 64, 64)), None);
|
||||
const TABLE_SELECT_CELL: Style = color(None, None);
|
||||
const TABLE_SELECT_ROW: Style = color(None, None);
|
||||
const TABLE_SELECT_COLUMN: Style = color(None, None);
|
||||
if let Some(s) = colors.get("status_bar_background") {
|
||||
ret.status_bar_background = *s;
|
||||
}
|
||||
|
||||
const HEXDUMP_INDEX: Style = color(Some(Color::Cyan), None);
|
||||
const HEXDUMP_SEGMENT: Style = color(Some(Color::Cyan), None).bold();
|
||||
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();
|
||||
if let Some(s) = colors.get("command_bar_text") {
|
||||
ret.cmd_bar_text = *s;
|
||||
}
|
||||
|
||||
insert_style(config, "status_bar_background", STATUS_BAR);
|
||||
insert_style(config, "command_bar_text", INPUT_BAR);
|
||||
insert_style(config, "highlight", HIGHLIGHT);
|
||||
if let Some(s) = colors.get("command_bar_background") {
|
||||
ret.cmd_bar_background = *s;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
{
|
||||
let mut hm = config
|
||||
.get("status")
|
||||
.and_then(parse_hash_map)
|
||||
.unwrap_or_default();
|
||||
if let Some(s) = colors.get("selected_cell") {
|
||||
ret.selected_cell = *s;
|
||||
}
|
||||
|
||||
insert_style(&mut hm, "info", STATUS_INFO);
|
||||
insert_style(&mut hm, "success", STATUS_SUCCESS);
|
||||
insert_style(&mut hm, "warn", STATUS_WARN);
|
||||
insert_style(&mut hm, "error", STATUS_ERROR);
|
||||
if let Some(hm) = explore_cfg_hash_map.get("status").and_then(create_map) {
|
||||
let colors = get_color_map(&hm);
|
||||
|
||||
config.insert(String::from("status"), map_into_value(hm));
|
||||
}
|
||||
if let Some(s) = colors.get("info") {
|
||||
ret.status_info = *s;
|
||||
}
|
||||
|
||||
{
|
||||
let mut hm = config
|
||||
.get("table")
|
||||
.and_then(parse_hash_map)
|
||||
.unwrap_or_default();
|
||||
if let Some(s) = colors.get("success") {
|
||||
ret.status_success = *s;
|
||||
}
|
||||
|
||||
insert_style(&mut hm, "split_line", TABLE_SPLIT_LINE);
|
||||
insert_style(&mut hm, "selected_cell", TABLE_SELECT_CELL);
|
||||
insert_style(&mut hm, "selected_row", TABLE_SELECT_ROW);
|
||||
insert_style(&mut hm, "selected_column", TABLE_SELECT_COLUMN);
|
||||
if let Some(s) = colors.get("warn") {
|
||||
ret.status_warn = *s;
|
||||
}
|
||||
|
||||
config.insert(String::from("table"), map_into_value(hm));
|
||||
}
|
||||
if let Some(s) = colors.get("error") {
|
||||
ret.status_error = *s;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut hm = config
|
||||
.get("hex-dump")
|
||||
.and_then(create_map)
|
||||
.unwrap_or_default();
|
||||
if let Some(hm) = explore_cfg_hash_map.get("try").and_then(create_map) {
|
||||
if let Some(reactive) = hm.get("reactive") {
|
||||
if let Ok(b) = reactive.as_bool() {
|
||||
ret.try_reactive = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insert_style(&mut hm, "color_index", HEXDUMP_INDEX);
|
||||
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));
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hash_map(value: &Value) -> Option<HashMap<String, Value>> {
|
||||
value.as_record().ok().map(|val| {
|
||||
val.iter()
|
||||
.map(|(col, val)| (col.clone(), val.clone()))
|
||||
.collect::<HashMap<_, _>>()
|
||||
})
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct TableConfig {
|
||||
pub separator_style: Style,
|
||||
pub show_index: bool,
|
||||
pub show_header: bool,
|
||||
pub column_padding_left: usize,
|
||||
pub column_padding_right: usize,
|
||||
}
|
||||
|
||||
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 {
|
||||
style_computer.compute(key, &Value::nothing(Span::unknown()))
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ use nu_protocol::{
|
|||
engine::{EngineState, Stack},
|
||||
PipelineData, Value,
|
||||
};
|
||||
use pager::{Page, Pager, PagerConfig, StyleConfig};
|
||||
use pager::{Page, Pager, PagerConfig};
|
||||
use registry::CommandRegistry;
|
||||
use terminal_size::{Height, Width};
|
||||
use views::{BinaryView, Orientation, Preview, RecordView};
|
||||
|
||||
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(
|
||||
|
|
|
@ -18,7 +18,7 @@ pub use command::run_command_with_value;
|
|||
pub use lscolor::{create_lscolors, lscolorize};
|
||||
pub use string::{string_width, truncate_str};
|
||||
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> {
|
||||
if data.len() == 1
|
||||
|
|
|
@ -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 {
|
||||
Value::string(String::from("❎"), NuSpan::unknown())
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ use self::{
|
|||
};
|
||||
use super::views::{Layout, View};
|
||||
use crate::{
|
||||
nu_common::{CtrlC, NuColor, NuConfig, NuSpan, NuStyle},
|
||||
explore::ExploreConfig,
|
||||
nu_common::{CtrlC, NuColor, NuConfig, NuStyle},
|
||||
registry::{Command, CommandRegistry},
|
||||
util::map_into_value,
|
||||
views::{util::nu_style_to_tui, ViewConfig},
|
||||
};
|
||||
use anyhow::Result;
|
||||
|
@ -26,15 +26,14 @@ use crossterm::{
|
|||
};
|
||||
use events::UIEvents;
|
||||
use lscolors::LsColors;
|
||||
use nu_color_config::{lookup_ansi_color_style, StyleComputer};
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Record, Value,
|
||||
Value,
|
||||
};
|
||||
use ratatui::{backend::CrosstermBackend, layout::Rect, widgets::Block};
|
||||
use std::{
|
||||
cmp::min,
|
||||
collections::HashMap,
|
||||
io::{self, Stdout},
|
||||
result,
|
||||
sync::atomic::Ordering,
|
||||
|
@ -42,7 +41,6 @@ use std::{
|
|||
|
||||
pub type Frame<'a> = ratatui::Frame<'a>;
|
||||
pub type Terminal = ratatui::Terminal<CrosstermBackend<Stdout>>;
|
||||
pub type ConfigMap = HashMap<String, Value>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Pager<'a> {
|
||||
|
@ -73,19 +71,6 @@ struct CommandBuf {
|
|||
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> {
|
||||
pub fn new(config: PagerConfig<'a>) -> Self {
|
||||
Self {
|
||||
|
@ -100,27 +85,6 @@ impl<'a> Pager<'a> {
|
|||
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(
|
||||
&mut self,
|
||||
engine_state: &EngineState,
|
||||
|
@ -132,8 +96,8 @@ impl<'a> Pager<'a> {
|
|||
if let Some(page) = &mut view {
|
||||
page.view.setup(ViewConfig::new(
|
||||
self.config.nu_config,
|
||||
self.config.explore_config,
|
||||
self.config.style_computer,
|
||||
&self.config.config,
|
||||
self.config.lscolors,
|
||||
))
|
||||
}
|
||||
|
@ -153,10 +117,9 @@ pub enum Transition {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct PagerConfig<'a> {
|
||||
pub nu_config: &'a NuConfig,
|
||||
pub explore_config: &'a ExploreConfig,
|
||||
pub style_computer: &'a StyleComputer<'a>,
|
||||
pub lscolors: &'a LsColors,
|
||||
pub config: ConfigMap,
|
||||
pub style: StyleConfig,
|
||||
// If true, when quitting output the value of the cell the cursor was on
|
||||
pub peek_value: bool,
|
||||
pub tail: bool,
|
||||
|
@ -165,18 +128,19 @@ pub struct PagerConfig<'a> {
|
|||
impl<'a> PagerConfig<'a> {
|
||||
pub fn new(
|
||||
nu_config: &'a NuConfig,
|
||||
explore_config: &'a ExploreConfig,
|
||||
style_computer: &'a StyleComputer,
|
||||
lscolors: &'a LsColors,
|
||||
config: ConfigMap,
|
||||
peek_value: bool,
|
||||
tail: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
nu_config,
|
||||
explore_config,
|
||||
style_computer,
|
||||
config,
|
||||
lscolors,
|
||||
peek_value: false,
|
||||
tail: false,
|
||||
style: StyleConfig::default(),
|
||||
peek_value,
|
||||
tail,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -401,7 +365,7 @@ fn draw_frame(
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -411,19 +375,24 @@ fn draw_info(f: &mut Frame, pager: &mut Pager<'_>, info: ViewInfo) {
|
|||
if let Some(report) = info.status {
|
||||
let last_2nd_line = area.bottom().saturating_sub(2);
|
||||
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 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> {
|
||||
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(
|
||||
|
@ -463,16 +432,7 @@ fn run_command(
|
|||
let value = view_stack.curr_view.as_mut().and_then(|p| p.view.exit());
|
||||
let transition = command.react(engine_state, stack, pager, value)?;
|
||||
match transition {
|
||||
Transition::Ok => {
|
||||
// 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::Ok => Ok(CmdResult::new(false, false, String::new())),
|
||||
Transition::Exit => Ok(CmdResult::new(true, false, String::new())),
|
||||
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));
|
||||
|
||||
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<'_>) {
|
||||
if let Some(page) = view_stack.curr_view.as_mut() {
|
||||
update_view_setup(&mut page.view, cfg);
|
||||
}
|
||||
|
||||
for page in &mut view_stack.stack {
|
||||
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);
|
||||
fn setup_view(view: &mut Box<dyn View>, cfg: &PagerConfig<'_>) {
|
||||
let cfg = ViewConfig::new(
|
||||
cfg.nu_config,
|
||||
cfg.explore_config,
|
||||
cfg.style_computer,
|
||||
cfg.lscolors,
|
||||
);
|
||||
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 mut status_bar = create_status_bar(report);
|
||||
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) {
|
||||
style
|
||||
} else {
|
||||
report_level_style(report.level, theme)
|
||||
report_level_style(report.level, config)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,15 +518,15 @@ fn render_cmd_bar(
|
|||
area: Rect,
|
||||
pager: &Pager,
|
||||
report: Option<Report>,
|
||||
theme: &StyleConfig,
|
||||
config: &ExploreConfig,
|
||||
) {
|
||||
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(
|
||||
&report.message,
|
||||
&report.context1,
|
||||
style,
|
||||
theme.cmd_bar_background,
|
||||
config.cmd_bar_background,
|
||||
);
|
||||
|
||||
f.render_widget(bar, area);
|
||||
|
@ -578,16 +534,16 @@ fn render_cmd_bar(
|
|||
}
|
||||
|
||||
if pager.cmd_buf.is_cmd_input {
|
||||
render_cmd_bar_cmd(f, area, pager, theme);
|
||||
render_cmd_bar_cmd(f, area, pager, config);
|
||||
return;
|
||||
}
|
||||
|
||||
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 {
|
||||
let message = format!("Pattern not found: {}", pager.search_buf.buf_cmd_input);
|
||||
let style = NuStyle {
|
||||
|
@ -596,7 +552,7 @@ fn render_cmd_bar_search(f: &mut Frame, area: Rect, pager: &Pager<'_>, theme: &S
|
|||
..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);
|
||||
return;
|
||||
}
|
||||
|
@ -615,11 +571,11 @@ fn render_cmd_bar_search(f: &mut Frame, area: Rect, pager: &Pager<'_>, theme: &S
|
|||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
if input.len() > area.width as usize + 1 {
|
||||
// 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 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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
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 {
|
||||
fn report_level_style(level: Severity, config: &ExploreConfig) -> NuStyle {
|
||||
match level {
|
||||
Severity::Info => theme.status_info,
|
||||
Severity::Success => theme.status_success,
|
||||
Severity::Warn => theme.status_warn,
|
||||
Severity::Err => theme.status_error,
|
||||
Severity::Info => config.status_info,
|
||||
Severity::Success => config.status_success,
|
||||
Severity::Warn => config.status_warn,
|
||||
Severity::Err => config.status_error,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
mod binary_widget;
|
||||
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use nu_color_config::get_color_map;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Value,
|
||||
|
@ -11,12 +10,12 @@ use nu_protocol::{
|
|||
use ratatui::layout::Rect;
|
||||
|
||||
use crate::{
|
||||
explore::ExploreConfig,
|
||||
nu_common::NuText,
|
||||
pager::{
|
||||
report::{Report, Severity},
|
||||
ConfigMap, Frame, Transition, ViewInfo,
|
||||
Frame, Transition, ViewInfo,
|
||||
},
|
||||
util::create_map,
|
||||
views::cursor::Position,
|
||||
};
|
||||
|
||||
|
@ -90,12 +89,7 @@ impl View for BinaryView {
|
|||
}
|
||||
|
||||
fn setup(&mut self, cfg: ViewConfig<'_>) {
|
||||
let hm = match cfg.config.get("hex-dump").and_then(create_map) {
|
||||
Some(hm) => hm,
|
||||
None => return,
|
||||
};
|
||||
|
||||
self.settings = settings_from_config(&hm);
|
||||
self.settings = settings_from_config(cfg.explore_config);
|
||||
|
||||
let count_rows =
|
||||
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 {
|
||||
let colors = get_color_map(config);
|
||||
|
||||
fn settings_from_config(config: &ExploreConfig) -> Settings {
|
||||
// Most of this is hardcoded for now, add it to the config later if needed
|
||||
Settings {
|
||||
opts: BinarySettings::new(
|
||||
config_get_usize(config, "segment_size", 2),
|
||||
config_get_usize(config, "count_segments", 8),
|
||||
),
|
||||
opts: BinarySettings::new(2, 8),
|
||||
style: BinaryStyle::new(
|
||||
colors.get("color_index").cloned(),
|
||||
config_get_usize(config, "column_padding_left", 1) as u16,
|
||||
config_get_usize(config, "column_padding_right", 1) as u16,
|
||||
None,
|
||||
config.table.column_padding_left 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 {
|
||||
let covered_percent = report_row_position(cursor);
|
||||
let cursor = report_cursor_position(cursor);
|
||||
|
|
|
@ -28,11 +28,10 @@ struct Cursor {
|
|||
|
||||
impl Cursor {
|
||||
/// Constructor to create a new Cursor
|
||||
pub fn new(size: usize) -> Result<Self> {
|
||||
if size == 0 {
|
||||
bail!("Size cannot be zero");
|
||||
}
|
||||
Ok(Cursor { position: 0, size })
|
||||
pub fn new(size: usize) -> Self {
|
||||
// In theory we should not be able to create a cursor with size 0, but in practice
|
||||
// it's easier to allow that for empty lists etc. instead of propagating errors
|
||||
Cursor { position: 0, size }
|
||||
}
|
||||
|
||||
/// The max position the cursor can be at
|
||||
|
@ -88,7 +87,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_cursor_set_position() {
|
||||
// from 0 to 9
|
||||
let mut cursor = Cursor::new(10).unwrap();
|
||||
let mut cursor = Cursor::new(10);
|
||||
cursor.set_position(5);
|
||||
assert_eq!(cursor.position, 5);
|
||||
|
||||
|
@ -99,7 +98,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_cursor_move_forward() {
|
||||
// from 0 to 9
|
||||
let mut cursor = Cursor::new(10).unwrap();
|
||||
let mut cursor = Cursor::new(10);
|
||||
assert_eq!(cursor.position, 0);
|
||||
cursor.move_forward(3);
|
||||
assert_eq!(cursor.position, 3);
|
||||
|
@ -111,7 +110,7 @@ mod tests {
|
|||
#[test]
|
||||
fn test_cursor_move_backward() {
|
||||
// from 0 to 9
|
||||
let mut cursor = Cursor::new(10).unwrap();
|
||||
let mut cursor = Cursor::new(10);
|
||||
cursor.move_backward(3);
|
||||
assert_eq!(cursor.position, 0);
|
||||
|
||||
|
|
|
@ -42,8 +42,8 @@ impl WindowCursor {
|
|||
}
|
||||
|
||||
Ok(Self {
|
||||
view: Cursor::new(view_size)?,
|
||||
window: Cursor::new(window_size)?,
|
||||
view: Cursor::new(view_size),
|
||||
window: Cursor::new(window_size),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
mod binary;
|
||||
mod colored_text_widget;
|
||||
mod cursor;
|
||||
mod interactive;
|
||||
mod preview;
|
||||
mod record;
|
||||
mod r#try;
|
||||
pub mod util;
|
||||
|
||||
use super::{
|
||||
nu_common::NuText,
|
||||
pager::{Frame, Transition, ViewInfo},
|
||||
};
|
||||
use crate::{nu_common::NuConfig, pager::ConfigMap};
|
||||
use crate::{explore::ExploreConfig, nu_common::NuConfig};
|
||||
use crossterm::event::KeyEvent;
|
||||
use lscolors::LsColors;
|
||||
use nu_color_config::StyleComputer;
|
||||
|
@ -21,8 +21,8 @@ use nu_protocol::{
|
|||
use ratatui::layout::Rect;
|
||||
|
||||
pub use binary::BinaryView;
|
||||
pub use interactive::InteractiveView;
|
||||
pub use preview::Preview;
|
||||
pub use r#try::TryView;
|
||||
pub use record::{Orientation, RecordView};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -55,22 +55,22 @@ impl ElementInfo {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ViewConfig<'a> {
|
||||
pub nu_config: &'a NuConfig,
|
||||
pub explore_config: &'a ExploreConfig,
|
||||
pub style_computer: &'a StyleComputer<'a>,
|
||||
pub config: &'a ConfigMap,
|
||||
pub lscolors: &'a LsColors,
|
||||
}
|
||||
|
||||
impl<'a> ViewConfig<'a> {
|
||||
pub fn new(
|
||||
nu_config: &'a NuConfig,
|
||||
explore_config: &'a ExploreConfig,
|
||||
style_computer: &'a StyleComputer<'a>,
|
||||
config: &'a ConfigMap,
|
||||
lscolors: &'a LsColors,
|
||||
) -> Self {
|
||||
Self {
|
||||
nu_config,
|
||||
explore_config,
|
||||
style_computer,
|
||||
config,
|
||||
lscolors,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
mod table_widget;
|
||||
|
||||
use self::table_widget::{TableStyle, TableWidget, TableWidgetState};
|
||||
use self::table_widget::{TableWidget, TableWidgetState};
|
||||
use super::{
|
||||
cursor::{Position, WindowCursor2D},
|
||||
util::{make_styled_string, nu_style_to_tui},
|
||||
Layout, View, ViewConfig,
|
||||
};
|
||||
use crate::{
|
||||
nu_common::{collect_input, lscolorize, NuConfig, NuSpan, NuStyle, NuText},
|
||||
explore::ExploreConfig,
|
||||
nu_common::{collect_input, lscolorize, NuSpan, NuText},
|
||||
pager::{
|
||||
report::{Report, Severity},
|
||||
ConfigMap, Frame, Transition, ViewInfo,
|
||||
Frame, Transition, ViewInfo,
|
||||
},
|
||||
util::create_map,
|
||||
views::ElementInfo,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use nu_color_config::{get_color_map, StyleComputer};
|
||||
use nu_color_config::StyleComputer;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
Record, Span, Value,
|
||||
Config, Record, Span, Value,
|
||||
};
|
||||
use ratatui::{layout::Rect, widgets::Block};
|
||||
use std::{borrow::Cow, collections::HashMap};
|
||||
|
@ -32,7 +32,7 @@ pub struct RecordView<'a> {
|
|||
layer_stack: Vec<RecordLayer<'a>>,
|
||||
mode: UIMode,
|
||||
orientation: Orientation,
|
||||
theme: TableTheme,
|
||||
cfg: ExploreConfig,
|
||||
}
|
||||
|
||||
impl<'a> RecordView<'a> {
|
||||
|
@ -44,52 +44,18 @@ impl<'a> RecordView<'a> {
|
|||
layer_stack: vec![RecordLayer::new(columns, records)],
|
||||
mode: UIMode::View,
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
let layer = self.get_layer_last_mut();
|
||||
transpose_table(layer);
|
||||
|
@ -193,7 +159,7 @@ impl<'a> RecordView<'a> {
|
|||
style_computer,
|
||||
row,
|
||||
column,
|
||||
self.theme.table,
|
||||
self.cfg.table,
|
||||
layer.orientation,
|
||||
)
|
||||
}
|
||||
|
@ -252,11 +218,11 @@ impl View for RecordView<'_> {
|
|||
column,
|
||||
table_layout.count_rows,
|
||||
self.get_layer_last().orientation,
|
||||
self.theme.table.show_header,
|
||||
self.cfg.table.show_header,
|
||||
);
|
||||
|
||||
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(
|
||||
&self.get_layer_last().records,
|
||||
&NuConfig::default(),
|
||||
&nu_protocol::Config::default(),
|
||||
&style_computer,
|
||||
);
|
||||
|
||||
|
@ -338,22 +304,7 @@ impl View for RecordView<'_> {
|
|||
|
||||
// todo: move the method to Command?
|
||||
fn setup(&mut self, cfg: ViewConfig<'_>) {
|
||||
if let Some(hm) = cfg.config.get("table").and_then(create_map) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.cfg = cfg.explore_config.clone();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -660,7 +611,7 @@ fn tail_data(state: &mut RecordView<'_>, page_size: usize) {
|
|||
|
||||
fn convert_records_to_string(
|
||||
records: &[Vec<Value>],
|
||||
cfg: &NuConfig,
|
||||
cfg: &Config,
|
||||
style_computer: &StyleComputer,
|
||||
) -> Vec<Vec<NuText>> {
|
||||
records
|
||||
|
@ -678,31 +629,8 @@ fn convert_records_to_string(
|
|||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn highlight_cell(f: &mut Frame, area: Rect, info: ElementInfo, theme: &CursorStyle) {
|
||||
// highlight selected column
|
||||
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
|
||||
}
|
||||
};
|
||||
fn highlight_selected_cell(f: &mut Frame, info: ElementInfo, cfg: &ExploreConfig) {
|
||||
let cell_style = cfg.selected_cell;
|
||||
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);
|
||||
f.render_widget(highlight_block.clone(), area)
|
||||
|
@ -842,53 +770,3 @@ fn _transpose_table(
|
|||
|
||||
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>,
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::Layout;
|
||||
use crate::{
|
||||
explore::TableConfig,
|
||||
nu_common::{truncate_str, NuStyle, NuText},
|
||||
views::util::{nu_style_to_tui, text_style_to_tui_style},
|
||||
};
|
||||
|
@ -23,7 +24,7 @@ pub struct TableWidget<'a> {
|
|||
data: Cow<'a, [Vec<NuText>]>,
|
||||
index_row: usize,
|
||||
index_column: usize,
|
||||
style: TableStyle,
|
||||
config: TableConfig,
|
||||
header_position: Orientation,
|
||||
style_computer: &'a StyleComputer<'a>,
|
||||
}
|
||||
|
@ -35,15 +36,6 @@ pub enum Orientation {
|
|||
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> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
|
@ -52,7 +44,7 @@ impl<'a> TableWidget<'a> {
|
|||
style_computer: &'a StyleComputer<'a>,
|
||||
index_row: usize,
|
||||
index_column: usize,
|
||||
style: TableStyle,
|
||||
config: TableConfig,
|
||||
header_position: Orientation,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
@ -61,7 +53,7 @@ impl<'a> TableWidget<'a> {
|
|||
style_computer,
|
||||
index_row,
|
||||
index_column,
|
||||
style,
|
||||
config,
|
||||
header_position,
|
||||
}
|
||||
}
|
||||
|
@ -100,13 +92,13 @@ impl StatefulWidget for TableWidget<'_> {
|
|||
// todo: refactoring these to methods as they have quite a bit in common.
|
||||
impl<'a> TableWidget<'a> {
|
||||
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_r = self.style.column_padding_right as u16;
|
||||
let padding_l = self.config.column_padding_left as u16;
|
||||
let padding_r = self.config.column_padding_right as u16;
|
||||
|
||||
let show_index = self.style.show_index;
|
||||
let show_head = self.style.show_header;
|
||||
let show_index = self.config.show_index;
|
||||
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_y = area.y;
|
||||
|
@ -137,7 +129,7 @@ impl<'a> TableWidget<'a> {
|
|||
}
|
||||
|
||||
if show_head {
|
||||
render_header_borders(buf, area, 1, splitline_s);
|
||||
render_header_borders(buf, area, 1, separator_s);
|
||||
}
|
||||
|
||||
if show_index {
|
||||
|
@ -158,7 +150,7 @@ impl<'a> TableWidget<'a> {
|
|||
data_height,
|
||||
show_head,
|
||||
false,
|
||||
splitline_s,
|
||||
separator_s,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -250,7 +242,7 @@ impl<'a> TableWidget<'a> {
|
|||
data_height,
|
||||
show_head,
|
||||
false,
|
||||
splitline_s,
|
||||
separator_s,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -268,12 +260,12 @@ impl<'a> TableWidget<'a> {
|
|||
return;
|
||||
}
|
||||
|
||||
let padding_l = self.style.column_padding_left as u16;
|
||||
let padding_r = self.style.column_padding_right as u16;
|
||||
let padding_l = self.config.column_padding_left as u16;
|
||||
let padding_r = self.config.column_padding_right as u16;
|
||||
|
||||
let show_index = self.style.show_index;
|
||||
let show_head = self.style.show_header;
|
||||
let splitline_s = self.style.splitline_style;
|
||||
let show_index = self.config.show_index;
|
||||
let show_head = self.config.show_header;
|
||||
let separator_s = self.config.separator_style;
|
||||
|
||||
let mut left_w = 0;
|
||||
|
||||
|
@ -295,7 +287,7 @@ impl<'a> TableWidget<'a> {
|
|||
area.height,
|
||||
false,
|
||||
false,
|
||||
splitline_s,
|
||||
separator_s,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -327,7 +319,7 @@ impl<'a> TableWidget<'a> {
|
|||
area.height,
|
||||
false,
|
||||
false,
|
||||
splitline_s,
|
||||
separator_s,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -352,7 +344,7 @@ impl<'a> TableWidget<'a> {
|
|||
area.height,
|
||||
false,
|
||||
false,
|
||||
splitline_s,
|
||||
separator_s,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
use super::{
|
||||
record::{RecordView, TableTheme},
|
||||
util::{lookup_tui_color, nu_style_to_tui},
|
||||
Layout, Orientation, View, ViewConfig,
|
||||
};
|
||||
use super::{record::RecordView, util::nu_style_to_tui, Layout, Orientation, View, ViewConfig};
|
||||
use crate::{
|
||||
nu_common::{collect_pipeline, run_command_with_value},
|
||||
pager::{report::Report, Frame, Transition, ViewInfo},
|
||||
util::create_map,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyCode, KeyEvent};
|
||||
use nu_color_config::get_color_map;
|
||||
use nu_protocol::{
|
||||
engine::{EngineState, Stack},
|
||||
PipelineData, Value,
|
||||
|
@ -22,26 +16,22 @@ use ratatui::{
|
|||
};
|
||||
use std::cmp::min;
|
||||
|
||||
pub struct InteractiveView<'a> {
|
||||
pub struct TryView<'a> {
|
||||
input: Value,
|
||||
command: String,
|
||||
immediate: bool,
|
||||
reactive: bool,
|
||||
table: Option<RecordView<'a>>,
|
||||
table_theme: TableTheme,
|
||||
view_mode: bool,
|
||||
border_color: Style,
|
||||
highlighted_color: Style,
|
||||
}
|
||||
|
||||
impl<'a> InteractiveView<'a> {
|
||||
impl<'a> TryView<'a> {
|
||||
pub fn new(input: Value) -> Self {
|
||||
Self {
|
||||
input,
|
||||
table: None,
|
||||
immediate: false,
|
||||
table_theme: TableTheme::default(),
|
||||
reactive: false,
|
||||
border_color: Style::default(),
|
||||
highlighted_color: Style::default(),
|
||||
view_mode: false,
|
||||
command: String::new(),
|
||||
}
|
||||
|
@ -52,18 +42,15 @@ impl<'a> InteractiveView<'a> {
|
|||
}
|
||||
|
||||
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)?;
|
||||
view.set_theme(self.table_theme.clone());
|
||||
|
||||
let view = run_command(&self.command, &self.input, engine_state, stack)?;
|
||||
self.table = Some(view);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl View for InteractiveView<'_> {
|
||||
impl View for TryView<'_> {
|
||||
fn draw(&mut self, f: &mut Frame, area: Rect, cfg: ViewConfig<'_>, layout: &mut Layout) {
|
||||
let border_color = self.border_color;
|
||||
let highlighted_color = self.highlighted_color;
|
||||
|
||||
let cmd_block = ratatui::widgets::Block::default()
|
||||
.borders(Borders::ALL)
|
||||
|
@ -77,7 +64,7 @@ impl View for InteractiveView<'_> {
|
|||
cmd_block
|
||||
.border_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.border_type(BorderType::Double)
|
||||
.border_style(highlighted_color)
|
||||
.border_style(border_color)
|
||||
};
|
||||
|
||||
f.render_widget(cmd_block, cmd_area);
|
||||
|
@ -127,7 +114,7 @@ impl View for InteractiveView<'_> {
|
|||
table_block
|
||||
.border_style(Style::default().add_modifier(Modifier::BOLD))
|
||||
.border_type(BorderType::Double)
|
||||
.border_style(highlighted_color)
|
||||
.border_style(border_color)
|
||||
} else {
|
||||
table_block
|
||||
};
|
||||
|
@ -135,6 +122,7 @@ impl View for InteractiveView<'_> {
|
|||
f.render_widget(table_block, table_area);
|
||||
|
||||
if let Some(table) = &mut self.table {
|
||||
table.setup(cfg);
|
||||
let area = Rect::new(
|
||||
area.x + 2,
|
||||
area.y + 4,
|
||||
|
@ -190,7 +178,7 @@ impl View for InteractiveView<'_> {
|
|||
if !self.command.is_empty() {
|
||||
self.command.pop();
|
||||
|
||||
if self.immediate {
|
||||
if self.reactive {
|
||||
match self.try_run(engine_state, stack) {
|
||||
Ok(_) => info.report = Some(Report::default()),
|
||||
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
||||
|
@ -203,7 +191,7 @@ impl View for InteractiveView<'_> {
|
|||
KeyCode::Char(c) => {
|
||||
self.command.push(*c);
|
||||
|
||||
if self.immediate {
|
||||
if self.reactive {
|
||||
match self.try_run(engine_state, stack) {
|
||||
Ok(_) => info.report = Some(Report::default()),
|
||||
Err(err) => info.report = Some(Report::error(format!("Error: {err}"))),
|
||||
|
@ -246,31 +234,14 @@ impl View for InteractiveView<'_> {
|
|||
}
|
||||
|
||||
fn setup(&mut self, config: ViewConfig<'_>) {
|
||||
self.border_color = lookup_tui_color(config.style_computer, "separator");
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
self.border_color = nu_style_to_tui(config.explore_config.table.separator_style);
|
||||
self.reactive = config.explore_config.try_reactive;
|
||||
|
||||
let mut r = RecordView::new(vec![], vec![]);
|
||||
r.setup(config);
|
||||
|
||||
self.table_theme = r.get_theme().clone();
|
||||
|
||||
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_current(r.get_orientation_current());
|
||||
}
|
|
@ -31,11 +31,6 @@ pub fn set_span(
|
|||
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 {
|
||||
let mut out = ratatui::style::Style::default();
|
||||
if let Some(clr) = style.background {
|
||||
|
|
|
@ -189,12 +189,7 @@ $env.config = {
|
|||
warn: {}
|
||||
info: {}
|
||||
},
|
||||
table: {
|
||||
split_line: { fg: "#404040" },
|
||||
selected_cell: { bg: light_blue },
|
||||
selected_row: {},
|
||||
selected_column: {},
|
||||
},
|
||||
selected_cell: { bg: light_blue },
|
||||
}
|
||||
|
||||
history: {
|
||||
|
|
|
@ -29,12 +29,12 @@ indexmap = { version = "2.2" }
|
|||
mimalloc = { version = "0.1.42" }
|
||||
num = {version = "0.4"}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sqlparser = { version = "0.45"}
|
||||
polars-io = { version = "0.39", features = ["avro"]}
|
||||
polars-arrow = { version = "0.39"}
|
||||
polars-ops = { version = "0.39"}
|
||||
polars-plan = { version = "0.39", features = ["regex"]}
|
||||
polars-utils = { version = "0.39"}
|
||||
sqlparser = { version = "0.47"}
|
||||
polars-io = { version = "0.40", features = ["avro"]}
|
||||
polars-arrow = { version = "0.40"}
|
||||
polars-ops = { version = "0.40"}
|
||||
polars-plan = { version = "0.40", features = ["regex"]}
|
||||
polars-utils = { version = "0.40"}
|
||||
typetag = "0.2"
|
||||
uuid = { version = "1.7", features = ["v4", "serde"] }
|
||||
|
||||
|
@ -70,7 +70,7 @@ features = [
|
|||
"to_dummies",
|
||||
]
|
||||
optional = false
|
||||
version = "0.39"
|
||||
version = "0.40"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.94.3" }
|
||||
|
|
|
@ -16,14 +16,17 @@ use std::{
|
|||
fs::File,
|
||||
io::BufReader,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use polars::prelude::{
|
||||
CsvEncoding, CsvReader, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader,
|
||||
LazyFrame, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||
CsvEncoding, IpcReader, JsonFormat, JsonReader, LazyCsvReader, LazyFileListReader, LazyFrame,
|
||||
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)]
|
||||
pub struct OpenDataFrame;
|
||||
|
@ -175,6 +178,7 @@ fn from_parquet(
|
|||
cloud_options: None,
|
||||
use_statistics: false,
|
||||
hive_options: HiveOptions::default(),
|
||||
glob: true,
|
||||
};
|
||||
|
||||
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 {
|
||||
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)
|
||||
} 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 {
|
||||
error: "Error creating CSV reader".into(),
|
||||
msg: e.to_string(),
|
||||
|
@ -483,52 +503,6 @@ fn from_csv(
|
|||
help: None,
|
||||
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()
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "CSV reader error".into(),
|
||||
|
@ -536,9 +510,8 @@ fn from_csv(
|
|||
span: Some(call.head),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
})?
|
||||
.into();
|
||||
|
||||
})?;
|
||||
let df = NuDataFrame::new(false, df);
|
||||
df.cache_and_to_value(plugin, engine, call.head)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,8 @@ use polars::prelude::{col, lit, DataType, Expr, LiteralValue, PolarsResult as Re
|
|||
|
||||
use sqlparser::ast::{
|
||||
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> {
|
||||
|
@ -33,7 +34,7 @@ fn map_sql_polars_datatype(data_type: &SQLDataType) -> Result<DataType> {
|
|||
SQLDataType::Interval => DataType::Duration(TimeUnit::Microseconds),
|
||||
SQLDataType::Array(array_type_def) => match array_type_def {
|
||||
ArrayElemTypeDef::AngleBracket(inner_type)
|
||||
| ArrayElemTypeDef::SquareBracket(inner_type) => {
|
||||
| ArrayElemTypeDef::SquareBracket(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::Cast {
|
||||
expr,
|
||||
data_type,
|
||||
format: _,
|
||||
expr, data_type, ..
|
||||
} => cast_(parse_sql_expr(expr)?, data_type)?,
|
||||
SqlExpr::Nested(expr) => parse_sql_expr(expr)?,
|
||||
SqlExpr::Value(value) => literal_expr(value)?,
|
||||
|
@ -162,8 +161,17 @@ fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
|||
use sqlparser::ast::{FunctionArg, FunctionArgExpr};
|
||||
// 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 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()
|
||||
.map(|arg| match arg {
|
||||
FunctionArg::Named { arg, .. } => arg,
|
||||
|
@ -174,15 +182,15 @@ fn parse_sql_function(sql_function: &SQLFunction) -> Result<Expr> {
|
|||
match (
|
||||
function_name.as_str(),
|
||||
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()
|
||||
}
|
||||
("count", [FunctionArgExpr::Expr(expr)], false) => {
|
||||
("count", [FunctionArgExpr::Expr(ref expr)], false) => {
|
||||
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()
|
||||
}
|
||||
// Special case for wildcard args to count function.
|
||||
|
|
|
@ -189,53 +189,19 @@ fn command(
|
|||
.map(|col| {
|
||||
let count = col.len() as f64;
|
||||
|
||||
let sum = col.sum_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 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 sum = col.sum::<f64>().ok();
|
||||
let mean = col.mean();
|
||||
let median = col.median();
|
||||
let std = col.std(0);
|
||||
let min = col.min::<f64>().ok().flatten();
|
||||
|
||||
let mut quantiles = quantiles
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|q| {
|
||||
col.quantile_as_series(q, QuantileInterpolOptions::default())
|
||||
col.quantile_reduce(q, QuantileInterpolOptions::default())
|
||||
.ok()
|
||||
.map(|s| s.into_series("quantile"))
|
||||
.and_then(|ca| ca.cast(&DataType::Float64).ok())
|
||||
.and_then(|ca| match ca.get(0) {
|
||||
Ok(AnyValue::Float64(v)) => Some(v),
|
||||
|
@ -244,15 +210,7 @@ fn command(
|
|||
})
|
||||
.collect::<Vec<Option<f64>>>();
|
||||
|
||||
let max = col.max_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 max = col.max::<f64>().ok().flatten();
|
||||
|
||||
let mut descriptors = vec![Some(count), sum, mean, median, std, min];
|
||||
descriptors.append(&mut quantiles);
|
||||
|
|
|
@ -5,9 +5,7 @@ use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
|
|||
use crate::values::CustomValueSupport;
|
||||
use crate::PolarsPlugin;
|
||||
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
|
||||
use nu_protocol::{
|
||||
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Type, Value,
|
||||
};
|
||||
use nu_protocol::{Category, Example, LabeledError, PipelineData, Signature, Span, Type, Value};
|
||||
|
||||
// The structs defined in this file are structs that form part of other commands
|
||||
// since they share a similar name
|
||||
|
@ -60,6 +58,7 @@ macro_rules! expr_command {
|
|||
mod $test {
|
||||
use super::*;
|
||||
use crate::test::test_polars_plugin_command;
|
||||
use nu_protocol::ShellError;
|
||||
|
||||
#[test]
|
||||
fn test_examples() -> Result<(), ShellError> {
|
||||
|
@ -163,19 +162,7 @@ macro_rules! lazy_expr_command {
|
|||
if NuDataFrame::can_downcast(&value) || NuLazyFrame::can_downcast(&value) {
|
||||
let lazy = NuLazyFrame::try_from_value_coerce(plugin, &value)
|
||||
.map_err(LabeledError::from)?;
|
||||
let lazy = NuLazyFrame::new(
|
||||
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)?,
|
||||
);
|
||||
let lazy = NuLazyFrame::new(lazy.from_eager, lazy.to_polars().$func());
|
||||
lazy.to_pipeline_data(plugin, engine, call.head)
|
||||
.map_err(LabeledError::from)
|
||||
} else {
|
||||
|
@ -192,6 +179,7 @@ macro_rules! lazy_expr_command {
|
|||
mod $test {
|
||||
use super::*;
|
||||
use crate::test::test_polars_plugin_command;
|
||||
use nu_protocol::ShellError;
|
||||
|
||||
#[test]
|
||||
fn test_examples() -> Result<(), ShellError> {
|
||||
|
@ -244,19 +232,7 @@ macro_rules! lazy_expr_command {
|
|||
if NuDataFrame::can_downcast(&value) || NuLazyFrame::can_downcast(&value) {
|
||||
let lazy = NuLazyFrame::try_from_value_coerce(plugin, &value)
|
||||
.map_err(LabeledError::from)?;
|
||||
let lazy = NuLazyFrame::new(
|
||||
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)?,
|
||||
);
|
||||
let lazy = NuLazyFrame::new(lazy.from_eager, lazy.to_polars().$func($ddof));
|
||||
lazy.to_pipeline_data(plugin, engine, call.head)
|
||||
.map_err(LabeledError::from)
|
||||
} else {
|
||||
|
@ -272,6 +248,7 @@ macro_rules! lazy_expr_command {
|
|||
mod $test {
|
||||
use super::*;
|
||||
use crate::test::test_polars_plugin_command;
|
||||
use nu_protocol::ShellError;
|
||||
|
||||
#[test]
|
||||
fn test_examples() -> Result<(), ShellError> {
|
||||
|
|
|
@ -35,7 +35,7 @@ impl PluginCommand for ExprLit {
|
|||
example: "polars lit 2 | polars into-nu",
|
||||
result: Some(Value::test_record(record! {
|
||||
"expr" => Value::test_string("literal"),
|
||||
"value" => Value::test_string("2"),
|
||||
"value" => Value::test_string("dyn int: 2"),
|
||||
})),
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -195,6 +195,7 @@ fn get_col_name(expr: &Expr) -> Option<String> {
|
|||
| Expr::Len
|
||||
| Expr::Nth(_)
|
||||
| Expr::SubPlan(_, _)
|
||||
| Expr::IndexColumn(_)
|
||||
| Expr::Selector(_) => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -189,7 +189,7 @@ impl PluginCommand for LazyJoin {
|
|||
let how = if left {
|
||||
JoinType::Left
|
||||
} else if outer {
|
||||
JoinType::Outer { coalesce: true }
|
||||
JoinType::Outer
|
||||
} else if cross {
|
||||
JoinType::Cross
|
||||
} else {
|
||||
|
|
|
@ -116,16 +116,7 @@ fn command(
|
|||
call: &EvaluatedCall,
|
||||
lazy: NuLazyFrame,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let polars_lazy = lazy
|
||||
.to_polars()
|
||||
.median()
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: format!("Error in median operation: {e}"),
|
||||
msg: "".into(),
|
||||
help: None,
|
||||
span: None,
|
||||
inner: vec![],
|
||||
})?;
|
||||
let polars_lazy = lazy.to_polars().median();
|
||||
let lazy = NuLazyFrame::new(lazy.from_eager, polars_lazy);
|
||||
lazy.to_pipeline_data(plugin, engine, call.head)
|
||||
}
|
||||
|
|
|
@ -134,14 +134,7 @@ fn command(
|
|||
let lazy = NuLazyFrame::new(
|
||||
lazy.from_eager,
|
||||
lazy.to_polars()
|
||||
.quantile(lit(quantile), QuantileInterpolOptions::default())
|
||||
.map_err(|e| ShellError::GenericError {
|
||||
error: "Dataframe Error".into(),
|
||||
msg: e.to_string(),
|
||||
help: None,
|
||||
span: None,
|
||||
inner: vec![],
|
||||
})?,
|
||||
.quantile(lit(quantile), QuantileInterpolOptions::default()),
|
||||
);
|
||||
|
||||
lazy.to_pipeline_data(plugin, engine, call.head)
|
||||
|
|
|
@ -7,7 +7,7 @@ use nu_protocol::{
|
|||
Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
use polars::prelude::{DataType, Duration, IntoSeries, RollingOptionsImpl, SeriesOpsTime};
|
||||
use polars::prelude::{DataType, IntoSeries, RollingOptionsFixedWindow, SeriesOpsTime};
|
||||
|
||||
enum RollType {
|
||||
Min,
|
||||
|
@ -131,7 +131,7 @@ fn command(
|
|||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
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 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 rolling_opts = RollingOptionsImpl {
|
||||
window_size: Duration::new(window_size),
|
||||
min_periods: window_size as usize,
|
||||
weights: None,
|
||||
center: false,
|
||||
by: None,
|
||||
closed_window: None,
|
||||
tu: None,
|
||||
tz: None,
|
||||
fn_params: None,
|
||||
let rolling_opts = RollingOptionsFixedWindow {
|
||||
window_size,
|
||||
min_periods: window_size,
|
||||
..RollingOptionsFixedWindow::default()
|
||||
};
|
||||
|
||||
let res = match roll_type {
|
||||
RollType::Max => series.rolling_max(rolling_opts),
|
||||
RollType::Min => series.rolling_min(rolling_opts),
|
||||
|
|
|
@ -155,7 +155,10 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Result<Value, ShellError> {
|
|||
span,
|
||||
)),
|
||||
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(
|
||||
record! {
|
||||
"expr" => Value::string("columns", span),
|
||||
|
@ -415,6 +418,12 @@ pub fn expr_to_value(expr: &Expr, span: Span) -> Result<Value, ShellError> {
|
|||
msg_span: span,
|
||||
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(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -160,7 +160,15 @@ impl CustomValueSupport for NuLazyFrame {
|
|||
.unwrap_or_else(|_| "<NOT AVAILABLE>".to_string());
|
||||
Ok(Value::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),
|
||||
},
|
||||
span,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
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)]
|
||||
pub struct NuSchema {
|
||||
|
@ -104,7 +107,7 @@ pub fn str_to_dtype(dtype: &str, span: Span) -> Result<DataType, ShellError> {
|
|||
"date" => Ok(DataType::Date),
|
||||
"time" => Ok(DataType::Time),
|
||||
"null" => Ok(DataType::Null),
|
||||
"unknown" => Ok(DataType::Unknown),
|
||||
"unknown" => Ok(DataType::Unknown(UnknownKind::Any)),
|
||||
"object" => Ok(DataType::Object("unknown", None)),
|
||||
_ if dtype.starts_with("list") => {
|
||||
let dtype = dtype
|
||||
|
@ -299,7 +302,7 @@ mod test {
|
|||
|
||||
let dtype = "unknown";
|
||||
let schema = str_to_dtype(dtype, Span::unknown()).unwrap();
|
||||
let expected = DataType::Unknown;
|
||||
let expected = DataType::Unknown(UnknownKind::Any);
|
||||
assert_eq!(schema, expected);
|
||||
|
||||
let dtype = "object";
|
||||
|
|
|
@ -590,4 +590,18 @@ mod external_command_arguments {
|
|||
|
||||
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"#);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user