mirror of
https://github.com/nushell/nushell.git
synced 2025-07-01 07:00:37 +02:00
Compare commits
111 Commits
Author | SHA1 | Date | |
---|---|---|---|
d2abb8603a | |||
894e0f7658 | |||
a5087c4966 | |||
ac4ab452d4 | |||
5378727049 | |||
0786ddddbd | |||
7617084ca3 | |||
28941f1a06 | |||
43ceb3edec | |||
bffd8e4dd2 | |||
66023f6243 | |||
10fc32e3ef | |||
3148acd3a4 | |||
e6ce8a89be | |||
74f8081290 | |||
2ae1de2470 | |||
028a327ce8 | |||
318862aad6 | |||
9f4510f2e1 | |||
98c7ab96b6 | |||
13114e972b | |||
4a1b3e26ef | |||
1e3248dfe8 | |||
2aa4cd5cc5 | |||
fb908df17d | |||
cdf09abcc0 | |||
fe2c498a81 | |||
fe7122280d | |||
2e0fb7c1a6 | |||
f33b60c001 | |||
7e48607820 | |||
68a821c84a | |||
c5e59efa4d | |||
ec5b9b9f37 | |||
e88a51e930 | |||
35f8d8548a | |||
7a123d3eb1 | |||
3ed45c7ba8 | |||
8b160f9850 | |||
696b2cda4a | |||
435348aa61 | |||
0a5f41abc2 | |||
2b97bc54c5 | |||
ad49c17eba | |||
f1e88d95c1 | |||
6eac9bfd0f | |||
5d94b16d71 | |||
3bd46fe27a | |||
7b1c7debcb | |||
e77a0a48aa | |||
aa37572ddc | |||
a0cecf7658 | |||
23170ff368 | |||
f9ffd9ae29 | |||
d5fa7b8a55 | |||
f94df58486 | |||
6f5bd62a97 | |||
bb3cc9e153 | |||
202dfdaee2 | |||
0674d4960b | |||
85c2035016 | |||
02be83efbf | |||
b964347895 | |||
56ed1eb807 | |||
570175f95d | |||
839010b89d | |||
7694d09d14 | |||
4accc67843 | |||
c070e2d6f7 | |||
d302d63030 | |||
a2e117f8b0 | |||
b1974fae39 | |||
7248de1167 | |||
a9582e1c62 | |||
bb6335830a | |||
c8f3799c20 | |||
bd3a61a2f7 | |||
077643cadf | |||
fa2d9a8a58 | |||
58f98a4260 | |||
066790552a | |||
dcb1a1996c | |||
6b4d06d8a7 | |||
f615038938 | |||
71b74a284a | |||
2b431f994f | |||
7e096e61d7 | |||
9d7a1097f2 | |||
a98b3124c5 | |||
572698bf3e | |||
7162289d77 | |||
14bf25da14 | |||
a455e2e5eb | |||
840b4b854b | |||
ec4941c8ac | |||
dd86f14a5a | |||
63103580d2 | |||
d25df9c00b | |||
778a00efa1 | |||
fea822792f | |||
f6033ac5af | |||
583ef8674e | |||
94bec72079 | |||
28ed21864d | |||
e16ce1df36 | |||
87abfee268 | |||
ba0f069c31 | |||
154856066f | |||
f91713b714 | |||
f1bf485b2a | |||
955de76116 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -24,7 +24,7 @@ Don't forget to add tests that cover your changes.
|
||||
Make sure you've run and fixed any issues with these commands:
|
||||
|
||||
- `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err` to check that you're using the standard code style
|
||||
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style
|
||||
- `cargo test --workspace` to check that all tests pass
|
||||
- `cargo run -- -c "use std testing; testing run-tests --path crates/nu-std"` to run the tests for the standard library
|
||||
|
||||
|
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@ -9,7 +9,7 @@ name: continuous-integration
|
||||
env:
|
||||
NUSHELL_CARGO_TARGET: ci
|
||||
NU_LOG_LEVEL: DEBUG
|
||||
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err"
|
||||
CLIPPY_OPTIONS: "-D warnings -D clippy::unwrap_used"
|
||||
|
||||
jobs:
|
||||
fmt-clippy:
|
||||
@ -115,21 +115,12 @@ jobs:
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- run: python -m pip install tox
|
||||
|
||||
- name: Install virtualenv
|
||||
run: git clone https://github.com/pypa/virtualenv.git
|
||||
run: pip install virtualenv
|
||||
shell: bash
|
||||
|
||||
- name: Test Nushell in virtualenv
|
||||
run: |
|
||||
cd virtualenv
|
||||
# if we encounter problems with bleeding edge tests pin to the latest tag
|
||||
# git checkout $(git describe --tags | cut -d - -f 1)
|
||||
# We need to disable failing on coverage levels.
|
||||
nu -c "open pyproject.toml | upsert tool.coverage.report.fail_under 1 | save patchproject.toml"
|
||||
mv patchproject.toml pyproject.toml
|
||||
tox -e ${{ matrix.py }} -- -k nushell
|
||||
run: nu scripts/test_virtualenv.nu
|
||||
shell: bash
|
||||
|
||||
plugins:
|
||||
|
2
.github/workflows/winget-submission.yml
vendored
2
.github/workflows/winget-submission.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
winget:
|
||||
name: Publish winget package
|
||||
runs-on: windows-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Submit package to Windows Package Manager Community Repository
|
||||
uses: vedantmgoyal2009/winget-releaser@v2
|
||||
|
@ -2,7 +2,20 @@
|
||||
|
||||
Welcome to Nushell and thank you for considering contributing!
|
||||
|
||||
## Review Process
|
||||
## Table of contents
|
||||
- [Proposing design changes](#proposing-design-changes)
|
||||
- [Developing](#developing)
|
||||
- [Setup](#setup)
|
||||
- [Tests](#tests)
|
||||
- [Useful commands](#useful-commands)
|
||||
- [Debugging tips](#debugging-tips)
|
||||
- [Git etiquette](#git-etiquette)
|
||||
- [Our Rust style](#our-rust-style)
|
||||
- [Generally discouraged](#generally-discouraged)
|
||||
- [Things we want to get better at](#things-we-want-to-get-better-at)
|
||||
- [License](#license)
|
||||
|
||||
## Proposing design changes
|
||||
|
||||
First of all, before diving into the code, if you want to create a new feature, change something significantly, and especially if the change is user-facing, it is a good practice to first get an approval from the core team before starting to work on it.
|
||||
This saves both your and our time if we realize the change needs to go another direction before spending time on it.
|
||||
@ -41,10 +54,13 @@ Tests can be found in different places:
|
||||
* command examples
|
||||
* crate-specific tests
|
||||
|
||||
The most comprehensive test suite we have is the `nu-test-support` crate. For testing specific features, such as running Nushell in a REPL mode, we have so called "testbins". For simple tests, you can find `run_test()` and `fail_test()` functions.
|
||||
Most of the tests are built upon the `nu-test-support` crate. For testing specific features, such as running Nushell in a REPL mode, we have so called "testbins". For simple tests, you can find `run_test()` and `fail_test()` functions.
|
||||
|
||||
### Useful Commands
|
||||
|
||||
As Nushell is build using a cargo workspace consisting of multiple crates keep in mind that you may need to pass additional flags compared to how you may be used to it from a single crate project.
|
||||
Read cargo's documentation for more details: https://doc.rust-lang.org/cargo/reference/workspaces.html
|
||||
|
||||
- Build and run Nushell:
|
||||
|
||||
```shell
|
||||
@ -59,7 +75,7 @@ The most comprehensive test suite we have is the `nu-test-support` crate. For te
|
||||
- Run Clippy on Nushell:
|
||||
|
||||
```shell
|
||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect -A clippy::result_large_err
|
||||
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used
|
||||
```
|
||||
or via the `toolkit.nu` command:
|
||||
```shell
|
||||
@ -220,7 +236,52 @@ You can help us to make the review process a smooth experience:
|
||||
- Choose what simplifies having confidence in the conflict resolution and the review. **Merge commits in your branch are OK** in the squash model.
|
||||
- Feel free to notify your reviewers or affected PR authors if your change might cause larger conflicts with another change.
|
||||
- During the rollup of multiple PRs, we may choose to resolve merge conflicts and CI failures ourselves. (Allow maintainers to push to your branch to enable us to do this quickly.)
|
||||
|
||||
## Our Rust style
|
||||
To make the collaboration on a project the scale of Nushell easy, we want to work towards a style of Rust code that can easily be understood by all of our contributors. We conservatively rely on most of [`clippy`s suggestions](https://github.com/rust-lang/rust-clippy) to get to the holy grail of "idiomatic" code. Good code in our eyes is not the most clever use of all available language features or with the most unique personal touch but readable and strikes a balance between being concise, and also unsurprising and explicit in the places where it matters.
|
||||
One example of this philosophy is that we generally avoid to fight the borrow-checker in our data model but rather try to get to a correct and simple solution first and then figure out where we should reuse data to achieve the necessary performance. As we are still pre-1.0 this served us well to be able to quickly refactor or change larger parts of the code base.
|
||||
|
||||
### License
|
||||
### Generally discouraged
|
||||
#### `+nightly` language features or things only available in the most recent `+stable`
|
||||
To make life for the people easier that maintain the Nushell packages in various distributions with their own release cycle of `rustc` we typically rely on slightly older Rust versions. We do not make explicit guarantees how far back in the past we live but you can find out in our [`rust-toolchain.toml`](https://github.com/nushell/nushell/blob/main/rust-toolchain.toml)
|
||||
(As a rule of thumb this has been typically been approximately 2 releases behind the newest stable compiler.)
|
||||
The use of nightly features is prohibited.
|
||||
|
||||
#### Panicking
|
||||
As Nushell aims to provide a reliable foundational way for folks to interact with their computer, we cannot carelessly crash the execution of their work by panicking Nushell.
|
||||
Thus panicking is not an allowed error handling strategy for anything that could be triggered by user input OR behavior of the outside system. If Nushell panics this is a bug or we are against all odds already in an unrecoverable state (The system stopped cooperating, we went out of memory). The use of `.unwrap()` is thus outright banned and any uses of `.expect()` or related panicking macros like `unreachable!` should include a helpful description which assumptions have been violated.
|
||||
|
||||
#### `unsafe` code
|
||||
For any use of `unsafe` code we need to require even higher standards and additional review. If you add or alter `unsafe` blocks you have to be familiar with the promises you need to uphold as found in the [Rustonomicon](https://doc.rust-lang.org/nomicon/intro.html). All `unsafe` uses should include `// SAFETY:` comments explaining how the invariants are upheld and thus alerting you what to watch out for when making a change.
|
||||
##### FFI with system calls and the outside world
|
||||
As a shell Nushell needs to interact with system APIs in several places, for which FFI code with unsafe blocks may be necessary. In some cases this can be handled by safe API wrapper crates but in some cases we may choose to directly do those calls.
|
||||
If you do so you need to document the system behavior on top of the Rust memory model guarantees that you uphold. This means documenting whether using a particular system call is safe to use in a particular context and all failure cases are properly recovered.
|
||||
##### Implementing self-contained data structures
|
||||
Another motivation for reaching to `unsafe` code might be to try to implement a particular data structure that is not expressible on safe `std` library APIs. Doing so in the Nushell code base would have to clear a high bar for need based on profiling results. Also you should first do a survey of the [crate ecosystem](https://crates.io) that there doesn't exist a usable well vetted crate that already provides safe APIs to the desired datastructure.
|
||||
##### Make things go faster by removing checks
|
||||
This is probably a bad idea if you feel tempted to do so. Don't
|
||||
#### Macros
|
||||
Another advanced feature people feel tempted to use to work around perceived limitations of Rusts syntax and we are not particularly fans of are custom macros.
|
||||
They have clear downsides not only in terms of readability if they locally introduce a different syntax. Most tooling apart from the compiler will struggle more with them. This limits for example consistent automatic formatting or automated refactors with `rust-analyzer`.
|
||||
That you can fluently read `macro_rules!` is less likely than regular code. This can lead people to introduce funky behavior when using a macro. Be it because a macro is not following proper hygiene rules or because it introduces excessive work at compile time.
|
||||
|
||||
So we generally discourage the addition of macros. In a lot of cases your macro may start do something that can be expressed with functions or generics in a much more reusable fashion.
|
||||
The only exceptions we may allow need to demonstrate that the macro can fix something that is otherwise extremely unreadable, error-prone, or consistently worse at compile time.
|
||||
### Things we want to get better at
|
||||
These are things we did pretty liberally to get Nushell off the ground, that make things harder for a high quality stable product. You may run across them but shouldn't take them as an endorsed example.
|
||||
#### Liberal use of third-party dependencies
|
||||
The amazing variety of crates on [crates.io](https://crates.io) allowed us to quickly get Nushell into a feature rich state but it left us with a bunch of baggage to clean up.
|
||||
Each dependency introduces a compile time cost and duplicated code can add to the overall binary size. Also vetting more for correct and secure implementations takes unreasonably more time as this is also a continuous process of reacting to updates or potential vulnerabilities.
|
||||
|
||||
Thus we only want to accept dependencies that are essential and well tested implementations of a particular requirement of Nushells codebase.
|
||||
Also as a project for the move to 1.0 we will try to unify among a set of dependencies if they possibly implement similar things in an area. We don't need three different crates with potentially perfect fit for three problems but rather one reliable crate with a maximized overlap between what it provides and what we need.
|
||||
We will favor crates that are well tested and used and promise to be more stable and still frequently maintained.
|
||||
#### Deeply nested code
|
||||
As Nushell uses a lot of enums in its internal data representation there are a lot of `match` expressions. Combined with the need to handle a lot of edge cases and be defensive about any errors this has led to some absolutely hard to read deeply nested code (e.g. in the parser but also in the implementation of several commands).
|
||||
This can be observed both as a "rightward drift" where the main part of the code is found after many levels of indentations or by long function bodies with several layers of branching with seemingly repeated branching inside the higher branch level.
|
||||
This can also be exacerbated by "quick" bugfixes/enhancements that may just try to add a special case to catch a previously unexpected condition. The likelihood of introducing a bug in a sea of code duplication is high.
|
||||
To combat this, consider using the early-`return` pattern to reject invalid data early in one place instead of building a tree through Rust's expression constructs with a lot of duplicated paths. Unpacking data into a type that expresses that the necessary things already have been checked and using functions to properly deal with separate and common behavior can also help.
|
||||
|
||||
## License
|
||||
|
||||
We use the [MIT License](https://github.com/nushell/nushell/blob/main/LICENSE) in all of our Nushell projects. If you are including or referencing a crate that uses the [GPL License](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text) unfortunately we will not be able to accept your PR.
|
||||
|
229
Cargo.lock
generated
229
Cargo.lock
generated
@ -28,15 +28,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
name = "adler32"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
@ -123,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "220044e6a1bb31ddee4e3db724d29767f352de47445a6cd75e1a173142136c83"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"vte",
|
||||
"vte 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -175,8 +170,9 @@ version = "0.17.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15ae0428d69ab31d7b2adad22a752d6f11fef2e901d2262d0cad4f5cb08b7093"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"arrow-format",
|
||||
"avro-schema",
|
||||
"base64",
|
||||
"bytemuck",
|
||||
"chrono",
|
||||
@ -284,6 +280,20 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "avro-schema"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5281855b39aba9684d2f47bf96983fbfd8f1725f12fabb0513a8ab879647bbd"
|
||||
dependencies = [
|
||||
"crc",
|
||||
"fallible-streaming-iterator",
|
||||
"libflate",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"snap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.68"
|
||||
@ -372,9 +382,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bracoxide"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae76c117551a0459f61c57b573271778214287482c346d14591d2ea250ecbea5"
|
||||
checksum = "d8654ced31d3071e01cee9112fb25f241798b17b08d083512f8da2afa3da076b"
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
@ -751,6 +761,21 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
@ -1393,6 +1418,12 @@ version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.28"
|
||||
@ -1550,9 +1581,6 @@ name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
@ -1560,7 +1588,7 @@ version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
@ -1570,7 +1598,7 @@ version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
@ -2030,6 +2058,26 @@ version = "0.2.147"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
|
||||
[[package]]
|
||||
name = "libflate"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ff4ae71b685bbad2f2f391fe74f6b7659a34871c08b210fdc039e43bee07d18"
|
||||
dependencies = [
|
||||
"adler32",
|
||||
"crc32fast",
|
||||
"libflate_lz77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libflate_lz77"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a52d3a8bfc85f250440e4424db7d857e241a3aebbbe301f3eb606ab15c39acbf"
|
||||
dependencies = [
|
||||
"rle-decode-fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.15.2+1.6.4"
|
||||
@ -2502,7 +2550,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"criterion",
|
||||
@ -2559,7 +2607,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cli"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
@ -2590,7 +2638,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-base"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"nu-engine",
|
||||
@ -2600,7 +2648,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-dataframe"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fancy-regex",
|
||||
@ -2612,15 +2660,17 @@ dependencies = [
|
||||
"nu-test-support",
|
||||
"num 0.4.0",
|
||||
"polars",
|
||||
"polars-io",
|
||||
"serde",
|
||||
"sqlparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-extra"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"Inflector",
|
||||
"ahash",
|
||||
"fancy-regex",
|
||||
"htmlescape",
|
||||
"nu-ansi-term",
|
||||
@ -2642,7 +2692,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"fancy-regex",
|
||||
"itertools",
|
||||
@ -2656,7 +2706,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-color-config"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-engine",
|
||||
@ -2669,9 +2719,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-command"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"alphanumeric-sort",
|
||||
"base64",
|
||||
"bracoxide",
|
||||
@ -2734,7 +2783,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"powierza-coefficient",
|
||||
"print-positions",
|
||||
"quick-xml 0.29.0",
|
||||
"quick-xml 0.30.0",
|
||||
"quickcheck",
|
||||
"quickcheck_macros",
|
||||
"rand",
|
||||
@ -2769,7 +2818,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-engine"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"nu-glob",
|
||||
"nu-path",
|
||||
@ -2780,7 +2829,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-explore"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"crossterm",
|
||||
@ -2800,14 +2849,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-glob"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-json"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
"num-traits",
|
||||
@ -2816,7 +2865,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-parser"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"bytesize",
|
||||
"chrono",
|
||||
@ -2832,7 +2881,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-path"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"omnipath",
|
||||
@ -2841,7 +2890,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-plugin"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"nu-engine",
|
||||
@ -2853,7 +2902,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-pretty-hex"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"heapless",
|
||||
"nu-ansi-term",
|
||||
@ -2862,7 +2911,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-protocol"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"byte-unit",
|
||||
"chrono",
|
||||
@ -2874,6 +2923,7 @@ dependencies = [
|
||||
"nu-test-support",
|
||||
"nu-utils",
|
||||
"num-format",
|
||||
"rstest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum 0.25.0",
|
||||
@ -2884,7 +2934,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-std"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"miette",
|
||||
"nu-engine",
|
||||
@ -2894,7 +2944,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-system"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"is-terminal",
|
||||
@ -2911,7 +2961,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-table"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"nu-color-config",
|
||||
@ -2923,7 +2973,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-term-grid"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"nu-utils",
|
||||
"unicode-width",
|
||||
@ -2931,7 +2981,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-test-support"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"hamcrest2",
|
||||
"nu-glob",
|
||||
@ -2944,7 +2994,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu-utils"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"crossterm_winapi",
|
||||
"log",
|
||||
@ -2966,7 +3016,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_example"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
@ -2974,7 +3024,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_formats"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"eml-parser",
|
||||
"ical",
|
||||
@ -2986,7 +3036,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_gstat"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"git2",
|
||||
"nu-plugin",
|
||||
@ -2995,7 +3045,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_inc"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"nu-plugin",
|
||||
"nu-protocol",
|
||||
@ -3004,7 +3054,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nu_plugin_query"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
dependencies = [
|
||||
"gjson",
|
||||
"nu-engine",
|
||||
@ -3303,9 +3353,9 @@ checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
|
||||
|
||||
[[package]]
|
||||
name = "papergrid"
|
||||
version = "0.9.1"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae7891b22598926e4398790c8fe6447930c72a67d36d983a49d6ce682ce83290"
|
||||
checksum = "a2ccbe15f2b6db62f9a9871642746427e297b0ceb85f9a7f1ee5ff47d184d0c8"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"ansitok",
|
||||
@ -3611,7 +3661,7 @@ version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b7216aa3336fd2a7b5ebfa66748f3770682b28cd682930d1abf59b53b670e0"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"arrow2",
|
||||
"bitflags 1.3.2",
|
||||
"chrono",
|
||||
@ -3654,7 +3704,7 @@ version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "270650704e08bef37d227a6904b36c4bdad2d013536ea3c57f40007c19ad2ebc"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"arrow2",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@ -3689,7 +3739,7 @@ version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7370203bca4ee29da6da4210200894eea6abf81d7c430e192159dabcba8441a4"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"arrow2",
|
||||
"fallible-streaming-iterator",
|
||||
"hashbrown 0.13.2",
|
||||
@ -3707,7 +3757,7 @@ version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2a8fe41263496b5212098f19d0cdddc11f75c71957a09d2ce300a8fdb4407e3"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"bitflags 1.3.2",
|
||||
"glob",
|
||||
"once_cell",
|
||||
@ -3767,7 +3817,7 @@ version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7d0f48bdd7fa9474f718ecb99fc12d97671933707091a249bb0da96b30f291"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"arrow2",
|
||||
"once_cell",
|
||||
"polars-arrow",
|
||||
@ -3834,7 +3884,7 @@ version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "145a59f928f8317fcf543407ef1b83e827448462e05e2fc6444162fcec84386a"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"hashbrown 0.13.2",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
@ -3986,9 +4036,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.29.0"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
|
||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -4130,9 +4180,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "reedline"
|
||||
version = "0.22.0"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2fde955d11817fdcb79d703932fb6b473192cb36b6a92ba21f7f4ac0513374e"
|
||||
checksum = "3defc4467a8909614bcb02cb304a3e8472c31f7a44e6c6c287eb9575b212bc4d"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossterm",
|
||||
@ -4191,6 +4241,18 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
|
||||
|
||||
[[package]]
|
||||
name = "relative-path"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4bf2521270932c3c7bed1a59151222bd7643c79310f2916f01925e1e16255698"
|
||||
|
||||
[[package]]
|
||||
name = "rle-decode-fast"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422"
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.11"
|
||||
@ -4224,25 +4286,30 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rstest"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962"
|
||||
checksum = "2b96577ca10cb3eade7b337eb46520108a67ca2818a24d0b63f41fd62bc9651c"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"futures-timer",
|
||||
"rstest_macros",
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rstest_macros"
|
||||
version = "0.17.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8"
|
||||
checksum = "225e674cf31712b8bb15fdbca3ec0c1b9d825c5a24407ff2b7e005fb6a29ba03"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"glob",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"relative-path",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
"syn 2.0.23",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
@ -4418,7 +4485,7 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c95a930e03325234c18c7071fd2b60118307e025d6fff3e12745ffbf63a3d29c"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"cssparser",
|
||||
"ego-tree",
|
||||
"html5ever",
|
||||
@ -4651,7 +4718,7 @@ version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3d0815e7ff0f1f05e09d4b029f86d8a330f0ab15b35b28736f3758325f59e14"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"halfbrown",
|
||||
"lexical-core",
|
||||
"once_cell",
|
||||
@ -4819,11 +4886,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "strip-ansi-escapes"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
|
||||
checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa"
|
||||
dependencies = [
|
||||
"vte",
|
||||
"vte 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4962,9 +5029,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tabled"
|
||||
version = "0.12.2"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce69a5028cd9576063ec1f48edb2c75339fd835e6094ef3e05b3a079bf594a6"
|
||||
checksum = "dfe9c3632da101aba5131ed63f9eed38665f8b3c68703a6bb18124835c1a5d22"
|
||||
dependencies = [
|
||||
"ansi-str",
|
||||
"ansitok",
|
||||
@ -5358,13 +5425,9 @@ checksum = "22049a19f4a68748a168c0fc439f9516686aa045927ff767eca0a85101fb6e73"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
|
||||
dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
"regex",
|
||||
]
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@ -5489,6 +5552,16 @@ dependencies = [
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte_generate_state_changes"
|
||||
version = "0.1.1"
|
||||
|
53
Cargo.toml
53
Cargo.toml
@ -11,7 +11,7 @@ license = "MIT"
|
||||
name = "nu"
|
||||
repository = "https://github.com/nushell/nushell"
|
||||
rust-version = "1.60"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -46,35 +46,34 @@ members = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.83.1" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.83.1" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.83.1" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.83.1" }
|
||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.83.1", optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.83.1", optional = true }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.83.1" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.83.1" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.83.1" }
|
||||
nu-json = { path = "./crates/nu-json", version = "0.83.1" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.83.1" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.83.1" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.83.1" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.83.1" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.83.1" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.83.1" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.83.1" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.83.1" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.83.1" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.83.1" }
|
||||
nu-cli = { path = "./crates/nu-cli", version = "0.84.0" }
|
||||
nu-color-config = { path = "./crates/nu-color-config", version = "0.84.0" }
|
||||
nu-cmd-base = { path = "./crates/nu-cmd-base", version = "0.84.0" }
|
||||
nu-cmd-lang = { path = "./crates/nu-cmd-lang", version = "0.84.0" }
|
||||
nu-cmd-dataframe = { path = "./crates/nu-cmd-dataframe", version = "0.84.0", features = ["dataframe"], optional = true }
|
||||
nu-cmd-extra = { path = "./crates/nu-cmd-extra", version = "0.84.0", optional = true }
|
||||
nu-command = { path = "./crates/nu-command", version = "0.84.0" }
|
||||
nu-engine = { path = "./crates/nu-engine", version = "0.84.0" }
|
||||
nu-explore = { path = "./crates/nu-explore", version = "0.84.0" }
|
||||
nu-json = { path = "./crates/nu-json", version = "0.84.0" }
|
||||
nu-parser = { path = "./crates/nu-parser", version = "0.84.0" }
|
||||
nu-path = { path = "./crates/nu-path", version = "0.84.0" }
|
||||
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.84.0" }
|
||||
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.84.0" }
|
||||
nu-protocol = { path = "./crates/nu-protocol", version = "0.84.0" }
|
||||
nu-system = { path = "./crates/nu-system", version = "0.84.0" }
|
||||
nu-table = { path = "./crates/nu-table", version = "0.84.0" }
|
||||
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.84.0" }
|
||||
nu-std = { path = "./crates/nu-std", version = "0.84.0" }
|
||||
nu-utils = { path = "./crates/nu-utils", version = "0.84.0" }
|
||||
nu-ansi-term = "0.49.0"
|
||||
reedline = { version = "0.22.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
mimalloc = { version = "0.1.37", default-features = false, optional = true}
|
||||
reedline = { version = "0.23.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
crossterm = "0.26"
|
||||
ctrlc = "3.4"
|
||||
log = "0.4"
|
||||
miette = { version = "5.10", features = ["fancy-no-backtrace"] }
|
||||
mimalloc = { version = "0.1.37", default-features = false, optional = true}
|
||||
serde_json = "1.0"
|
||||
simplelog = "0.12"
|
||||
time = "0.3"
|
||||
@ -97,13 +96,13 @@ nix = { version = "0.26", default-features = false, features = [
|
||||
is-terminal = "0.4.8"
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.83.1" }
|
||||
tempfile = "3.7"
|
||||
nu-test-support = { path = "./crates/nu-test-support", version = "0.84.0" }
|
||||
assert_cmd = "2.0"
|
||||
criterion = "0.5"
|
||||
pretty_assertions = "1.4"
|
||||
rstest = { version = "0.18", default-features = false }
|
||||
serial_test = "2.0"
|
||||
rstest = { version = "0.17", default-features = false }
|
||||
tempfile = "3.7"
|
||||
|
||||
[features]
|
||||
plugin = [
|
||||
|
15
Cross.toml
15
Cross.toml
@ -1,9 +1,18 @@
|
||||
# Configuration for cross-rs: https://github.com/cross-rs/cross
|
||||
# Run cross-rs like this:
|
||||
# cross build --target aarch64-unknown-linux-musl --release
|
||||
# cross build --target aarch64-unknown-linux-gnu --release
|
||||
# or
|
||||
# cross build --target aarch64-unknown-linux-musl --release --features=static-link-openssl
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-gnu.dockerfile"
|
||||
pre-build = [
|
||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||
"apt-get update && apt-get install --assume-yes libssl-dev:$CROSS_DEB_ARCH clang"
|
||||
]
|
||||
|
||||
# NOTE: for musl you will need to build with --features=static-link-openssl
|
||||
[target.aarch64-unknown-linux-musl]
|
||||
dockerfile = "./docker/cross-rs/aarch64-unknown-linux-musl.dockerfile"
|
||||
pre-build = [
|
||||
"dpkg --add-architecture $CROSS_DEB_ARCH",
|
||||
"apt-get update && apt-get install --assume-yes clang"
|
||||
]
|
||||
|
@ -5,27 +5,27 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cli"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cli"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.83.1" }
|
||||
rstest = { version = "0.17.0", default-features = false }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.84.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.84.0" }
|
||||
rstest = { version = "0.18.1", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.83.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.83.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.83.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.83.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.83.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.83.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.83.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.83.1" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.84.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.84.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.84.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.84.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.84.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.84.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.84.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.84.0" }
|
||||
nu-ansi-term = "0.49.0"
|
||||
reedline = { version = "0.22.0", features = ["bashisms", "sqlite"]}
|
||||
reedline = { version = "0.23.0", features = ["bashisms", "sqlite"]}
|
||||
|
||||
chrono = { default-features = false, features = ["std"], version = "0.4" }
|
||||
crossterm = "0.26"
|
||||
|
@ -62,11 +62,11 @@ impl Command for KeybindingsList {
|
||||
let all_options = ["modifiers", "keycodes", "edits", "modes", "events"];
|
||||
all_options
|
||||
.iter()
|
||||
.flat_map(|argument| get_records(argument, &call.head))
|
||||
.flat_map(|argument| get_records(argument, call.head))
|
||||
.collect()
|
||||
} else {
|
||||
call.named_iter()
|
||||
.flat_map(|(argument, _, _)| get_records(argument.item.as_str(), &call.head))
|
||||
.flat_map(|(argument, _, _)| get_records(argument.item.as_str(), call.head))
|
||||
.collect()
|
||||
};
|
||||
|
||||
@ -78,7 +78,7 @@ impl Command for KeybindingsList {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_records(entry_type: &str, span: &Span) -> Vec<Value> {
|
||||
fn get_records(entry_type: &str, span: Span) -> Vec<Value> {
|
||||
let values = match entry_type {
|
||||
"modifiers" => get_reedline_keybinding_modifiers().sorted(),
|
||||
"keycodes" => get_reedline_keycodes().sorted(),
|
||||
@ -95,15 +95,15 @@ fn get_records(entry_type: &str, span: &Span) -> Vec<Value> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn convert_to_record(edit: &str, entry_type: &str, span: &Span) -> Value {
|
||||
let entry_type = Value::string(entry_type, *span);
|
||||
fn convert_to_record(edit: &str, entry_type: &str, span: Span) -> Value {
|
||||
let entry_type = Value::string(entry_type, span);
|
||||
|
||||
let name = Value::string(edit, *span);
|
||||
let name = Value::string(edit, span);
|
||||
|
||||
Value::Record {
|
||||
cols: vec!["type".to_string(), "name".to_string()],
|
||||
vals: vec![entry_type, name],
|
||||
span: *span,
|
||||
span,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ impl NuCompleter {
|
||||
) -> Option<Vec<Suggestion>> {
|
||||
let stack = self.stack.clone();
|
||||
let block = self.engine_state.get_block(block_id);
|
||||
let mut callee_stack = stack.gather_captures(&block.captures);
|
||||
let mut callee_stack = stack.gather_captures(&self.engine_state, &block.captures);
|
||||
|
||||
// Line
|
||||
if let Some(pos_arg) = block.signature.required_positional.get(0) {
|
||||
|
@ -2,6 +2,7 @@ use crate::util::eval_source;
|
||||
use log::info;
|
||||
use log::trace;
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_engine::eval_block_with_early_return;
|
||||
use nu_engine::{convert_env_values, current_dir};
|
||||
use nu_parser::parse;
|
||||
use nu_path::canonicalize_with;
|
||||
@ -98,23 +99,59 @@ pub fn evaluate_file(
|
||||
Value::string(file_path.to_string_lossy(), Span::unknown()),
|
||||
);
|
||||
|
||||
let source_filename = file_path
|
||||
.file_name()
|
||||
.expect("internal error: script missing filename");
|
||||
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
trace!("parsing file: {}", file_path_str);
|
||||
let _ = parse(&mut working_set, Some(file_path_str), &file, false);
|
||||
let block = parse(&mut working_set, Some(file_path_str), &file, false);
|
||||
|
||||
if working_set.find_decl(b"main").is_some() {
|
||||
for block in &mut working_set.delta.blocks {
|
||||
if block.signature.name == "main" {
|
||||
block.signature.name = source_filename.to_string_lossy().to_string();
|
||||
} else if block.signature.name.starts_with("main ") {
|
||||
block.signature.name = source_filename.to_string_lossy().to_string()
|
||||
+ " "
|
||||
+ &String::from_utf8_lossy(&block.signature.name.as_bytes()[5..]);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = engine_state.merge_delta(working_set.delta);
|
||||
|
||||
if engine_state.find_decl(b"main", &[]).is_some() {
|
||||
let args = format!("main {}", args.join(" "));
|
||||
|
||||
if !eval_source(
|
||||
let pipeline_data = eval_block_with_early_return(
|
||||
engine_state,
|
||||
stack,
|
||||
&file,
|
||||
file_path_str,
|
||||
&block,
|
||||
PipelineData::empty(),
|
||||
true,
|
||||
) {
|
||||
false,
|
||||
false,
|
||||
)
|
||||
.unwrap_or_else(|e| {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
let result = pipeline_data.print(engine_state, stack, true, false);
|
||||
|
||||
match result {
|
||||
Err(err) => {
|
||||
let working_set = StateWorkingSet::new(engine_state);
|
||||
|
||||
report_error(&working_set, &err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
Ok(exit_code) => {
|
||||
if exit_code != 0 {
|
||||
std::process::exit(exit_code as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !eval_source(
|
||||
engine_state,
|
||||
stack,
|
||||
|
@ -646,7 +646,10 @@ impl Menu for DescriptionMenu {
|
||||
|lb| {
|
||||
lb.replace_range(start..end, replacement);
|
||||
let mut offset = lb.insertion_point();
|
||||
offset += lb.len().saturating_sub(end.saturating_sub(start));
|
||||
offset += lb
|
||||
.len()
|
||||
.saturating_sub(end.saturating_sub(start))
|
||||
.saturating_sub(start);
|
||||
lb.set_insertion_point(offset);
|
||||
},
|
||||
UndoBehavior::CreateUndoPoint,
|
||||
|
@ -109,8 +109,7 @@ impl Prompt for NushellPrompt {
|
||||
let prompt = default
|
||||
.render_prompt_left()
|
||||
.to_string()
|
||||
.replace('\n', "\r\n")
|
||||
+ " ";
|
||||
.replace('\n', "\r\n");
|
||||
|
||||
prompt.into()
|
||||
}
|
||||
@ -144,11 +143,11 @@ impl Prompt for NushellPrompt {
|
||||
PromptEditMode::Vi(vi_mode) => match vi_mode {
|
||||
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
|
||||
Some(indicator) => indicator,
|
||||
None => ": ",
|
||||
None => "> ",
|
||||
},
|
||||
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
|
||||
Some(indicator) => indicator,
|
||||
None => "> ",
|
||||
None => ": ",
|
||||
},
|
||||
}
|
||||
.into(),
|
||||
|
@ -131,7 +131,7 @@ fn add_menu(
|
||||
config: &Config,
|
||||
) -> Result<Reedline, ShellError> {
|
||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||
let layout = extract_value("layout", cols, vals, span)?.into_string("", config);
|
||||
let layout = extract_value("layout", cols, vals, *span)?.into_string("", config);
|
||||
|
||||
match layout.as_str() {
|
||||
"columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config),
|
||||
@ -155,7 +155,7 @@ fn add_menu(
|
||||
macro_rules! add_style {
|
||||
// first arm match add!(1,2), add!(2,3) etc
|
||||
($name:expr, $cols: expr, $vals:expr, $span:expr, $config: expr, $menu:expr, $f:expr) => {
|
||||
$menu = match extract_value($name, $cols, $vals, $span) {
|
||||
$menu = match extract_value($name, $cols, $vals, *$span) {
|
||||
Ok(text) => {
|
||||
let style = match text {
|
||||
Value::String { val, .. } => lookup_ansi_color_style(&val),
|
||||
@ -181,7 +181,7 @@ pub(crate) fn add_columnar_menu(
|
||||
let mut columnar_menu = ColumnarMenu::default().with_name(&name);
|
||||
|
||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||
columnar_menu = match extract_value("columns", cols, vals, span) {
|
||||
columnar_menu = match extract_value("columns", cols, vals, *span) {
|
||||
Ok(columns) => {
|
||||
let columns = columns.as_int()?;
|
||||
columnar_menu.with_columns(columns as u16)
|
||||
@ -189,7 +189,7 @@ pub(crate) fn add_columnar_menu(
|
||||
Err(_) => columnar_menu,
|
||||
};
|
||||
|
||||
columnar_menu = match extract_value("col_width", cols, vals, span) {
|
||||
columnar_menu = match extract_value("col_width", cols, vals, *span) {
|
||||
Ok(col_width) => {
|
||||
let col_width = col_width.as_int()?;
|
||||
columnar_menu.with_column_width(Some(col_width as usize))
|
||||
@ -197,7 +197,7 @@ pub(crate) fn add_columnar_menu(
|
||||
Err(_) => columnar_menu.with_column_width(None),
|
||||
};
|
||||
|
||||
columnar_menu = match extract_value("col_padding", cols, vals, span) {
|
||||
columnar_menu = match extract_value("col_padding", cols, vals, *span) {
|
||||
Ok(col_padding) => {
|
||||
let col_padding = col_padding.as_int()?;
|
||||
columnar_menu.with_column_padding(col_padding as usize)
|
||||
@ -283,7 +283,7 @@ pub(crate) fn add_list_menu(
|
||||
let mut list_menu = ListMenu::default().with_name(&name);
|
||||
|
||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||
list_menu = match extract_value("page_size", cols, vals, span) {
|
||||
list_menu = match extract_value("page_size", cols, vals, *span) {
|
||||
Ok(page_size) => {
|
||||
let page_size = page_size.as_int()?;
|
||||
list_menu.with_page_size(page_size as usize)
|
||||
@ -369,7 +369,7 @@ pub(crate) fn add_description_menu(
|
||||
let mut description_menu = DescriptionMenu::default().with_name(&name);
|
||||
|
||||
if let Value::Record { cols, vals, span } = &menu.menu_type {
|
||||
description_menu = match extract_value("columns", cols, vals, span) {
|
||||
description_menu = match extract_value("columns", cols, vals, *span) {
|
||||
Ok(columns) => {
|
||||
let columns = columns.as_int()?;
|
||||
description_menu.with_columns(columns as u16)
|
||||
@ -377,7 +377,7 @@ pub(crate) fn add_description_menu(
|
||||
Err(_) => description_menu,
|
||||
};
|
||||
|
||||
description_menu = match extract_value("col_width", cols, vals, span) {
|
||||
description_menu = match extract_value("col_width", cols, vals, *span) {
|
||||
Ok(col_width) => {
|
||||
let col_width = col_width.as_int()?;
|
||||
description_menu.with_column_width(Some(col_width as usize))
|
||||
@ -385,7 +385,7 @@ pub(crate) fn add_description_menu(
|
||||
Err(_) => description_menu.with_column_width(None),
|
||||
};
|
||||
|
||||
description_menu = match extract_value("col_padding", cols, vals, span) {
|
||||
description_menu = match extract_value("col_padding", cols, vals, *span) {
|
||||
Ok(col_padding) => {
|
||||
let col_padding = col_padding.as_int()?;
|
||||
description_menu.with_column_padding(col_padding as usize)
|
||||
@ -393,7 +393,7 @@ pub(crate) fn add_description_menu(
|
||||
Err(_) => description_menu,
|
||||
};
|
||||
|
||||
description_menu = match extract_value("selection_rows", cols, vals, span) {
|
||||
description_menu = match extract_value("selection_rows", cols, vals, *span) {
|
||||
Ok(selection_rows) => {
|
||||
let selection_rows = selection_rows.as_int()?;
|
||||
description_menu.with_selection_rows(selection_rows as u16)
|
||||
@ -401,7 +401,7 @@ pub(crate) fn add_description_menu(
|
||||
Err(_) => description_menu,
|
||||
};
|
||||
|
||||
description_menu = match extract_value("description_rows", cols, vals, span) {
|
||||
description_menu = match extract_value("description_rows", cols, vals, *span) {
|
||||
Ok(description_rows) => {
|
||||
let description_rows = description_rows.as_int()?;
|
||||
description_menu.with_description_rows(description_rows as usize)
|
||||
@ -523,6 +523,12 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
|
||||
KeyCode::F(1),
|
||||
ReedlineEvent::Menu("help_menu".to_string()),
|
||||
);
|
||||
|
||||
keybindings.add_binding(
|
||||
KeyModifiers::CONTROL,
|
||||
KeyCode::Char('q'),
|
||||
ReedlineEvent::SearchHistory,
|
||||
);
|
||||
}
|
||||
|
||||
pub enum KeybindingsMode {
|
||||
@ -719,26 +725,26 @@ impl<'config> EventType<'config> {
|
||||
fn try_from_columns(
|
||||
cols: &'config [String],
|
||||
vals: &'config [Value],
|
||||
span: &'config Span,
|
||||
span: Span,
|
||||
) -> Result<Self, ShellError> {
|
||||
extract_value("send", cols, vals, span)
|
||||
.map(Self::Send)
|
||||
.or_else(|_| extract_value("edit", cols, vals, span).map(Self::Edit))
|
||||
.or_else(|_| extract_value("until", cols, vals, span).map(Self::Until))
|
||||
.map_err(|_| ShellError::MissingConfigValue("send, edit or until".to_string(), *span))
|
||||
.map_err(|_| ShellError::MissingConfigValue("send, edit or until".to_string(), span))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>, ShellError> {
|
||||
match value {
|
||||
Value::Record { cols, vals, span } => {
|
||||
match EventType::try_from_columns(cols, vals, span)? {
|
||||
match EventType::try_from_columns(cols, vals, *span)? {
|
||||
EventType::Send(value) => event_from_record(
|
||||
value.into_string("", config).to_lowercase().as_str(),
|
||||
cols,
|
||||
vals,
|
||||
config,
|
||||
span,
|
||||
*span,
|
||||
)
|
||||
.map(Some),
|
||||
EventType::Edit(value) => {
|
||||
@ -747,7 +753,7 @@ fn parse_event(value: &Value, config: &Config) -> Result<Option<ReedlineEvent>,
|
||||
cols,
|
||||
vals,
|
||||
config,
|
||||
span,
|
||||
*span,
|
||||
)?;
|
||||
Ok(Some(ReedlineEvent::Edit(vec![edit])))
|
||||
}
|
||||
@ -810,7 +816,7 @@ fn event_from_record(
|
||||
cols: &[String],
|
||||
vals: &[Value],
|
||||
config: &Config,
|
||||
span: &Span,
|
||||
span: Span,
|
||||
) -> Result<ReedlineEvent, ShellError> {
|
||||
let event = match name {
|
||||
"none" => ReedlineEvent::None,
|
||||
@ -853,7 +859,7 @@ fn event_from_record(
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"Reedline event".to_string(),
|
||||
v.to_string(),
|
||||
*span,
|
||||
span,
|
||||
))
|
||||
}
|
||||
};
|
||||
@ -866,7 +872,7 @@ fn edit_from_record(
|
||||
cols: &[String],
|
||||
vals: &[Value],
|
||||
config: &Config,
|
||||
span: &Span,
|
||||
span: Span,
|
||||
) -> Result<EditCommand, ShellError> {
|
||||
let edit = match name {
|
||||
"movetostart" => EditCommand::MoveToStart,
|
||||
@ -968,7 +974,7 @@ fn edit_from_record(
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"reedline EditCommand".to_string(),
|
||||
e.to_string(),
|
||||
*span,
|
||||
span,
|
||||
))
|
||||
}
|
||||
};
|
||||
@ -995,7 +1001,7 @@ mod test {
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
let b = EventType::try_from_columns(&cols, &vals, span).unwrap();
|
||||
assert!(matches!(b, EventType::Send(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
@ -1015,7 +1021,7 @@ mod test {
|
||||
let vals = vec![Value::test_string("Clear")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
let b = EventType::try_from_columns(&cols, &vals, span).unwrap();
|
||||
assert!(matches!(b, EventType::Edit(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
@ -1041,7 +1047,7 @@ mod test {
|
||||
];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
let b = EventType::try_from_columns(&cols, &vals, span).unwrap();
|
||||
assert!(matches!(b, EventType::Send(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
@ -1091,7 +1097,7 @@ mod test {
|
||||
}];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span).unwrap();
|
||||
let b = EventType::try_from_columns(&cols, &vals, span).unwrap();
|
||||
assert!(matches!(b, EventType::Until(_)));
|
||||
|
||||
let event = Value::Record {
|
||||
@ -1159,7 +1165,7 @@ mod test {
|
||||
let vals = vec![Value::test_string("Enter")];
|
||||
|
||||
let span = Span::test_data();
|
||||
let b = EventType::try_from_columns(&cols, &vals, &span);
|
||||
let b = EventType::try_from_columns(&cols, &vals, span);
|
||||
assert!(matches!(b, Err(ShellError::MissingConfigValue(_, _))));
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ impl Highlighter for NuHighlighter {
|
||||
($shape:expr, $span:expr, $text:expr) => {{
|
||||
let spans = split_span_by_highlight_positions(
|
||||
line,
|
||||
&$span,
|
||||
$span,
|
||||
&matching_brackets_pos,
|
||||
global_span_offset,
|
||||
);
|
||||
@ -143,7 +143,7 @@ impl Highlighter for NuHighlighter {
|
||||
|
||||
fn split_span_by_highlight_positions(
|
||||
line: &str,
|
||||
span: &Span,
|
||||
span: Span,
|
||||
highlight_positions: &Vec<usize>,
|
||||
global_span_offset: usize,
|
||||
) -> Vec<(Span, bool)> {
|
||||
|
@ -105,7 +105,7 @@ fn gather_env_vars(
|
||||
span: full_span,
|
||||
} = token
|
||||
{
|
||||
let contents = engine_state.get_span_contents(&full_span);
|
||||
let contents = engine_state.get_span_contents(full_span);
|
||||
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
||||
|
||||
let name = if let Some(Token {
|
||||
|
@ -5,12 +5,12 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-base"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-base"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.83.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.83.1" }
|
||||
nu-protocol = { version = "0.83.1", path = "../nu-protocol" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.84.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.84.0" }
|
||||
nu-protocol = { version = "0.84.0", path = "../nu-protocol" }
|
||||
indexmap = { version = "2.0" }
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-dataframe"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-dataframe"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,9 +13,9 @@ version = "0.83.1"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.83.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.83.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.83.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.84.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.84.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.84.0" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
chrono = { version = "0.4", features = ["std", "unstable-locales"], default-features = false }
|
||||
@ -24,6 +24,7 @@ indexmap = { version = "2.0" }
|
||||
num = { version = "0.4", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sqlparser = { version = "0.34", features = ["serde"], optional = true }
|
||||
polars-io = { version = "0.30.0", features = ["avro"] }
|
||||
|
||||
[dependencies.polars]
|
||||
features = [
|
||||
@ -50,15 +51,15 @@ features = [
|
||||
"serde",
|
||||
"serde-lazy",
|
||||
"strings",
|
||||
"to_dummies",
|
||||
"to_dummies"
|
||||
]
|
||||
optional = true
|
||||
version = "0.30.0"
|
||||
|
||||
[features]
|
||||
dataframe = ["default"]
|
||||
default = ["num", "polars", "sqlparser"]
|
||||
dataframe = ["num", "polars", "sqlparser"]
|
||||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.83.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.84.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.84.0" }
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::super::values::{Column, NuDataFrame};
|
||||
use super::super::values::{Column, NuDataFrame, NuExpression};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
@ -15,7 +15,7 @@ impl Command for FirstDF {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Show only the first number of rows."
|
||||
"Show only the first number of rows or create a first expression"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
@ -25,10 +25,16 @@ impl Command for FirstDF {
|
||||
SyntaxShape::Int,
|
||||
"starting from the front, the number of rows to return",
|
||||
)
|
||||
.input_output_type(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
@ -64,6 +70,11 @@ impl Command for FirstDF {
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a first expression from a column",
|
||||
example: "dfr col a | dfr first",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@ -74,8 +85,19 @@ impl Command for FirstDF {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
command(engine_state, stack, call, df)
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let df = NuDataFrame::try_from_value(value)?;
|
||||
command(engine_state, stack, call, df)
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().first().into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,11 +119,25 @@ fn command(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::test_dataframe::test_dataframe;
|
||||
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_dataframe(vec![Box::new(FirstDF {})])
|
||||
fn test_examples_dataframe() {
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new(FirstDF {})]);
|
||||
test_dataframe_example(&mut engine_state, &FirstDF.examples()[0]);
|
||||
test_dataframe_example(&mut engine_state, &FirstDF.examples()[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_expression() {
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new(FirstDF {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &FirstDF.examples()[2]);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame};
|
||||
use super::super::values::{utils::DEFAULT_ROWS, Column, NuDataFrame, NuExpression};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
@ -21,26 +21,39 @@ impl Command for LastDF {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.optional("rows", SyntaxShape::Int, "Number of rows for tail")
|
||||
.input_output_type(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create new dataframe with last rows",
|
||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(3)]),
|
||||
Column::new("b".to_string(), vec![Value::test_int(4)]),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Create new dataframe with last rows",
|
||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(3)]),
|
||||
Column::new("b".to_string(), vec![Value::test_int(4)]),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a last expression from a column",
|
||||
example: "dfr col a | dfr last",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -50,8 +63,19 @@ impl Command for LastDF {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
command(engine_state, stack, call, df)
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let df = NuDataFrame::try_from_value(value)?;
|
||||
command(engine_state, stack, call, df)
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().last().into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,11 +97,24 @@ fn command(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::test_dataframe::test_dataframe;
|
||||
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_dataframe(vec![Box::new(LastDF {})])
|
||||
fn test_examples_dataframe() {
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new(LastDF {})]);
|
||||
test_dataframe_example(&mut engine_state, &LastDF.examples()[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_expression() {
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new(LastDF {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &LastDF.examples()[1]);
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ mod sql_expr;
|
||||
mod summary;
|
||||
mod take;
|
||||
mod to_arrow;
|
||||
mod to_avro;
|
||||
mod to_csv;
|
||||
mod to_df;
|
||||
mod to_json_lines;
|
||||
@ -55,6 +56,7 @@ pub use sql_expr::parse_sql_expr;
|
||||
pub use summary::Summary;
|
||||
pub use take::TakeDF;
|
||||
pub use to_arrow::ToArrow;
|
||||
pub use to_avro::ToAvro;
|
||||
pub use to_csv::ToCSV;
|
||||
pub use to_df::ToDataFrame;
|
||||
pub use to_json_lines::ToJsonLines;
|
||||
@ -96,6 +98,7 @@ pub fn add_eager_decls(working_set: &mut StateWorkingSet) {
|
||||
SliceDF,
|
||||
TakeDF,
|
||||
ToArrow,
|
||||
ToAvro,
|
||||
ToCSV,
|
||||
ToDataFrame,
|
||||
ToNu,
|
||||
|
@ -13,6 +13,8 @@ use polars::prelude::{
|
||||
LazyFrame, ParallelStrategy, ParquetReader, ScanArgsIpc, ScanArgsParquet, SerReader,
|
||||
};
|
||||
|
||||
use polars_io::avro::AvroReader;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpenDataFrame;
|
||||
|
||||
@ -22,7 +24,7 @@ impl Command for OpenDataFrame {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Opens CSV, JSON, JSON lines, arrow, or parquet file to create dataframe."
|
||||
"Opens CSV, JSON, JSON lines, arrow, avro, or parquet file to create dataframe."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
@ -36,7 +38,7 @@ impl Command for OpenDataFrame {
|
||||
.named(
|
||||
"type",
|
||||
SyntaxShape::String,
|
||||
"File type: csv, tsv, json, parquet, arrow. If omitted, derive from file extension",
|
||||
"File type: csv, tsv, json, parquet, arrow, avro. If omitted, derive from file extension",
|
||||
Some('t'),
|
||||
)
|
||||
.named(
|
||||
@ -118,6 +120,7 @@ fn command(
|
||||
"ipc" | "arrow" => from_ipc(engine_state, stack, call),
|
||||
"json" => from_json(engine_state, stack, call),
|
||||
"jsonl" => from_jsonl(engine_state, stack, call),
|
||||
"avro" => from_avro(engine_state, stack, call),
|
||||
_ => Err(ShellError::FileNotFoundCustom(
|
||||
format!("{msg}. Supported values: csv, tsv, parquet, ipc, arrow, json"),
|
||||
blamed,
|
||||
@ -199,6 +202,46 @@ fn from_parquet(
|
||||
}
|
||||
}
|
||||
|
||||
fn from_avro(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
) -> Result<Value, ShellError> {
|
||||
let file: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||
let columns: Option<Vec<String>> = call.get_flag(engine_state, stack, "columns")?;
|
||||
|
||||
let r = File::open(&file.item).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error opening file".into(),
|
||||
e.to_string(),
|
||||
Some(file.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
let reader = AvroReader::new(r);
|
||||
|
||||
let reader = match columns {
|
||||
None => reader,
|
||||
Some(columns) => reader.with_columns(Some(columns)),
|
||||
};
|
||||
|
||||
let df: NuDataFrame = reader
|
||||
.finish()
|
||||
.map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Avro reader error".into(),
|
||||
format!("{e:?}"),
|
||||
Some(call.head),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?
|
||||
.into();
|
||||
|
||||
Ok(df.into_value(call.head))
|
||||
}
|
||||
|
||||
fn from_ipc(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
|
123
crates/nu-cmd-dataframe/src/dataframe/eager/to_avro.rs
Normal file
123
crates/nu-cmd-dataframe/src/dataframe/eager/to_avro.rs
Normal file
@ -0,0 +1,123 @@
|
||||
use std::{fs::File, path::PathBuf};
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value,
|
||||
};
|
||||
use polars_io::avro::{AvroCompression, AvroWriter};
|
||||
use polars_io::SerWriter;
|
||||
|
||||
use super::super::values::NuDataFrame;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ToAvro;
|
||||
|
||||
impl Command for ToAvro {
|
||||
fn name(&self) -> &str {
|
||||
"dfr to-avro"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Saves dataframe to avro file."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.named(
|
||||
"compression",
|
||||
SyntaxShape::String,
|
||||
"use compression, supports deflate or snappy",
|
||||
Some('c'),
|
||||
)
|
||||
.required("file", SyntaxShape::Filepath, "file path to save dataframe")
|
||||
.input_output_type(Type::Custom("dataframe".into()), Type::Any)
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Saves dataframe to avro file",
|
||||
example: "[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-avro test.avro",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
command(engine_state, stack, call, input)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_compression(call: &Call) -> Result<Option<AvroCompression>, ShellError> {
|
||||
if let Some((compression, span)) = call
|
||||
.get_flag_expr("compression")
|
||||
.and_then(|e| e.as_string().map(|s| (s, e.span)))
|
||||
{
|
||||
match compression.as_ref() {
|
||||
"snappy" => Ok(Some(AvroCompression::Snappy)),
|
||||
"deflate" => Ok(Some(AvroCompression::Deflate)),
|
||||
_ => Err(ShellError::IncorrectValue {
|
||||
msg: "compression must be one of deflate or snappy".to_string(),
|
||||
val_span: span,
|
||||
call_span: span,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
fn command(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let file_name: Spanned<PathBuf> = call.req(engine_state, stack, 0)?;
|
||||
let compression = get_compression(call)?;
|
||||
|
||||
let mut df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
|
||||
let file = File::create(&file_name.item).map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error with file name".into(),
|
||||
e.to_string(),
|
||||
Some(file_name.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
AvroWriter::new(file)
|
||||
.with_compression(compression)
|
||||
.finish(df.as_mut())
|
||||
.map_err(|e| {
|
||||
ShellError::GenericError(
|
||||
"Error saving file".into(),
|
||||
e.to_string(),
|
||||
Some(file_name.span),
|
||||
None,
|
||||
Vec::new(),
|
||||
)
|
||||
})?;
|
||||
|
||||
let file_value = Value::String {
|
||||
val: format!("saved {:?}", &file_name.item),
|
||||
span: file_name.span,
|
||||
};
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
Value::List {
|
||||
vals: vec![file_value],
|
||||
span: call.head,
|
||||
},
|
||||
None,
|
||||
))
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/// Definition of multiple Expression commands using a macro rule
|
||||
/// All of these expressions have an identical body and only require
|
||||
/// to have a change in the name, description and expression function
|
||||
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
|
||||
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
@ -134,6 +134,186 @@ macro_rules! expr_command {
|
||||
};
|
||||
}
|
||||
|
||||
// The structs defined in this file are structs that form part of other commands
|
||||
// since they share a similar name
|
||||
macro_rules! lazy_expr_command {
|
||||
($command: ident, $name: expr, $desc: expr, $examples: expr, $func: ident, $test: ident) => {
|
||||
#[derive(Clone)]
|
||||
pub struct $command;
|
||||
|
||||
impl Command for $command {
|
||||
fn name(&self) -> &str {
|
||||
$name
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
$desc
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("expression".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
$examples
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let lazy = NuLazyFrame::try_from_value(value)?;
|
||||
let lazy = NuLazyFrame::new(lazy.from_eager, lazy.into_polars().$func());
|
||||
|
||||
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().$func().into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod $test {
|
||||
use super::super::super::test_dataframe::{
|
||||
build_test_engine_state, test_dataframe_example,
|
||||
};
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
|
||||
#[test]
|
||||
fn test_examples_dataframe() {
|
||||
// the first example should be a for the dataframe case
|
||||
let example = &$command.examples()[0];
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new($command {})]);
|
||||
test_dataframe_example(&mut engine_state, &example)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_expressions() {
|
||||
// the second example should be a for the dataframe case
|
||||
let example = &$command.examples()[1];
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new($command {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &example)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($command: ident, $name: expr, $desc: expr, $examples: expr, $func: ident, $test: ident, $ddof: expr) => {
|
||||
#[derive(Clone)]
|
||||
pub struct $command;
|
||||
|
||||
impl Command for $command {
|
||||
fn name(&self) -> &str {
|
||||
$name
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
$desc
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("expression".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
$examples
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let lazy = NuLazyFrame::try_from_value(value)?;
|
||||
let lazy = NuLazyFrame::new(lazy.from_eager, lazy.into_polars().$func($ddof));
|
||||
|
||||
Ok(PipelineData::Value(lazy.into_value(call.head)?, None))
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().$func($ddof).into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod $test {
|
||||
use super::super::super::test_dataframe::{
|
||||
build_test_engine_state, test_dataframe_example,
|
||||
};
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
|
||||
#[test]
|
||||
fn test_examples_dataframe() {
|
||||
// the first example should be a for the dataframe case
|
||||
let example = &$command.examples()[0];
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new($command {})]);
|
||||
test_dataframe_example(&mut engine_state, &example)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_expressions() {
|
||||
// the second example should be a for the dataframe case
|
||||
let example = &$command.examples()[1];
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new($command {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &example)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ExprList command
|
||||
// Expands to a command definition for a list expression
|
||||
expr_command!(
|
||||
@ -164,36 +344,6 @@ expr_command!(
|
||||
test_groups
|
||||
);
|
||||
|
||||
// ExprFlatten command
|
||||
// Expands to a command definition for a flatten expression
|
||||
expr_command!(
|
||||
ExprFlatten,
|
||||
"dfr flatten",
|
||||
"creates a flatten expression",
|
||||
vec![Example {
|
||||
description: "",
|
||||
example: "",
|
||||
result: None,
|
||||
}],
|
||||
flatten,
|
||||
test_flatten
|
||||
);
|
||||
|
||||
// ExprExplode command
|
||||
// Expands to a command definition for a explode expression
|
||||
expr_command!(
|
||||
ExprExplode,
|
||||
"dfr explode",
|
||||
"creates an explode expression",
|
||||
vec![Example {
|
||||
description: "",
|
||||
example: "",
|
||||
result: None,
|
||||
}],
|
||||
explode,
|
||||
test_explode
|
||||
);
|
||||
|
||||
// ExprCount command
|
||||
// Expands to a command definition for a count expression
|
||||
expr_command!(
|
||||
@ -209,81 +359,6 @@ expr_command!(
|
||||
test_count
|
||||
);
|
||||
|
||||
// ExprFirst command
|
||||
// Expands to a command definition for a count expression
|
||||
expr_command!(
|
||||
ExprFirst,
|
||||
"dfr first",
|
||||
"creates a first expression",
|
||||
vec![Example {
|
||||
description: "Creates a first expression from a column",
|
||||
example: "dfr col a | dfr first",
|
||||
result: None,
|
||||
},],
|
||||
first,
|
||||
test_first
|
||||
);
|
||||
|
||||
// ExprLast command
|
||||
// Expands to a command definition for a count expression
|
||||
expr_command!(
|
||||
ExprLast,
|
||||
"dfr last",
|
||||
"creates a last expression",
|
||||
vec![Example {
|
||||
description: "Creates a last expression from a column",
|
||||
example: "dfr col a | dfr last",
|
||||
result: None,
|
||||
},],
|
||||
last,
|
||||
test_last
|
||||
);
|
||||
|
||||
// ExprNUnique command
|
||||
// Expands to a command definition for a n-unique expression
|
||||
expr_command!(
|
||||
ExprNUnique,
|
||||
"dfr n-unique",
|
||||
"creates a n-unique expression",
|
||||
vec![Example {
|
||||
description: "Creates a is n-unique expression from a column",
|
||||
example: "dfr col a | dfr n-unique",
|
||||
result: None,
|
||||
},],
|
||||
n_unique,
|
||||
test_nunique
|
||||
);
|
||||
|
||||
// ExprIsNotNull command
|
||||
// Expands to a command definition for a n-unique expression
|
||||
expr_command!(
|
||||
ExprIsNotNull,
|
||||
"dfr is-not-null",
|
||||
"creates a is not null expression",
|
||||
vec![Example {
|
||||
description: "Creates a is not null expression from a column",
|
||||
example: "dfr col a | dfr is-not-null",
|
||||
result: None,
|
||||
},],
|
||||
is_not_null,
|
||||
test_is_not_null
|
||||
);
|
||||
|
||||
// ExprIsNull command
|
||||
// Expands to a command definition for a n-unique expression
|
||||
expr_command!(
|
||||
ExprIsNull,
|
||||
"dfr is-null",
|
||||
"creates a is null expression",
|
||||
vec![Example {
|
||||
description: "Creates a is null expression from a column",
|
||||
example: "dfr col a | dfr is-null",
|
||||
result: None,
|
||||
},],
|
||||
is_null,
|
||||
test_is_null
|
||||
);
|
||||
|
||||
// ExprNot command
|
||||
// Expands to a command definition for a not expression
|
||||
expr_command!(
|
||||
@ -301,124 +376,180 @@ expr_command!(
|
||||
|
||||
// ExprMax command
|
||||
// Expands to a command definition for max aggregation
|
||||
expr_command!(
|
||||
lazy_expr_command!(
|
||||
ExprMax,
|
||||
"dfr max",
|
||||
"Creates a max expression",
|
||||
vec![Example {
|
||||
description: "Max aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
"Creates a max expression or aggregates columns to their max value",
|
||||
vec![
|
||||
Example {
|
||||
description: "Max value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr max",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(6)],),
|
||||
Column::new("b".to_string(), vec![Value::test_int(4)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Max aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
| dfr into-df
|
||||
| dfr group-by a
|
||||
| dfr agg (dfr col b | dfr max)"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_int(4), Value::test_int(1)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_int(4), Value::test_int(1)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
],
|
||||
max,
|
||||
test_max
|
||||
);
|
||||
|
||||
// ExprMin command
|
||||
// Expands to a command definition for min aggregation
|
||||
expr_command!(
|
||||
lazy_expr_command!(
|
||||
ExprMin,
|
||||
"dfr min",
|
||||
"Creates a min expression",
|
||||
vec![Example {
|
||||
description: "Min aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
"Creates a min expression or aggregates columns to their min value",
|
||||
vec![
|
||||
Example {
|
||||
description: "Min value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr min",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(1)],),
|
||||
Column::new("b".to_string(), vec![Value::test_int(1)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Min aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
| dfr into-df
|
||||
| dfr group-by a
|
||||
| dfr agg (dfr col b | dfr min)"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_int(2), Value::test_int(1)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_int(2), Value::test_int(1)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
],
|
||||
min,
|
||||
test_min
|
||||
);
|
||||
|
||||
// ExprSum command
|
||||
// Expands to a command definition for sum aggregation
|
||||
expr_command!(
|
||||
lazy_expr_command!(
|
||||
ExprSum,
|
||||
"dfr sum",
|
||||
"Creates a sum expression for an aggregation",
|
||||
vec![Example {
|
||||
description: "Sum aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
"Creates a sum expression for an aggregation or aggregates columns to their sum value",
|
||||
vec![
|
||||
Example {
|
||||
description: "Sums all columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sum",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(11)],),
|
||||
Column::new("b".to_string(), vec![Value::test_int(7)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Sum aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
| dfr into-df
|
||||
| dfr group-by a
|
||||
| dfr agg (dfr col b | dfr sum)"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_int(6), Value::test_int(1)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_int(6), Value::test_int(1)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
],
|
||||
sum,
|
||||
test_sum
|
||||
);
|
||||
|
||||
// ExprMean command
|
||||
// Expands to a command definition for mean aggregation
|
||||
expr_command!(
|
||||
lazy_expr_command!(
|
||||
ExprMean,
|
||||
"dfr mean",
|
||||
"Creates a mean expression for an aggregation",
|
||||
vec![Example {
|
||||
description: "Mean aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
"Creates a mean expression for an aggregation or aggregates columns to their mean value",
|
||||
vec![
|
||||
Example {
|
||||
description: "Mean value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr mean",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
||||
Column::new("b".to_string(), vec![Value::test_float(2.0)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Mean aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 4] [two 1]]
|
||||
| dfr into-df
|
||||
| dfr group-by a
|
||||
| dfr agg (dfr col b | dfr mean)"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_float(3.0), Value::test_float(1.0)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_float(3.0), Value::test_float(1.0)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
],
|
||||
mean,
|
||||
test_mean
|
||||
);
|
||||
@ -456,64 +587,93 @@ expr_command!(
|
||||
|
||||
// ExprStd command
|
||||
// Expands to a command definition for std aggregation
|
||||
expr_command!(
|
||||
lazy_expr_command!(
|
||||
ExprStd,
|
||||
"dfr std",
|
||||
"Creates a std expression for an aggregation",
|
||||
vec![Example {
|
||||
description: "Std aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
||||
"Creates a std expression for an aggregation of std value from columns in a dataframe",
|
||||
vec![
|
||||
Example {
|
||||
description: "Std value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr std",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_float(2.0)],),
|
||||
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Std aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
||||
| dfr into-df
|
||||
| dfr group-by a
|
||||
| dfr agg (dfr col b | dfr std)"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_float(0.0), Value::test_float(0.0)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_float(0.0), Value::test_float(0.0)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
],
|
||||
std,
|
||||
test_std,
|
||||
0
|
||||
1
|
||||
);
|
||||
|
||||
// ExprVar command
|
||||
// Expands to a command definition for var aggregation
|
||||
expr_command!(
|
||||
lazy_expr_command!(
|
||||
ExprVar,
|
||||
"dfr var",
|
||||
"Create a var expression for an aggregation",
|
||||
vec![Example {
|
||||
description: "Var aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
||||
vec![
|
||||
Example {
|
||||
description:
|
||||
"Var value from columns in a dataframe or aggregates columns to their var value",
|
||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr var",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
||||
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Var aggregation for a group-by",
|
||||
example: r#"[[a b]; [one 2] [one 2] [two 1] [two 1]]
|
||||
| dfr into-df
|
||||
| dfr group-by a
|
||||
| dfr agg (dfr col b | dfr var)"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_float(0.0), Value::test_float(0.0)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"a".to_string(),
|
||||
vec![Value::test_string("one"), Value::test_string("two")],
|
||||
),
|
||||
Column::new(
|
||||
"b".to_string(),
|
||||
vec![Value::test_float(0.0), Value::test_float(0.0)],
|
||||
),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
],
|
||||
var,
|
||||
test_var,
|
||||
0
|
||||
1
|
||||
);
|
||||
|
@ -47,15 +47,8 @@ pub fn add_expressions(working_set: &mut StateWorkingSet) {
|
||||
ExprQuantile,
|
||||
ExprList,
|
||||
ExprAggGroups,
|
||||
ExprFlatten,
|
||||
ExprExplode,
|
||||
ExprCount,
|
||||
ExprFirst,
|
||||
ExprLast,
|
||||
ExprNUnique,
|
||||
ExprIsIn,
|
||||
ExprIsNotNull,
|
||||
ExprIsNull,
|
||||
ExprNot,
|
||||
ExprMax,
|
||||
ExprMin,
|
||||
|
158
crates/nu-cmd-dataframe/src/dataframe/lazy/explode.rs
Normal file
158
crates/nu-cmd-dataframe/src/dataframe/lazy/explode.rs
Normal file
@ -0,0 +1,158 @@
|
||||
use crate::dataframe::values::{Column, NuDataFrame, NuExpression, NuLazyFrame};
|
||||
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LazyExplode;
|
||||
|
||||
impl Command for LazyExplode {
|
||||
fn name(&self) -> &str {
|
||||
"dfr explode"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Explodes a dataframe or creates a explode expression."
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest(
|
||||
"columns",
|
||||
SyntaxShape::String,
|
||||
"columns to explode, only applicable for dataframes",
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("lazyframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Explode the specified dataframe",
|
||||
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr explode hobbies | dfr collect",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"id".to_string(),
|
||||
vec![
|
||||
Value::test_int(1),
|
||||
Value::test_int(1),
|
||||
Value::test_int(2),
|
||||
Value::test_int(2),
|
||||
]),
|
||||
Column::new(
|
||||
"name".to_string(),
|
||||
vec![
|
||||
Value::test_string("Mercy"),
|
||||
Value::test_string("Mercy"),
|
||||
Value::test_string("Bob"),
|
||||
Value::test_string("Bob"),
|
||||
]),
|
||||
Column::new(
|
||||
"hobbies".to_string(),
|
||||
vec![
|
||||
Value::test_string("Cycling"),
|
||||
Value::test_string("Knitting"),
|
||||
Value::test_string("Skiing"),
|
||||
Value::test_string("Football"),
|
||||
]),
|
||||
]).expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
)
|
||||
},
|
||||
Example {
|
||||
description: "Select a column and explode the values",
|
||||
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr select (dfr col hobbies | dfr explode)",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"hobbies".to_string(),
|
||||
vec![
|
||||
Value::test_string("Cycling"),
|
||||
Value::test_string("Knitting"),
|
||||
Value::test_string("Skiing"),
|
||||
Value::test_string("Football"),
|
||||
]),
|
||||
]).expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
explode(call, input)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn explode(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let df = NuLazyFrame::try_from_value(value)?;
|
||||
let columns: Vec<String> = call
|
||||
.positional_iter()
|
||||
.filter_map(|e| e.as_string())
|
||||
.collect();
|
||||
|
||||
let exploded = df
|
||||
.into_polars()
|
||||
.explode(columns.iter().map(AsRef::as_ref).collect::<Vec<&str>>());
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuLazyFrame::from(exploded).into_value(call.head)?,
|
||||
None,
|
||||
))
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().explode().into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
|
||||
#[test]
|
||||
fn test_examples_dataframe() {
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new(LazyExplode {})]);
|
||||
test_dataframe_example(&mut engine_state, &LazyExplode.examples()[0]);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_examples_expression() {
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new(LazyExplode {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &LazyExplode.examples()[1]);
|
||||
}
|
||||
}
|
132
crates/nu-cmd-dataframe/src/dataframe/lazy/flatten.rs
Normal file
132
crates/nu-cmd-dataframe/src/dataframe/lazy/flatten.rs
Normal file
@ -0,0 +1,132 @@
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::dataframe::values::{Column, NuDataFrame};
|
||||
|
||||
use super::explode::explode;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct LazyFlatten;
|
||||
|
||||
impl Command for LazyFlatten {
|
||||
fn name(&self) -> &str {
|
||||
"dfr flatten"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"An alias for dfr explode"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.rest(
|
||||
"columns",
|
||||
SyntaxShape::String,
|
||||
"columns to flatten, only applicable for dataframes",
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("lazyframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Flatten the specified dataframe",
|
||||
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr flatten hobbies | dfr collect",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"id".to_string(),
|
||||
vec![
|
||||
Value::test_int(1),
|
||||
Value::test_int(1),
|
||||
Value::test_int(2),
|
||||
Value::test_int(2),
|
||||
]),
|
||||
Column::new(
|
||||
"name".to_string(),
|
||||
vec![
|
||||
Value::test_string("Mercy"),
|
||||
Value::test_string("Mercy"),
|
||||
Value::test_string("Bob"),
|
||||
Value::test_string("Bob"),
|
||||
]),
|
||||
Column::new(
|
||||
"hobbies".to_string(),
|
||||
vec![
|
||||
Value::test_string("Cycling"),
|
||||
Value::test_string("Knitting"),
|
||||
Value::test_string("Skiing"),
|
||||
Value::test_string("Football"),
|
||||
]),
|
||||
]).expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
)
|
||||
},
|
||||
Example {
|
||||
description: "Select a column and flatten the values",
|
||||
example: "[[id name hobbies]; [1 Mercy [Cycling Knitting]] [2 Bob [Skiing Football]]] | dfr into-df | dfr select (dfr col hobbies | dfr flatten)",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new(
|
||||
"hobbies".to_string(),
|
||||
vec![
|
||||
Value::test_string("Cycling"),
|
||||
Value::test_string("Knitting"),
|
||||
Value::test_string("Skiing"),
|
||||
Value::test_string("Football"),
|
||||
]),
|
||||
]).expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
explode(call, input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
|
||||
#[test]
|
||||
fn test_examples_dataframe() {
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new(LazyFlatten {})]);
|
||||
test_dataframe_example(&mut engine_state, &LazyFlatten.examples()[0]);
|
||||
}
|
||||
|
||||
#[ignore]
|
||||
#[test]
|
||||
fn test_examples_expression() {
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new(LazyFlatten {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &LazyFlatten.examples()[1]);
|
||||
}
|
||||
}
|
@ -157,94 +157,6 @@ lazy_command!(
|
||||
test_cache
|
||||
);
|
||||
|
||||
// LazyMax command
|
||||
// Expands to a command definition for max aggregation
|
||||
lazy_command!(
|
||||
LazyMax,
|
||||
"dfr max",
|
||||
"Aggregates columns to their max value",
|
||||
vec![Example {
|
||||
description: "Max value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr max",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(6)],),
|
||||
Column::new("b".to_string(), vec![Value::test_int(4)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
max,
|
||||
test_max
|
||||
);
|
||||
|
||||
// LazyMin command
|
||||
// Expands to a command definition for min aggregation
|
||||
lazy_command!(
|
||||
LazyMin,
|
||||
"dfr min",
|
||||
"Aggregates columns to their min value",
|
||||
vec![Example {
|
||||
description: "Min value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr min",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(1)],),
|
||||
Column::new("b".to_string(), vec![Value::test_int(1)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
min,
|
||||
test_min
|
||||
);
|
||||
|
||||
// LazySum command
|
||||
// Expands to a command definition for sum aggregation
|
||||
lazy_command!(
|
||||
LazySum,
|
||||
"dfr sum",
|
||||
"Aggregates columns to their sum value",
|
||||
vec![Example {
|
||||
description: "Sums all columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sum",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_int(11)],),
|
||||
Column::new("b".to_string(), vec![Value::test_int(7)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
sum,
|
||||
test_sum
|
||||
);
|
||||
|
||||
// LazyMean command
|
||||
// Expands to a command definition for mean aggregation
|
||||
lazy_command!(
|
||||
LazyMean,
|
||||
"dfr mean",
|
||||
"Aggregates columns to their mean value",
|
||||
vec![Example {
|
||||
description: "Mean value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr mean",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
||||
Column::new("b".to_string(), vec![Value::test_float(2.0)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
mean,
|
||||
test_mean
|
||||
);
|
||||
|
||||
// LazyMedian command
|
||||
// Expands to a command definition for median aggregation
|
||||
lazy_command!(
|
||||
@ -266,49 +178,3 @@ lazy_command!(
|
||||
median,
|
||||
test_median
|
||||
);
|
||||
|
||||
// LazyStd command
|
||||
// Expands to a command definition for std aggregation
|
||||
lazy_command!(
|
||||
LazyStd,
|
||||
"dfr std",
|
||||
"Aggregates columns to their std value",
|
||||
vec![Example {
|
||||
description: "Std value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr std",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_float(2.0)],),
|
||||
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
std,
|
||||
test_std,
|
||||
1
|
||||
);
|
||||
|
||||
// LazyVar command
|
||||
// Expands to a command definition for var aggregation
|
||||
lazy_command!(
|
||||
LazyVar,
|
||||
"dfr var",
|
||||
"Aggregates columns to their var value",
|
||||
vec![Example {
|
||||
description: "Var value from columns in a dataframe",
|
||||
example: "[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr var",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![
|
||||
Column::new("a".to_string(), vec![Value::test_float(4.0)],),
|
||||
Column::new("b".to_string(), vec![Value::test_float(0.0)],),
|
||||
])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},],
|
||||
var,
|
||||
test_var,
|
||||
1
|
||||
);
|
||||
|
@ -1,9 +1,11 @@
|
||||
pub mod aggregate;
|
||||
mod collect;
|
||||
mod explode;
|
||||
mod fetch;
|
||||
mod fill_nan;
|
||||
mod fill_null;
|
||||
mod filter;
|
||||
mod flatten;
|
||||
pub mod groupby;
|
||||
mod join;
|
||||
mod macro_commands;
|
||||
@ -27,6 +29,8 @@ use crate::dataframe::lazy::quantile::LazyQuantile;
|
||||
pub(crate) use crate::dataframe::lazy::select::LazySelect;
|
||||
use crate::dataframe::lazy::sort_by_expr::LazySortBy;
|
||||
pub use crate::dataframe::lazy::to_lazy::ToLazyFrame;
|
||||
pub use explode::LazyExplode;
|
||||
pub use flatten::LazyFlatten;
|
||||
|
||||
pub fn add_lazy_decls(working_set: &mut StateWorkingSet) {
|
||||
macro_rules! bind_command {
|
||||
@ -49,17 +53,13 @@ pub fn add_lazy_decls(working_set: &mut StateWorkingSet) {
|
||||
LazyFilter,
|
||||
LazyJoin,
|
||||
LazyQuantile,
|
||||
LazyMax,
|
||||
LazyMin,
|
||||
LazySum,
|
||||
LazyMean,
|
||||
LazyMedian,
|
||||
LazyStd,
|
||||
LazyVar,
|
||||
LazyReverse,
|
||||
LazySelect,
|
||||
LazySortBy,
|
||||
ToLazyFrame,
|
||||
ToLazyGroupBy
|
||||
ToLazyGroupBy,
|
||||
LazyExplode,
|
||||
LazyFlatten
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::super::super::values::{Column, NuDataFrame};
|
||||
use super::super::super::values::{Column, NuDataFrame, NuExpression};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
@ -20,33 +20,46 @@ impl Command for IsNotNull {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_type(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create mask where values are not null",
|
||||
example: r#"let s = ([5 6 0 8] | dfr into-df);
|
||||
vec![
|
||||
Example {
|
||||
description: "Create mask where values are not null",
|
||||
example: r#"let s = ([5 6 0 8] | dfr into-df);
|
||||
let res = ($s / $s);
|
||||
$res | dfr is-not-null"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"is_not_null".to_string(),
|
||||
vec![
|
||||
Value::test_bool(true),
|
||||
Value::test_bool(true),
|
||||
Value::test_bool(false),
|
||||
Value::test_bool(true),
|
||||
],
|
||||
)])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
}]
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"is_not_null".to_string(),
|
||||
vec![
|
||||
Value::test_bool(true),
|
||||
Value::test_bool(true),
|
||||
Value::test_bool(false),
|
||||
Value::test_bool(true),
|
||||
],
|
||||
)])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a is not null expression from a column",
|
||||
example: "dfr col a | dfr is-not-null",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -56,8 +69,19 @@ impl Command for IsNotNull {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
command(engine_state, stack, call, df)
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let df = NuDataFrame::try_from_value(value)?;
|
||||
command(engine_state, stack, call, df)
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().is_not_null().into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,11 +100,24 @@ fn command(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::super::test_dataframe::test_dataframe;
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
use crate::dataframe::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_dataframe(vec![Box::new(IsNotNull {})])
|
||||
fn test_examples_dataframe() {
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new(IsNotNull {})]);
|
||||
test_dataframe_example(&mut engine_state, &IsNotNull.examples()[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_expression() {
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new(IsNotNull {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &IsNotNull.examples()[1]);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::super::super::values::{Column, NuDataFrame};
|
||||
use super::super::super::values::{Column, NuDataFrame, NuExpression};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
@ -20,33 +20,46 @@ impl Command for IsNull {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_type(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Create mask where values are null",
|
||||
example: r#"let s = ([5 6 0 8] | dfr into-df);
|
||||
vec![
|
||||
Example {
|
||||
description: "Create mask where values are null",
|
||||
example: r#"let s = ([5 6 0 8] | dfr into-df);
|
||||
let res = ($s / $s);
|
||||
$res | dfr is-null"#,
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"is_null".to_string(),
|
||||
vec![
|
||||
Value::test_bool(false),
|
||||
Value::test_bool(false),
|
||||
Value::test_bool(true),
|
||||
Value::test_bool(false),
|
||||
],
|
||||
)])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
}]
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"is_null".to_string(),
|
||||
vec![
|
||||
Value::test_bool(false),
|
||||
Value::test_bool(false),
|
||||
Value::test_bool(true),
|
||||
Value::test_bool(false),
|
||||
],
|
||||
)])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a is null expression from a column",
|
||||
example: "dfr col a | dfr is-null",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -56,8 +69,19 @@ impl Command for IsNull {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
command(engine_state, stack, call, df)
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let df = NuDataFrame::try_from_value(value)?;
|
||||
command(engine_state, stack, call, df)
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().is_null().into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,11 +100,24 @@ fn command(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::super::test_dataframe::test_dataframe;
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
use crate::dataframe::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_dataframe(vec![Box::new(IsNull {})])
|
||||
fn test_examples_dataframe() {
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new(IsNull {})]);
|
||||
test_dataframe_example(&mut engine_state, &IsNull.examples()[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_expression() {
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new(IsNull {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &IsNull.examples()[1]);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use super::super::values::{Column, NuDataFrame};
|
||||
use super::super::values::{Column, NuDataFrame, NuExpression};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
@ -19,26 +19,39 @@ impl Command for NUnique {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build(self.name())
|
||||
.input_output_type(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
)
|
||||
.input_output_types(vec![
|
||||
(
|
||||
Type::Custom("expression".into()),
|
||||
Type::Custom("expression".into()),
|
||||
),
|
||||
(
|
||||
Type::Custom("dataframe".into()),
|
||||
Type::Custom("dataframe".into()),
|
||||
),
|
||||
])
|
||||
.category(Category::Custom("dataframe".into()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Counts unique values",
|
||||
example: "[1 1 2 2 3 3 4] | dfr into-df | dfr n-unique",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"count_unique".to_string(),
|
||||
vec![Value::test_int(4)],
|
||||
)])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
}]
|
||||
vec![
|
||||
Example {
|
||||
description: "Counts unique values",
|
||||
example: "[1 1 2 2 3 3 4] | dfr into-df | dfr n-unique",
|
||||
result: Some(
|
||||
NuDataFrame::try_from_columns(vec![Column::new(
|
||||
"count_unique".to_string(),
|
||||
vec![Value::test_int(4)],
|
||||
)])
|
||||
.expect("simple df for test should not fail")
|
||||
.into_value(Span::test_data()),
|
||||
),
|
||||
},
|
||||
Example {
|
||||
description: "Creates a is n-unique expression from a column",
|
||||
example: "dfr col a | dfr n-unique",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
@ -48,8 +61,19 @@ impl Command for NUnique {
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let df = NuDataFrame::try_from_pipeline(input, call.head)?;
|
||||
command(engine_state, stack, call, df)
|
||||
let value = input.into_value(call.head);
|
||||
if NuDataFrame::can_downcast(&value) {
|
||||
let df = NuDataFrame::try_from_value(value)?;
|
||||
command(engine_state, stack, call, df)
|
||||
} else {
|
||||
let expr = NuExpression::try_from_value(value)?;
|
||||
let expr: NuExpression = expr.into_polars().n_unique().into();
|
||||
|
||||
Ok(PipelineData::Value(
|
||||
NuExpression::into_value(expr, call.head),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,11 +101,24 @@ fn command(
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::super::super::test_dataframe::test_dataframe;
|
||||
use super::super::super::test_dataframe::{build_test_engine_state, test_dataframe_example};
|
||||
use super::*;
|
||||
use crate::dataframe::lazy::aggregate::LazyAggregate;
|
||||
use crate::dataframe::lazy::groupby::ToLazyGroupBy;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
test_dataframe(vec![Box::new(NUnique {})])
|
||||
fn test_examples_dataframe() {
|
||||
let mut engine_state = build_test_engine_state(vec![Box::new(NUnique {})]);
|
||||
test_dataframe_example(&mut engine_state, &NUnique.examples()[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_expression() {
|
||||
let mut engine_state = build_test_engine_state(vec![
|
||||
Box::new(NUnique {}),
|
||||
Box::new(LazyAggregate {}),
|
||||
Box::new(ToLazyGroupBy {}),
|
||||
]);
|
||||
test_dataframe_example(&mut engine_state, &NUnique.examples()[1]);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_protocol::{
|
||||
engine::{Command, EngineState, Stack, StateWorkingSet},
|
||||
PipelineData, Span,
|
||||
Example, PipelineData, Span,
|
||||
};
|
||||
|
||||
use super::eager::ToDataFrame;
|
||||
@ -17,6 +17,14 @@ pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
|
||||
|
||||
// The first element in the cmds vector must be the one tested
|
||||
let examples = cmds[0].examples();
|
||||
let mut engine_state = build_test_engine_state(cmds.clone());
|
||||
|
||||
for example in examples {
|
||||
test_dataframe_example(&mut engine_state, &example);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_test_engine_state(cmds: Vec<Box<dyn Command + 'static>>) -> Box<EngineState> {
|
||||
let mut engine_state = Box::new(EngineState::new());
|
||||
|
||||
let delta = {
|
||||
@ -41,54 +49,55 @@ pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
|
||||
.merge_delta(delta)
|
||||
.expect("Error merging delta");
|
||||
|
||||
for example in examples {
|
||||
// Skip tests that don't have results to compare to
|
||||
if example.result.is_none() {
|
||||
continue;
|
||||
engine_state
|
||||
}
|
||||
|
||||
pub fn test_dataframe_example(engine_state: &mut Box<EngineState>, example: &Example) {
|
||||
// Skip tests that don't have results to compare to
|
||||
if example.result.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let output = parse(&mut working_set, None, example.example.as_bytes(), false);
|
||||
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
panic!("test parse error in `{}`: {:?}", example.example, err)
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
let (block, delta) = {
|
||||
let mut working_set = StateWorkingSet::new(&engine_state);
|
||||
let output = parse(&mut working_set, None, example.example.as_bytes(), false);
|
||||
(output, working_set.render())
|
||||
};
|
||||
|
||||
if let Some(err) = working_set.parse_errors.first() {
|
||||
panic!("test parse error in `{}`: {:?}", example.example, err)
|
||||
}
|
||||
engine_state
|
||||
.merge_delta(delta)
|
||||
.expect("Error merging delta");
|
||||
|
||||
(output, working_set.render())
|
||||
};
|
||||
let mut stack = Stack::new();
|
||||
|
||||
engine_state
|
||||
.merge_delta(delta)
|
||||
.expect("Error merging delta");
|
||||
let result = eval_block(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
PipelineData::empty(),
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", example.example, err))
|
||||
.into_value(Span::test_data());
|
||||
|
||||
let mut stack = Stack::new();
|
||||
println!("input: {}", example.example);
|
||||
println!("result: {result:?}");
|
||||
println!("done: {:?}", start.elapsed());
|
||||
|
||||
let result = eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
PipelineData::empty(),
|
||||
true,
|
||||
true,
|
||||
)
|
||||
.unwrap_or_else(|err| panic!("test eval error in `{}`: {:?}", example.example, err))
|
||||
.into_value(Span::test_data());
|
||||
|
||||
println!("input: {}", example.example);
|
||||
println!("result: {result:?}");
|
||||
println!("done: {:?}", start.elapsed());
|
||||
|
||||
// Note. Value implements PartialEq for Bool, Int, Float, String and Block
|
||||
// If the command you are testing requires to compare another case, then
|
||||
// you need to define its equality in the Value struct
|
||||
if let Some(expected) = example.result {
|
||||
if result != expected {
|
||||
panic!(
|
||||
"the example result is different to expected value: {result:?} != {expected:?}"
|
||||
)
|
||||
}
|
||||
// Note. Value implements PartialEq for Bool, Int, Float, String and Block
|
||||
// If the command you are testing requires to compare another case, then
|
||||
// you need to define its equality in the Value struct
|
||||
if let Some(expected) = example.result.clone() {
|
||||
if result != expected {
|
||||
panic!("the example result is different to expected value: {result:?} != {expected:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,4 @@
|
||||
#[cfg(feature = "dataframe")]
|
||||
pub mod dataframe;
|
||||
#[cfg(feature = "dataframe")]
|
||||
pub use dataframe::*;
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-extra"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-extra"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -13,21 +13,22 @@ version = "0.83.1"
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.83.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.83.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.83.1" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.83.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.83.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.84.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.84.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.84.0" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.84.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.84.0" }
|
||||
|
||||
# Potential dependencies for extras
|
||||
Inflector = "0.11"
|
||||
num-traits = "0.2"
|
||||
ahash = "0.8.3"
|
||||
nu-ansi-term = "0.49.0"
|
||||
fancy-regex = "0.11.0"
|
||||
rust-embed = "6.7.0"
|
||||
serde = "1.0.164"
|
||||
nu-pretty-hex = { version = "0.83.1", path = "../nu-pretty-hex" }
|
||||
nu-json = { version = "0.83.1", path = "../nu-json" }
|
||||
nu-pretty-hex = { version = "0.84.0", path = "../nu-pretty-hex" }
|
||||
nu-json = { version = "0.84.0", path = "../nu-json" }
|
||||
serde_urlencoded = "0.7.1"
|
||||
htmlescape = "0.3.1"
|
||||
|
||||
@ -36,6 +37,6 @@ extra = ["default"]
|
||||
default = []
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.1" }
|
||||
nu-command = { path = "../nu-command", version = "0.83.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.83.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.84.0" }
|
||||
nu-command = { path = "../nu-command", version = "0.84.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.84.0" }
|
||||
|
@ -1,5 +1,4 @@
|
||||
mod bits;
|
||||
mod bytes;
|
||||
mod conversions;
|
||||
mod filters;
|
||||
mod formats;
|
||||
@ -7,19 +6,6 @@ mod math;
|
||||
mod platform;
|
||||
mod strings;
|
||||
|
||||
pub use bytes::Bytes;
|
||||
pub use bytes::BytesAdd;
|
||||
pub use bytes::BytesAt;
|
||||
pub use bytes::BytesBuild;
|
||||
pub use bytes::BytesCollect;
|
||||
pub use bytes::BytesEndsWith;
|
||||
pub use bytes::BytesIndexOf;
|
||||
pub use bytes::BytesLen;
|
||||
pub use bytes::BytesRemove;
|
||||
pub use bytes::BytesReplace;
|
||||
pub use bytes::BytesReverse;
|
||||
pub use bytes::BytesStartsWith;
|
||||
|
||||
pub use bits::Bits;
|
||||
pub use bits::BitsAnd;
|
||||
pub use bits::BitsInto;
|
||||
@ -85,9 +71,15 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
|
||||
bind_command!(
|
||||
strings::format::Format,
|
||||
strings::format::FileSize,
|
||||
strings::encode_decode::EncodeHex,
|
||||
strings::encode_decode::DecodeHex
|
||||
strings::encode_decode::DecodeHex,
|
||||
strings::str_::case::Str,
|
||||
strings::str_::case::StrCamelCase,
|
||||
strings::str_::case::StrKebabCase,
|
||||
strings::str_::case::StrPascalCase,
|
||||
strings::str_::case::StrScreamingSnakeCase,
|
||||
strings::str_::case::StrSnakeCase,
|
||||
strings::str_::case::StrTitleCase
|
||||
);
|
||||
|
||||
bind_command!(formats::ToHtml, formats::FromUrl);
|
||||
@ -105,22 +97,6 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState {
|
||||
BitsXor
|
||||
}
|
||||
|
||||
// Bytes
|
||||
bind_command! {
|
||||
Bytes,
|
||||
BytesLen,
|
||||
BytesStartsWith,
|
||||
BytesEndsWith,
|
||||
BytesReverse,
|
||||
BytesReplace,
|
||||
BytesAdd,
|
||||
BytesAt,
|
||||
BytesIndexOf,
|
||||
BytesCollect,
|
||||
BytesRemove,
|
||||
BytesBuild
|
||||
}
|
||||
|
||||
// Math
|
||||
bind_command! {
|
||||
MathArcSin,
|
||||
|
@ -129,13 +129,13 @@ fn operate(
|
||||
input.map(
|
||||
move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, fgs_hex, fge_hex, bgs_hex, bge_hex, &head)
|
||||
action(&v, fgs_hex, fge_hex, bgs_hex, bge_hex, head)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &column_paths {
|
||||
let r = ret.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| action(old, fgs_hex, fge_hex, bgs_hex, bge_hex, &head)),
|
||||
Box::new(move |old| action(old, fgs_hex, fge_hex, bgs_hex, bge_hex, head)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error {
|
||||
@ -156,10 +156,11 @@ fn action(
|
||||
fg_end: Option<Rgb>,
|
||||
bg_start: Option<Rgb>,
|
||||
bg_end: Option<Rgb>,
|
||||
command_span: &Span,
|
||||
command_span: Span,
|
||||
) -> Value {
|
||||
match input {
|
||||
Value::String { val, span } => {
|
||||
let span = *span;
|
||||
match (fg_start, fg_end, bg_start, bg_end) {
|
||||
(None, None, None, None) => {
|
||||
// Error - no colors
|
||||
@ -167,7 +168,7 @@ fn action(
|
||||
error: Box::new(ShellError::MissingParameter {
|
||||
param_name:
|
||||
"please supply foreground and/or background color parameters".into(),
|
||||
span: *command_span,
|
||||
span: command_span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -176,27 +177,27 @@ fn action(
|
||||
let bg_start = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Background);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(None, None, Some(bg_start), None) => {
|
||||
// Error - missing bg_end, so assume black
|
||||
let bg_end = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Background);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(None, None, Some(bg_start), Some(bg_end)) => {
|
||||
// Background Only
|
||||
let gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Background);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(None, Some(fg_end), None, None) => {
|
||||
// Error - missing fg_start, so assume black
|
||||
let fg_start = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(fg_start, fg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Foreground);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(None, Some(fg_end), None, Some(bg_end)) => {
|
||||
// missing fg_start and bg_start, so assume black
|
||||
@ -205,7 +206,7 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(None, Some(fg_end), Some(bg_start), None) => {
|
||||
// Error - missing fg_start and bg_end
|
||||
@ -214,7 +215,7 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(None, Some(fg_end), Some(bg_start), Some(bg_end)) => {
|
||||
// Error - missing fg_start, so assume black
|
||||
@ -222,14 +223,14 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), None, None, None) => {
|
||||
// Error - missing fg_end, so assume black
|
||||
let fg_end = Rgb::new(0, 0, 0);
|
||||
let gradient = Gradient::new(fg_start, fg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Foreground);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), None, None, Some(bg_end)) => {
|
||||
// Error - missing fg_end, bg_start, so assume black
|
||||
@ -238,7 +239,7 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), None, Some(bg_start), None) => {
|
||||
// Error - missing fg_end, bg_end, so assume black
|
||||
@ -247,7 +248,7 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), None, Some(bg_start), Some(bg_end)) => {
|
||||
// Error - missing fg_end, so assume black
|
||||
@ -255,13 +256,13 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), None, None) => {
|
||||
// Foreground Only
|
||||
let gradient = Gradient::new(fg_start, fg_end);
|
||||
let gradient_string = gradient.build(val, TargetGround::Foreground);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), None, Some(bg_end)) => {
|
||||
// Error - missing bg_start, so assume black
|
||||
@ -269,7 +270,7 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), Some(bg_start), None) => {
|
||||
// Error - missing bg_end, so assume black
|
||||
@ -277,14 +278,14 @@ fn action(
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
(Some(fg_start), Some(fg_end), Some(bg_start), Some(bg_end)) => {
|
||||
// Foreground and Background Gradient
|
||||
let fg_gradient = Gradient::new(fg_start, fg_end);
|
||||
let bg_gradient = Gradient::new(bg_start, bg_end);
|
||||
let gradient_string = build_all_gradient_text(val, fg_gradient, bg_gradient);
|
||||
Value::string(gradient_string, *span)
|
||||
Value::string(gradient_string, span)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -294,7 +295,7 @@ fn action(
|
||||
Value::Error {
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: got,
|
||||
span: other.span().unwrap_or(*command_span),
|
||||
span: other.span().unwrap_or(command_span),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -326,7 +327,7 @@ mod tests {
|
||||
Some(fg_end),
|
||||
None,
|
||||
None,
|
||||
&Span::test_data(),
|
||||
Span::test_data(),
|
||||
);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
@ -98,12 +98,12 @@ fn operate(
|
||||
|
||||
if column_paths.is_empty() {
|
||||
input.map(
|
||||
move |v| process_value(&v, &text, &command_span),
|
||||
move |v| process_value(&v, &text, command_span),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
} else {
|
||||
input.map(
|
||||
move |v| process_each_path(v, &column_paths, &text, &command_span),
|
||||
move |v| process_each_path(v, &column_paths, &text, command_span),
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
@ -113,7 +113,7 @@ fn process_each_path(
|
||||
mut value: Value,
|
||||
column_paths: &Vec<CellPath>,
|
||||
text: &Option<String>,
|
||||
command_span: &Span,
|
||||
command_span: Span,
|
||||
) -> Value {
|
||||
for path in column_paths {
|
||||
let ret = value.update_cell_path(
|
||||
@ -129,7 +129,7 @@ fn process_each_path(
|
||||
value
|
||||
}
|
||||
|
||||
fn process_value(value: &Value, text: &Option<String>, command_span: &Span) -> Value {
|
||||
fn process_value(value: &Value, text: &Option<String>, command_span: Span) -> Value {
|
||||
match value {
|
||||
Value::String { val, span } => {
|
||||
let text = text.as_deref().unwrap_or(val.as_str());
|
||||
@ -142,7 +142,7 @@ fn process_value(value: &Value, text: &Option<String>, command_span: &Span) -> V
|
||||
Value::Error {
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: got,
|
||||
span: other.span().unwrap_or(*command_span),
|
||||
span: other.span().unwrap_or(command_span),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::vec;
|
||||
|
||||
use nu_engine::{eval_expression, CallExt};
|
||||
use nu_parser::parse_expression;
|
||||
use nu_protocol::ast::{Call, PathMember};
|
||||
@ -17,15 +19,16 @@ impl Command for Format {
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("format")
|
||||
.input_output_types(vec![(
|
||||
Type::Table(vec![]),
|
||||
Type::List(Box::new(Type::String)),
|
||||
)])
|
||||
.input_output_types(vec![
|
||||
(Type::Table(vec![]), Type::List(Box::new(Type::String))),
|
||||
(Type::Record(vec![]), Type::Any),
|
||||
])
|
||||
.required(
|
||||
"pattern",
|
||||
SyntaxShape::String,
|
||||
"the pattern to output. e.g.) \"{foo}: {bar}\"",
|
||||
)
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Strings)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,3 @@
|
||||
mod command;
|
||||
mod filesize;
|
||||
|
||||
pub(crate) use command::Format;
|
||||
pub(crate) use filesize::FileSize;
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub(crate) mod encode_decode;
|
||||
pub(crate) mod format;
|
||||
pub(crate) mod str_;
|
||||
|
@ -5,7 +5,7 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::operate;
|
||||
use super::operate;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
@ -5,7 +5,7 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::operate;
|
||||
use super::operate;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
74
crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs
Normal file
74
crates/nu-cmd-extra/src/extra/strings/str_/case/mod.rs
Normal file
@ -0,0 +1,74 @@
|
||||
mod camel_case;
|
||||
mod kebab_case;
|
||||
mod pascal_case;
|
||||
mod screaming_snake_case;
|
||||
mod snake_case;
|
||||
mod str_;
|
||||
mod title_case;
|
||||
|
||||
pub use camel_case::SubCommand as StrCamelCase;
|
||||
pub use kebab_case::SubCommand as StrKebabCase;
|
||||
pub use pascal_case::SubCommand as StrPascalCase;
|
||||
pub use screaming_snake_case::SubCommand as StrScreamingSnakeCase;
|
||||
pub use snake_case::SubCommand as StrSnakeCase;
|
||||
pub use str_::Str;
|
||||
pub use title_case::SubCommand as StrTitleCase;
|
||||
|
||||
use nu_engine::CallExt;
|
||||
|
||||
use nu_cmd_base::input_handler::{operate as general_operate, CmdArgument};
|
||||
use nu_protocol::ast::{Call, CellPath};
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{PipelineData, ShellError, Span, Value};
|
||||
|
||||
struct Arguments<F: Fn(&str) -> String + Send + Sync + 'static> {
|
||||
case_operation: &'static F,
|
||||
cell_paths: Option<Vec<CellPath>>,
|
||||
}
|
||||
|
||||
impl<F: Fn(&str) -> String + Send + Sync + 'static> CmdArgument for Arguments<F> {
|
||||
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||
self.cell_paths.take()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn operate<F>(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
case_operation: &'static F,
|
||||
) -> Result<PipelineData, ShellError>
|
||||
where
|
||||
F: Fn(&str) -> String + Send + Sync + 'static,
|
||||
{
|
||||
let cell_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||
let args = Arguments {
|
||||
case_operation,
|
||||
cell_paths,
|
||||
};
|
||||
general_operate(action, args, input, call.head, engine_state.ctrlc.clone())
|
||||
}
|
||||
|
||||
fn action<F>(input: &Value, args: &Arguments<F>, head: Span) -> Value
|
||||
where
|
||||
F: Fn(&str) -> String + Send + Sync + 'static,
|
||||
{
|
||||
let case_operation = args.case_operation;
|
||||
match input {
|
||||
Value::String { val, .. } => Value::String {
|
||||
val: case_operation(val),
|
||||
span: head,
|
||||
},
|
||||
Value::Error { .. } => input.clone(),
|
||||
_ => Value::Error {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string".into(),
|
||||
wrong_type: input.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: input.expect_span(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::operate;
|
||||
use super::operate;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
@ -5,7 +5,8 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::operate;
|
||||
use super::operate;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
@ -5,7 +5,7 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::operate;
|
||||
use super::operate;
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
49
crates/nu-cmd-extra/src/extra/strings/str_/case/str_.rs
Normal file
49
crates/nu-cmd-extra/src/extra/strings/str_/case/str_.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use nu_engine::get_full_help;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Str;
|
||||
|
||||
impl Command for Str {
|
||||
fn name(&self) -> &str {
|
||||
"str"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("str")
|
||||
.category(Category::Strings)
|
||||
.input_output_types(vec![(Type::Nothing, Type::String)])
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Various commands for working with string data."
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(Value::String {
|
||||
val: get_full_help(
|
||||
&Str.signature(),
|
||||
&Str.examples(),
|
||||
engine_state,
|
||||
stack,
|
||||
self.is_parser_keyword(),
|
||||
),
|
||||
span: call.head,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
use crate::operate;
|
||||
use super::operate;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
3
crates/nu-cmd-extra/src/extra/strings/str_/mod.rs
Normal file
3
crates/nu-cmd-extra/src/extra/strings/str_/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(crate) mod case;
|
||||
|
||||
pub use case::*;
|
@ -6,16 +6,16 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-cmd-lang"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-cmd-lang"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-engine = { path = "../nu-engine", version = "0.83.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.83.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.83.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.83.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.84.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.84.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.84.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.84.0" }
|
||||
nu-ansi-term = "0.49.0"
|
||||
|
||||
fancy-regex = "0.11"
|
||||
|
66
crates/nu-cmd-lang/src/core_commands/export_const.rs
Normal file
66
crates/nu-cmd-lang/src/core_commands/export_const.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportConst;
|
||||
|
||||
impl Command for ExportConst {
|
||||
fn name(&self) -> &str {
|
||||
"export const"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Use parse-time constant from a module and export them from this module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export const")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("const_name", SyntaxShape::VarWithOptType, "constant name")
|
||||
.required(
|
||||
"initial_value",
|
||||
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::MathExpression)),
|
||||
"equals sign followed by constant value",
|
||||
)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Re-export a command from another module",
|
||||
example: r#"module spam { export const foo = 3; }
|
||||
module eggs { export use spam foo }
|
||||
use eggs foo
|
||||
foo
|
||||
"#,
|
||||
result: Some(Value::test_int(3)),
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["reexport", "import", "module"]
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExportExternWrapped;
|
||||
|
||||
impl Command for ExportExternWrapped {
|
||||
fn name(&self) -> &str {
|
||||
"export extern-wrapped"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define an extern with a custom code block and export it from a module."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("export extern-wrapped")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required("body", SyntaxShape::Block, "wrapper code block")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
r#"This command is a parser keyword. For details, check:
|
||||
https://www.nushell.sh/book/thinking_in_nu.html"#
|
||||
}
|
||||
|
||||
fn is_parser_keyword(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
_engine_state: &EngineState,
|
||||
_stack: &mut Stack,
|
||||
_call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
Ok(PipelineData::empty())
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Export the signature for an external command",
|
||||
example: r#"export extern-wrapped my-echo [...rest] { echo $rest }"#,
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["signature", "module", "declare"]
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ExternWrapped;
|
||||
@ -11,15 +13,16 @@ impl Command for ExternWrapped {
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Define a signature for an external command."
|
||||
"Define a signature for an external command with a custom code block."
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("extern-wrapped")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.allow_variants_without_examples(true)
|
||||
.required("def_name", SyntaxShape::String, "definition name")
|
||||
.required("params", SyntaxShape::Signature, "parameters")
|
||||
.required("body", SyntaxShape::Block, "wrapper function block")
|
||||
.required("body", SyntaxShape::Block, "wrapper code block")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
@ -44,9 +47,19 @@ impl Command for ExternWrapped {
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Write a signature for an external command",
|
||||
example: r#"extern-wrapped echo [text: string] { print $text }"#,
|
||||
result: None,
|
||||
description: "Define a custom wrapper for an external command",
|
||||
example: r#"extern-wrapped my-echo [...rest] { echo $rest }; my-echo spam"#,
|
||||
result: Some(Value::test_list(vec![Value::test_string("spam")])),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use super::ExternWrapped;
|
||||
use crate::test_examples;
|
||||
test_examples(ExternWrapped {})
|
||||
}
|
||||
}
|
||||
|
@ -121,12 +121,11 @@ impl Command for Match {
|
||||
},
|
||||
Example {
|
||||
description: "Match with a guard",
|
||||
example: "
|
||||
match [1 2 3] {
|
||||
[$x, ..$y] if $x == 1 => { 'good list' },
|
||||
_ => { 'not a very good list' }
|
||||
}
|
||||
",
|
||||
example: "match [1 2 3] {
|
||||
[$x, ..$y] if $x == 1 => { 'good list' },
|
||||
_ => { 'not a very good list' }
|
||||
}
|
||||
",
|
||||
result: Some(Value::test_string("good list")),
|
||||
},
|
||||
]
|
||||
|
@ -11,9 +11,11 @@ mod echo;
|
||||
mod error_make;
|
||||
mod export;
|
||||
mod export_alias;
|
||||
mod export_const;
|
||||
mod export_def;
|
||||
mod export_def_env;
|
||||
mod export_extern;
|
||||
mod export_extern_wrapped;
|
||||
mod export_module;
|
||||
mod export_use;
|
||||
mod extern_;
|
||||
@ -50,9 +52,11 @@ pub use echo::Echo;
|
||||
pub use error_make::ErrorMake;
|
||||
pub use export::ExportCommand;
|
||||
pub use export_alias::ExportAlias;
|
||||
pub use export_const::ExportConst;
|
||||
pub use export_def::ExportDef;
|
||||
pub use export_def_env::ExportDefEnv;
|
||||
pub use export_extern::ExportExtern;
|
||||
pub use export_extern_wrapped::ExportExternWrapped;
|
||||
pub use export_module::ExportModule;
|
||||
pub use export_use::ExportUse;
|
||||
pub use extern_::Extern;
|
||||
|
@ -68,7 +68,7 @@ impl Command for OverlayUse {
|
||||
|
||||
let maybe_origin_module_id =
|
||||
if let Some(overlay_expr) = call.get_parser_info("overlay_expr") {
|
||||
if let Expr::Overlay(module_id) = overlay_expr.expr {
|
||||
if let Expr::Overlay(module_id) = &overlay_expr.expr {
|
||||
module_id
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned {
|
||||
@ -106,11 +106,11 @@ impl Command for OverlayUse {
|
||||
};
|
||||
|
||||
if let Some(module_id) = maybe_origin_module_id {
|
||||
// Add environment variables only if:
|
||||
// Add environment variables only if (determined by parser):
|
||||
// a) adding a new overlay
|
||||
// b) refreshing an active overlay (the origin module changed)
|
||||
|
||||
let module = engine_state.get_module(module_id);
|
||||
let module = engine_state.get_module(*module_id);
|
||||
|
||||
// Evaluate the export-env block (if any) and keep its environment
|
||||
if let Some(block_id) = module.env_block {
|
||||
@ -122,7 +122,7 @@ impl Command for OverlayUse {
|
||||
)?;
|
||||
|
||||
let block = engine_state.get_block(block_id);
|
||||
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
||||
let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
|
||||
|
||||
if let Some(path) = &maybe_path {
|
||||
// Set the currently evaluated directory, if the argument is a valid path
|
||||
|
@ -19,7 +19,7 @@ impl Command for Return {
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("return")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Nothing)])
|
||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||
.optional("return_value", SyntaxShape::Any, "optional value to return")
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ impl Command for ScopeAliases {
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_all();
|
||||
scope_data.populate_decls();
|
||||
|
||||
Ok(scope_data.collect_aliases(span).into_pipeline_data(ctrlc))
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ impl Command for ScopeCommands {
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_all();
|
||||
scope_data.populate_decls();
|
||||
|
||||
Ok(scope_data.collect_commands(span).into_pipeline_data(ctrlc))
|
||||
}
|
||||
|
@ -31,8 +31,7 @@ impl Command for ScopeEngineStats {
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_all();
|
||||
let scope_data = ScopeData::new(engine_state, stack);
|
||||
|
||||
Ok(scope_data.collect_engine_state(span).into_pipeline_data())
|
||||
}
|
||||
|
62
crates/nu-cmd-lang/src/core_commands/scope/externs.rs
Normal file
62
crates/nu-cmd-lang/src/core_commands/scope/externs.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use nu_engine::scope::ScopeData;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Type,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ScopeExterns;
|
||||
|
||||
impl Command for ScopeExterns {
|
||||
fn name(&self) -> &str {
|
||||
"scope externs"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("scope externs")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Any)])
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Filters)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output info on the known externals in the current scope."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let span = call.head;
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_decls();
|
||||
|
||||
Ok(scope_data.collect_externs(span).into_pipeline_data(ctrlc))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![Example {
|
||||
description: "Show the known externals in the current scope",
|
||||
example: "scope externs",
|
||||
result: None,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(ScopeExterns {})
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ mod aliases;
|
||||
mod command;
|
||||
mod commands;
|
||||
mod engine_stats;
|
||||
mod externs;
|
||||
mod modules;
|
||||
mod variables;
|
||||
|
||||
@ -9,5 +10,6 @@ pub use aliases::*;
|
||||
pub use command::*;
|
||||
pub use commands::*;
|
||||
pub use engine_stats::*;
|
||||
pub use externs::*;
|
||||
pub use modules::*;
|
||||
pub use variables::*;
|
||||
|
@ -35,7 +35,7 @@ impl Command for ScopeVariables {
|
||||
let ctrlc = engine_state.ctrlc.clone();
|
||||
|
||||
let mut scope_data = ScopeData::new(engine_state, stack);
|
||||
scope_data.populate_all();
|
||||
scope_data.populate_vars();
|
||||
|
||||
Ok(scope_data.collect_vars(span).into_pipeline_data(ctrlc))
|
||||
}
|
||||
|
@ -63,15 +63,30 @@ This command is a parser keyword. For details, check:
|
||||
};
|
||||
|
||||
if let Some(module_id) = import_pattern.head.id {
|
||||
let module = engine_state.get_module(module_id);
|
||||
// Add constants
|
||||
for var_id in &import_pattern.constants {
|
||||
let var = engine_state.get_var(*var_id);
|
||||
|
||||
if let Some(constval) = &var.const_val {
|
||||
caller_stack.add_var(*var_id, constval.clone());
|
||||
} else {
|
||||
return Err(ShellError::NushellFailedSpanned {
|
||||
msg: "Missing Constant".to_string(),
|
||||
label: "constant not added by the parser".to_string(),
|
||||
span: var.declaration_span,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the export-env block if there is one
|
||||
let module = engine_state.get_module(module_id);
|
||||
|
||||
if let Some(block_id) = module.env_block {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
// See if the module is a file
|
||||
let module_arg_str = String::from_utf8_lossy(
|
||||
engine_state.get_span_contents(&import_pattern.head.span),
|
||||
engine_state.get_span_contents(import_pattern.head.span),
|
||||
);
|
||||
|
||||
let maybe_file_path = find_in_dirs_env(
|
||||
@ -84,7 +99,7 @@ This command is a parser keyword. For details, check:
|
||||
.as_ref()
|
||||
.and_then(|path| path.parent().map(|p| p.to_path_buf()));
|
||||
|
||||
let mut callee_stack = caller_stack.gather_captures(&block.captures);
|
||||
let mut callee_stack = caller_stack.gather_captures(engine_state, &block.captures);
|
||||
|
||||
// If so, set the currently evaluated directory (file-relative PWD)
|
||||
if let Some(parent) = maybe_parent {
|
||||
|
@ -1,6 +1,8 @@
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Type, Value,
|
||||
};
|
||||
use shadow_rs::shadow;
|
||||
|
||||
shadow!(build);
|
||||
@ -17,6 +19,7 @@ impl Command for Version {
|
||||
Signature::build("version")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Record(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
.category(Category::Core)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -29,9 +29,11 @@ pub fn create_default_context() -> EngineState {
|
||||
ErrorMake,
|
||||
ExportAlias,
|
||||
ExportCommand,
|
||||
ExportConst,
|
||||
ExportDef,
|
||||
ExportDefEnv,
|
||||
ExportExtern,
|
||||
ExportExternWrapped,
|
||||
ExportUse,
|
||||
ExportModule,
|
||||
Extern,
|
||||
@ -57,6 +59,7 @@ pub fn create_default_context() -> EngineState {
|
||||
ScopeAliases,
|
||||
ScopeCommands,
|
||||
ScopeEngineStats,
|
||||
ScopeExterns,
|
||||
ScopeModules,
|
||||
ScopeVariables,
|
||||
Try,
|
||||
|
@ -5,19 +5,19 @@ repository = "https://github.com/nushell/nushell/tree/main/crates/nu-color-confi
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-color-config"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
[lib]
|
||||
bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.83.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.84.0" }
|
||||
nu-ansi-term = "0.49.0"
|
||||
nu-utils = { path = "../nu-utils", version = "0.83.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.83.1" }
|
||||
nu-json = { path="../nu-json", version = "0.83.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.84.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.84.0" }
|
||||
nu-json = { path="../nu-json", version = "0.84.0" }
|
||||
|
||||
serde = { version="1.0", features=["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nu-test-support = { path="../nu-test-support", version = "0.83.1" }
|
||||
nu-test-support = { path="../nu-test-support", version = "0.84.0" }
|
||||
|
@ -143,13 +143,13 @@ impl<'a> StyleComputer<'a> {
|
||||
let mut map: StyleMapping = [
|
||||
("separator".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("leading_trailing_space_bg".to_string(), ComputableStyle::Static(Style::default().on(Color::Rgb(128, 128, 128)))),
|
||||
("header".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("empty".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("bool".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("header".to_string(), ComputableStyle::Static(Color::Green.bold())),
|
||||
("empty".to_string(), ComputableStyle::Static(Color::Blue.normal())),
|
||||
("bool".to_string(), ComputableStyle::Static(Color::LightCyan.normal())),
|
||||
("int".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("filesize".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("filesize".to_string(), ComputableStyle::Static(Color::Cyan.normal())),
|
||||
("duration".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("date".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("date".to_string(), ComputableStyle::Static(Color::Purple.normal())),
|
||||
("range".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("float".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
("string".to_string(), ComputableStyle::Static(Color::White.normal())),
|
||||
|
@ -5,7 +5,7 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
name = "nu-command"
|
||||
repository = "https://github.com/nushell/nushell/tree/main/crates/nu-command"
|
||||
version = "0.83.1"
|
||||
version = "0.84.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@ -14,21 +14,20 @@ bench = false
|
||||
|
||||
[dependencies]
|
||||
nu-ansi-term = "0.49.0"
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.83.1" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.83.1" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.83.1" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.83.1" }
|
||||
nu-json = { path = "../nu-json", version = "0.83.1" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.83.1" }
|
||||
nu-path = { path = "../nu-path", version = "0.83.1" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.83.1" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.83.1" }
|
||||
nu-system = { path = "../nu-system", version = "0.83.1" }
|
||||
nu-table = { path = "../nu-table", version = "0.83.1" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.83.1" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.83.1" }
|
||||
nu-cmd-base = { path = "../nu-cmd-base", version = "0.84.0" }
|
||||
nu-color-config = { path = "../nu-color-config", version = "0.84.0" }
|
||||
nu-engine = { path = "../nu-engine", version = "0.84.0" }
|
||||
nu-glob = { path = "../nu-glob", version = "0.84.0" }
|
||||
nu-json = { path = "../nu-json", version = "0.84.0" }
|
||||
nu-parser = { path = "../nu-parser", version = "0.84.0" }
|
||||
nu-path = { path = "../nu-path", version = "0.84.0" }
|
||||
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.84.0" }
|
||||
nu-protocol = { path = "../nu-protocol", version = "0.84.0" }
|
||||
nu-system = { path = "../nu-system", version = "0.84.0" }
|
||||
nu-table = { path = "../nu-table", version = "0.84.0" }
|
||||
nu-term-grid = { path = "../nu-term-grid", version = "0.84.0" }
|
||||
nu-utils = { path = "../nu-utils", version = "0.84.0" }
|
||||
|
||||
Inflector = "0.11"
|
||||
alphanumeric-sort = "1.5"
|
||||
base64 = "0.21"
|
||||
byteorder = "1.4"
|
||||
@ -70,7 +69,7 @@ pathdiff = "0.2"
|
||||
percent-encoding = "2.3"
|
||||
powierza-coefficient = "1.0"
|
||||
print-positions = "0.6"
|
||||
quick-xml = "0.29"
|
||||
quick-xml = "0.30"
|
||||
rand = "0.8"
|
||||
rayon = "1.7"
|
||||
regex = "1.7"
|
||||
@ -84,7 +83,7 @@ serde_yaml = "0.9"
|
||||
sha2 = "0.10"
|
||||
sqlparser = { version = "0.34", features = ["serde"], optional = true }
|
||||
sysinfo = "0.29"
|
||||
tabled = { version = "0.12.2", features = ["color"], default-features = false }
|
||||
tabled = { version = "0.14.0", features = ["color"], default-features = false }
|
||||
terminal_size = "0.2"
|
||||
titlecase = "2.0"
|
||||
toml = "0.7"
|
||||
@ -94,7 +93,7 @@ url = "2.2"
|
||||
uuid = { version = "1.3", features = ["v4"] }
|
||||
wax = { version = "0.5" }
|
||||
which = { version = "4.4", optional = true }
|
||||
bracoxide = "0.1.1"
|
||||
bracoxide = "0.1.2"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winreg = "0.50"
|
||||
@ -125,11 +124,11 @@ trash-support = ["trash"]
|
||||
which-support = ["which"]
|
||||
|
||||
[dev-dependencies]
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.83.1" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.83.1" }
|
||||
nu-cmd-lang = { path = "../nu-cmd-lang", version = "0.84.0" }
|
||||
nu-test-support = { path = "../nu-test-support", version = "0.84.0" }
|
||||
|
||||
dirs-next = "2.0"
|
||||
mockito = { version = "1.1", default-features = false }
|
||||
quickcheck = "1.0"
|
||||
quickcheck_macros = "1.0"
|
||||
rstest = { version = "0.17", default-features = false }
|
||||
rstest = { version = "0.18", default-features = false }
|
||||
|
5
crates/nu-command/src/charting/histogram.rs
Normal file → Executable file
5
crates/nu-command/src/charting/histogram.rs
Normal file → Executable file
@ -4,8 +4,8 @@ use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
|
||||
Type, Value,
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Type, Value,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::iter;
|
||||
@ -29,6 +29,7 @@ impl Command for Histogram {
|
||||
.optional("column-name", SyntaxShape::String, "column name to calc frequency, no need to provide if input is just a list")
|
||||
.optional("frequency-column-name", SyntaxShape::String, "histogram's frequency column, default to be frequency column output")
|
||||
.named("percentage-type", SyntaxShape::String, "percentage calculate method, can be 'normalize' or 'relative', in 'normalize', defaults to be 'normalize'", Some('t'))
|
||||
.category(Category::Chart)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
|
@ -87,7 +87,7 @@ impl Command for SubCommand {
|
||||
.named(
|
||||
"format",
|
||||
SyntaxShape::String,
|
||||
"Specify expected format of string input to parse to datetime. Use --list to see options",
|
||||
"Specify expected format of INPUT string to parse to datetime. Use --list to see options",
|
||||
Some('f'),
|
||||
)
|
||||
.switch(
|
||||
@ -236,6 +236,20 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
let timezone = &args.zone_options;
|
||||
let dateformat = &args.format_options;
|
||||
|
||||
// Let's try dtparse first
|
||||
if matches!(input, Value::String { .. }) && dateformat.is_none() {
|
||||
if let Ok(input_val) = input.as_spanned_string() {
|
||||
match parse_date_from_string(&input_val.item, input_val.span) {
|
||||
Ok(date) => {
|
||||
return Value::Date {
|
||||
val: date,
|
||||
span: input_val.span,
|
||||
}
|
||||
}
|
||||
Err(err) => err,
|
||||
};
|
||||
}
|
||||
}
|
||||
const HOUR: i32 = 60 * 60;
|
||||
|
||||
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
|
||||
@ -256,51 +270,72 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
}
|
||||
};
|
||||
|
||||
if let Ok(ts) = timestamp {
|
||||
macro_rules! match_datetime {
|
||||
($expr:expr) => {
|
||||
match $expr {
|
||||
dt => Value::Date {
|
||||
val: dt.into(),
|
||||
span: head,
|
||||
if dateformat.is_none() {
|
||||
if let Ok(ts) = timestamp {
|
||||
return match timezone {
|
||||
// note all these `.timestamp_nanos()` could overflow if we didn't check range in `<date> | into int`.
|
||||
|
||||
// default to UTC
|
||||
None => Value::Date {
|
||||
val: Utc.timestamp_nanos(ts).into(),
|
||||
span: head,
|
||||
},
|
||||
Some(Spanned { item, span }) => match item {
|
||||
Zone::Utc => {
|
||||
let dt = Utc.timestamp_nanos(ts);
|
||||
Value::Date {
|
||||
val: dt.into(),
|
||||
span: *span,
|
||||
}
|
||||
}
|
||||
Zone::Local => {
|
||||
let dt = Local.timestamp_nanos(ts);
|
||||
Value::Date {
|
||||
val: dt.into(),
|
||||
span: *span,
|
||||
}
|
||||
}
|
||||
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
|
||||
Some(eastoffset) => {
|
||||
let dt = eastoffset.timestamp_nanos(ts);
|
||||
Value::Date {
|
||||
val: dt,
|
||||
span: *span,
|
||||
}
|
||||
}
|
||||
None => Value::Error {
|
||||
error: Box::new(ShellError::DatetimeParseError(
|
||||
input.debug_value(),
|
||||
*span,
|
||||
)),
|
||||
},
|
||||
},
|
||||
}
|
||||
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
|
||||
Some(westoffset) => {
|
||||
let dt = westoffset.timestamp_nanos(ts);
|
||||
Value::Date {
|
||||
val: dt,
|
||||
span: *span,
|
||||
}
|
||||
}
|
||||
None => Value::Error {
|
||||
error: Box::new(ShellError::DatetimeParseError(
|
||||
input.debug_value(),
|
||||
*span,
|
||||
)),
|
||||
},
|
||||
},
|
||||
Zone::Error => Value::Error {
|
||||
// This is an argument error, not an input error
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: "Invalid timezone or offset".to_string(),
|
||||
span: *span,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return match timezone {
|
||||
// note all these `.timestamp_nanos()` could overflow if we didn't check range in `<date> | into int`.
|
||||
|
||||
// default to UTC
|
||||
None => Value::Date {
|
||||
val: Utc.timestamp_nanos(ts).into(),
|
||||
span: head,
|
||||
},
|
||||
Some(Spanned { item, span }) => match item {
|
||||
Zone::Utc => match_datetime!(Utc.timestamp_nanos(ts)),
|
||||
Zone::Local => match_datetime!(Local.timestamp_nanos(ts)),
|
||||
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
|
||||
Some(eastoffset) => match_datetime!(eastoffset.timestamp_nanos(ts)),
|
||||
None => Value::Error {
|
||||
error: Box::new(ShellError::DatetimeParseError(input.debug_value(), *span)),
|
||||
},
|
||||
},
|
||||
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
|
||||
Some(westoffset) => match_datetime!(westoffset.timestamp_nanos(ts)),
|
||||
None => Value::Error {
|
||||
error: Box::new(ShellError::DatetimeParseError(input.debug_value(), *span)),
|
||||
},
|
||||
},
|
||||
Zone::Error => Value::Error {
|
||||
// This is an argument error, not an input error
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: "Invalid timezone or offset".to_string(),
|
||||
span: *span,
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// If input is not a timestamp, try parsing it as a string
|
||||
match input {
|
||||
@ -314,6 +349,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Tries to automatically parse the date
|
||||
// (i.e. without a format string)
|
||||
// and assumes the system's local timezone if none is specified
|
||||
|
@ -17,14 +17,15 @@ impl Command for SubCommand {
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("into decimal")
|
||||
.input_output_types(vec![
|
||||
(Type::Int, Type::Number),
|
||||
(Type::String, Type::Number),
|
||||
(Type::Bool, Type::Number),
|
||||
(Type::Int, Type::Float),
|
||||
(Type::String, Type::Float),
|
||||
(Type::Bool, Type::Float),
|
||||
(Type::Float, Type::Float),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
(Type::Record(vec![]), Type::Record(vec![])),
|
||||
(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::Number)),
|
||||
Type::List(Box::new(Type::Float)),
|
||||
),
|
||||
])
|
||||
.rest(
|
||||
@ -76,9 +77,12 @@ impl Command for SubCommand {
|
||||
result: Some(Value::test_float(1.345)),
|
||||
},
|
||||
Example {
|
||||
description: "Convert decimal to decimal",
|
||||
example: "'-5.9' | into decimal",
|
||||
result: Some(Value::test_float(-5.9)),
|
||||
description: "Coerce list of ints and floats to float",
|
||||
example: "[4 -5.9] | into decimal",
|
||||
result: Some(Value::test_list(vec![
|
||||
Value::test_float(4.0),
|
||||
Value::test_float(-5.9),
|
||||
])),
|
||||
},
|
||||
Example {
|
||||
description: "Convert boolean to decimal",
|
||||
@ -91,6 +95,7 @@ impl Command for SubCommand {
|
||||
|
||||
fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
||||
match input {
|
||||
Value::Float { .. } => input.clone(),
|
||||
Value::String { val: s, span } => {
|
||||
let other = s.trim();
|
||||
|
||||
|
@ -3,10 +3,10 @@ use nu_parser::{parse_unit_value, DURATION_UNIT_GROUPS};
|
||||
use nu_protocol::{
|
||||
ast::{Call, CellPath, Expr},
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Unit,
|
||||
Value,
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Unit, Value,
|
||||
};
|
||||
|
||||
const NS_PER_SEC: i64 = 1_000_000_000;
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
@ -20,19 +20,11 @@ impl Command for SubCommand {
|
||||
.input_output_types(vec![
|
||||
(Type::String, Type::Duration),
|
||||
(Type::Duration, Type::Duration),
|
||||
// TODO: --convert option should be implemented as `format duration`
|
||||
(Type::String, Type::String),
|
||||
(Type::Duration, Type::String),
|
||||
(Type::Table(vec![]), Type::Table(vec![])),
|
||||
(Type::Record(vec![]), Type::Record(vec![])),
|
||||
//todo: record<hour,minute,sign> | into duration -> Duration
|
||||
//(Type::Record(vec![]), Type::Record(vec![])),
|
||||
])
|
||||
.allow_variants_without_examples(true)
|
||||
.named(
|
||||
"convert",
|
||||
SyntaxShape::String,
|
||||
"convert duration into another duration",
|
||||
Some('c'),
|
||||
)
|
||||
//.allow_variants_without_examples(true)
|
||||
.rest(
|
||||
"rest",
|
||||
SyntaxShape::CellPath,
|
||||
@ -46,7 +38,7 @@ impl Command for SubCommand {
|
||||
}
|
||||
|
||||
fn extra_usage(&self) -> &str {
|
||||
"This command does not take leap years into account, and every month is assumed to have 30 days."
|
||||
"Max duration value is i64::MAX nanoseconds; max duration time unit is wk (weeks)."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
@ -67,7 +59,23 @@ impl Command for SubCommand {
|
||||
let span = Span::test_data();
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert string to duration in table",
|
||||
description: "Convert duration string to duration value",
|
||||
example: "'7min' | into duration",
|
||||
result: Some(Value::Duration {
|
||||
val: 7 * 60 * NS_PER_SEC,
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert compound duration string to duration value",
|
||||
example: "'1day 2hr 3min 4sec' | into duration",
|
||||
result: Some(Value::Duration {
|
||||
val: (((((/* 1 * */24) + 2) * 60) + 3) * 60 + 4) * NS_PER_SEC,
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert table of duration strings to table of duration values",
|
||||
example:
|
||||
"[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
|
||||
result: Some(Value::List {
|
||||
@ -75,7 +83,7 @@ impl Command for SubCommand {
|
||||
Value::Record {
|
||||
cols: vec!["value".to_string()],
|
||||
vals: vec![Value::Duration {
|
||||
val: 1000 * 1000 * 1000,
|
||||
val: NS_PER_SEC,
|
||||
span,
|
||||
}],
|
||||
span,
|
||||
@ -83,7 +91,7 @@ impl Command for SubCommand {
|
||||
Value::Record {
|
||||
cols: vec!["value".to_string()],
|
||||
vals: vec![Value::Duration {
|
||||
val: 2 * 60 * 1000 * 1000 * 1000,
|
||||
val: 2 * 60 * NS_PER_SEC,
|
||||
span,
|
||||
}],
|
||||
span,
|
||||
@ -91,7 +99,7 @@ impl Command for SubCommand {
|
||||
Value::Record {
|
||||
cols: vec!["value".to_string()],
|
||||
vals: vec![Value::Duration {
|
||||
val: 3 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
val: 3 * 60 * 60 * NS_PER_SEC,
|
||||
span,
|
||||
}],
|
||||
span,
|
||||
@ -99,7 +107,7 @@ impl Command for SubCommand {
|
||||
Value::Record {
|
||||
cols: vec!["value".to_string()],
|
||||
vals: vec![Value::Duration {
|
||||
val: 4 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
val: 4 * 24 * 60 * 60 * NS_PER_SEC,
|
||||
span,
|
||||
}],
|
||||
span,
|
||||
@ -107,7 +115,7 @@ impl Command for SubCommand {
|
||||
Value::Record {
|
||||
cols: vec!["value".to_string()],
|
||||
vals: vec![Value::Duration {
|
||||
val: 5 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
val: 5 * 7 * 24 * 60 * 60 * NS_PER_SEC,
|
||||
span,
|
||||
}],
|
||||
span,
|
||||
@ -116,59 +124,11 @@ impl Command for SubCommand {
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert string to duration",
|
||||
example: "'7min' | into duration",
|
||||
result: Some(Value::Duration {
|
||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert string to the requested duration as a string",
|
||||
example: "'7min' | into duration --convert sec",
|
||||
result: Some(Value::String {
|
||||
val: "420 sec".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert duration to duration",
|
||||
example: "420sec | into duration",
|
||||
result: Some(Value::Duration {
|
||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert duration to the requested duration as a string",
|
||||
example: "420sec | into duration --convert ms",
|
||||
result: Some(Value::String {
|
||||
val: "420000 ms".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert µs duration to the requested duration as a string",
|
||||
example: "1000000µs | into duration --convert sec",
|
||||
result: Some(Value::String {
|
||||
val: "1 sec".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert duration to the µs duration as a string",
|
||||
example: "1sec | into duration --convert µs",
|
||||
result: Some(Value::String {
|
||||
val: "1000000 µs".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Convert duration to µs as a string if unit asked for was us",
|
||||
example: "1sec | into duration --convert us",
|
||||
result: Some(Value::String {
|
||||
val: "1000000 µs".to_string(),
|
||||
val: 7 * 60 * NS_PER_SEC,
|
||||
span,
|
||||
}),
|
||||
},
|
||||
@ -186,23 +146,17 @@ fn into_duration(
|
||||
Some(t) => t,
|
||||
None => call.head,
|
||||
};
|
||||
let convert_to_unit: Option<Spanned<String>> = call.get_flag(engine_state, stack, "convert")?;
|
||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
||||
let config = engine_state.get_config();
|
||||
let float_precision = config.float_precision as usize;
|
||||
|
||||
input.map(
|
||||
move |v| {
|
||||
if column_paths.is_empty() {
|
||||
action(&v, &convert_to_unit, float_precision, span)
|
||||
action(&v, span)
|
||||
} else {
|
||||
let mut ret = v;
|
||||
for path in &column_paths {
|
||||
let d = convert_to_unit.clone();
|
||||
let r = ret.update_cell_path(
|
||||
&path.members,
|
||||
Box::new(move |old| action(old, &d, float_precision, span)),
|
||||
);
|
||||
let r =
|
||||
ret.update_cell_path(&path.members, Box::new(move |old| action(old, span)));
|
||||
if let Err(error) = r {
|
||||
return Value::Error {
|
||||
error: Box::new(error),
|
||||
@ -217,199 +171,32 @@ fn into_duration(
|
||||
)
|
||||
}
|
||||
|
||||
fn convert_str_from_unit_to_unit(
|
||||
val: i64,
|
||||
from_unit: &str,
|
||||
to_unit: &str,
|
||||
span: Span,
|
||||
value_span: Span,
|
||||
) -> Result<f64, ShellError> {
|
||||
match (from_unit, to_unit) {
|
||||
("ns", "ns") => Ok(val as f64),
|
||||
("ns", "us") => Ok(val as f64 / 1000.0),
|
||||
("ns", "µs") => Ok(val as f64 / 1000.0), // Micro sign
|
||||
("ns", "μs") => Ok(val as f64 / 1000.0), // Greek small letter
|
||||
("ns", "ms") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("ns", "sec") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0),
|
||||
("ns", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0),
|
||||
("ns", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("ns", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("ns", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("ns", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("ns", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("ns", "dec") => {
|
||||
Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0)
|
||||
}
|
||||
|
||||
("us", "ns") => Ok(val as f64 * 1000.0),
|
||||
("us", "us") => Ok(val as f64),
|
||||
("us", "µs") => Ok(val as f64), // Micro sign
|
||||
("us", "μs") => Ok(val as f64), // Greek small letter
|
||||
("us", "ms") => Ok(val as f64 / 1000.0),
|
||||
("us", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("us", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||
("us", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("us", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("us", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("us", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("us", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("us", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
// Micro sign
|
||||
("µs", "ns") => Ok(val as f64 * 1000.0),
|
||||
("µs", "us") => Ok(val as f64),
|
||||
("µs", "µs") => Ok(val as f64), // Micro sign
|
||||
("µs", "μs") => Ok(val as f64), // Greek small letter
|
||||
("µs", "ms") => Ok(val as f64 / 1000.0),
|
||||
("µs", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("µs", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||
("µs", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("µs", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("µs", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("µs", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("µs", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("µs", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
// Greek small letter
|
||||
("μs", "ns") => Ok(val as f64 * 1000.0),
|
||||
("μs", "us") => Ok(val as f64),
|
||||
("μs", "µs") => Ok(val as f64), // Micro sign
|
||||
("μs", "μs") => Ok(val as f64), // Greek small letter
|
||||
("μs", "ms") => Ok(val as f64 / 1000.0),
|
||||
("μs", "sec") => Ok(val as f64 / 1000.0 / 1000.0),
|
||||
("μs", "min") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0),
|
||||
("μs", "hr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0),
|
||||
("μs", "day") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("μs", "wk") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("μs", "month") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("μs", "yr") => Ok(val as f64 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("μs", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("ms", "ns") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||
("ms", "us") => Ok(val as f64 * 1000.0),
|
||||
("ms", "µs") => Ok(val as f64 * 1000.0), // Micro sign
|
||||
("ms", "μs") => Ok(val as f64 * 1000.0), // Greek small letter
|
||||
("ms", "ms") => Ok(val as f64),
|
||||
("ms", "sec") => Ok(val as f64 / 1000.0),
|
||||
("ms", "min") => Ok(val as f64 / 1000.0 / 60.0),
|
||||
("ms", "hr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0),
|
||||
("ms", "day") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0),
|
||||
("ms", "wk") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("ms", "month") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("ms", "yr") => Ok(val as f64 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("ms", "dec") => Ok(val as f64 / 10.0 / 1000.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("sec", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0),
|
||||
("sec", "us") => Ok(val as f64 * 1000.0 * 1000.0),
|
||||
("sec", "µs") => Ok(val as f64 * 1000.0 * 1000.0), // Micro sign
|
||||
("sec", "μs") => Ok(val as f64 * 1000.0 * 1000.0), // Greek small letter
|
||||
("sec", "ms") => Ok(val as f64 * 1000.0),
|
||||
("sec", "sec") => Ok(val as f64),
|
||||
("sec", "min") => Ok(val as f64 / 60.0),
|
||||
("sec", "hr") => Ok(val as f64 / 60.0 / 60.0),
|
||||
("sec", "day") => Ok(val as f64 / 60.0 / 60.0 / 24.0),
|
||||
("sec", "wk") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 7.0),
|
||||
("sec", "month") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 30.0),
|
||||
("sec", "yr") => Ok(val as f64 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
("sec", "dec") => Ok(val as f64 / 10.0 / 60.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("min", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0),
|
||||
("min", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0),
|
||||
("min", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0), // Micro sign
|
||||
("min", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0), // Greek small letter
|
||||
("min", "ms") => Ok(val as f64 * 1000.0 * 60.0),
|
||||
("min", "sec") => Ok(val as f64 * 60.0),
|
||||
("min", "min") => Ok(val as f64),
|
||||
("min", "hr") => Ok(val as f64 / 60.0),
|
||||
("min", "day") => Ok(val as f64 / 60.0 / 24.0),
|
||||
("min", "wk") => Ok(val as f64 / 60.0 / 24.0 / 7.0),
|
||||
("min", "month") => Ok(val as f64 / 60.0 / 24.0 / 30.0),
|
||||
("min", "yr") => Ok(val as f64 / 60.0 / 24.0 / 365.0),
|
||||
("min", "dec") => Ok(val as f64 / 10.0 / 60.0 / 24.0 / 365.0),
|
||||
|
||||
("hr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0), // Micro sign
|
||||
("hr", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0), // Greek small letter
|
||||
("hr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0),
|
||||
("hr", "sec") => Ok(val as f64 * 60.0 * 60.0),
|
||||
("hr", "min") => Ok(val as f64 * 60.0),
|
||||
("hr", "hr") => Ok(val as f64),
|
||||
("hr", "day") => Ok(val as f64 / 24.0),
|
||||
("hr", "wk") => Ok(val as f64 / 24.0 / 7.0),
|
||||
("hr", "month") => Ok(val as f64 / 24.0 / 30.0),
|
||||
("hr", "yr") => Ok(val as f64 / 24.0 / 365.0),
|
||||
("hr", "dec") => Ok(val as f64 / 10.0 / 24.0 / 365.0),
|
||||
|
||||
("day", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0), // Micro sign
|
||||
("day", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0), // Greek small letter
|
||||
("day", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0),
|
||||
("day", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0),
|
||||
("day", "min") => Ok(val as f64 * 60.0 * 24.0),
|
||||
("day", "hr") => Ok(val as f64 * 24.0),
|
||||
("day", "day") => Ok(val as f64),
|
||||
("day", "wk") => Ok(val as f64 / 7.0),
|
||||
("day", "month") => Ok(val as f64 / 30.0),
|
||||
("day", "yr") => Ok(val as f64 / 365.0),
|
||||
("day", "dec") => Ok(val as f64 / 10.0 / 365.0),
|
||||
|
||||
("wk", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0), // Micro sign
|
||||
("wk", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0), // Greek small letter
|
||||
("wk", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "min") => Ok(val as f64 * 60.0 * 24.0 * 7.0),
|
||||
("wk", "hr") => Ok(val as f64 * 24.0 * 7.0),
|
||||
("wk", "day") => Ok(val as f64 * 7.0),
|
||||
("wk", "wk") => Ok(val as f64),
|
||||
("wk", "month") => Ok(val as f64 / 4.0),
|
||||
("wk", "yr") => Ok(val as f64 / 52.0),
|
||||
("wk", "dec") => Ok(val as f64 / 10.0 / 52.0),
|
||||
|
||||
("month", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0), // Micro sign
|
||||
("month", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0), // Greek small letter
|
||||
("month", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 30.0),
|
||||
("month", "min") => Ok(val as f64 * 60.0 * 24.0 * 30.0),
|
||||
("month", "hr") => Ok(val as f64 * 24.0 * 30.0),
|
||||
("month", "day") => Ok(val as f64 * 30.0),
|
||||
("month", "wk") => Ok(val as f64 * 4.0),
|
||||
("month", "month") => Ok(val as f64),
|
||||
("month", "yr") => Ok(val as f64 / 12.0),
|
||||
("month", "dec") => Ok(val as f64 / 10.0 / 12.0),
|
||||
|
||||
("yr", "ns") => Ok(val as f64 * 1000.0 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "us") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "µs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0), // Micro sign
|
||||
("yr", "μs") => Ok(val as f64 * 1000.0 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0), // Greek small letter
|
||||
("yr", "ms") => Ok(val as f64 * 1000.0 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "sec") => Ok(val as f64 * 60.0 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "min") => Ok(val as f64 * 60.0 * 24.0 * 365.0),
|
||||
("yr", "hr") => Ok(val as f64 * 24.0 * 365.0),
|
||||
("yr", "day") => Ok(val as f64 * 365.0),
|
||||
("yr", "wk") => Ok(val as f64 * 52.0),
|
||||
("yr", "month") => Ok(val as f64 * 12.0),
|
||||
("yr", "yr") => Ok(val as f64),
|
||||
("yr", "dec") => Ok(val as f64 / 10.0),
|
||||
|
||||
_ => Err(ShellError::CantConvertToDuration {
|
||||
details: to_unit.to_string(),
|
||||
dst_span: span,
|
||||
src_span: value_span,
|
||||
help: Some(
|
||||
"supported units are ns, us/µs, ms, sec, min, hr, day, wk, month, yr, and dec"
|
||||
.to_string(),
|
||||
),
|
||||
}),
|
||||
}
|
||||
// convert string list of duration values to duration NS.
|
||||
// technique for getting substrings and span based on: https://stackoverflow.com/a/67098851/2036651
|
||||
#[inline]
|
||||
fn addr_of(s: &str) -> usize {
|
||||
s.as_ptr() as usize
|
||||
}
|
||||
|
||||
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
|
||||
fn split_whitespace_indices(s: &str, span: Span) -> impl Iterator<Item = (&str, Span)> {
|
||||
s.split_whitespace().map(move |sub| {
|
||||
let start_offset = span.start + addr_of(sub) - addr_of(s);
|
||||
(sub, Span::new(start_offset, start_offset + sub.len()))
|
||||
})
|
||||
}
|
||||
|
||||
fn compound_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
||||
let mut duration_ns: i64 = 0;
|
||||
|
||||
for (substring, substring_span) in split_whitespace_indices(s, span) {
|
||||
let sub_ns = string_to_duration(substring, substring_span)?;
|
||||
duration_ns += sub_ns;
|
||||
}
|
||||
|
||||
Ok(duration_ns)
|
||||
}
|
||||
|
||||
fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
||||
if let Some(Ok(expression)) = parse_unit_value(
|
||||
s.as_bytes(),
|
||||
span,
|
||||
@ -423,11 +210,11 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
||||
Unit::Nanosecond => return Ok(x),
|
||||
Unit::Microsecond => return Ok(x * 1000),
|
||||
Unit::Millisecond => return Ok(x * 1000 * 1000),
|
||||
Unit::Second => return Ok(x * 1000 * 1000 * 1000),
|
||||
Unit::Minute => return Ok(x * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
|
||||
Unit::Second => return Ok(x * NS_PER_SEC),
|
||||
Unit::Minute => return Ok(x * 60 * NS_PER_SEC),
|
||||
Unit::Hour => return Ok(x * 60 * 60 * NS_PER_SEC),
|
||||
Unit::Day => return Ok(x * 24 * 60 * 60 * NS_PER_SEC),
|
||||
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * NS_PER_SEC),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@ -437,154 +224,23 @@ fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, Shel
|
||||
Err(ShellError::CantConvertToDuration {
|
||||
details: s.to_string(),
|
||||
dst_span: span,
|
||||
src_span: value_span,
|
||||
help: Some(
|
||||
"supported units are ns, us/µs, ms, sec, min, hr, day, wk, month, yr, and dec"
|
||||
.to_string(),
|
||||
),
|
||||
src_span: span,
|
||||
help: Some("supported units are ns, us/µs, ms, sec, min, hr, day, and wk".to_string()),
|
||||
})
|
||||
}
|
||||
|
||||
fn string_to_unit_duration(
|
||||
s: &str,
|
||||
span: Span,
|
||||
value_span: Span,
|
||||
) -> Result<(&str, i64), ShellError> {
|
||||
if let Some(Ok(expression)) = parse_unit_value(
|
||||
s.as_bytes(),
|
||||
span,
|
||||
DURATION_UNIT_GROUPS,
|
||||
Type::Duration,
|
||||
|x| x,
|
||||
) {
|
||||
if let Expr::ValueWithUnit(value, unit) = expression.expr {
|
||||
if let Expr::Int(x) = value.expr {
|
||||
match unit.item {
|
||||
Unit::Nanosecond => return Ok(("ns", x)),
|
||||
Unit::Microsecond => return Ok(("µs", x)),
|
||||
Unit::Millisecond => return Ok(("ms", x)),
|
||||
Unit::Second => return Ok(("sec", x)),
|
||||
Unit::Minute => return Ok(("min", x)),
|
||||
Unit::Hour => return Ok(("hr", x)),
|
||||
Unit::Day => return Ok(("day", x)),
|
||||
Unit::Week => return Ok(("wk", x)),
|
||||
|
||||
_ => return Ok(("ns", 0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(ShellError::CantConvertToDuration {
|
||||
details: s.to_string(),
|
||||
dst_span: span,
|
||||
src_span: value_span,
|
||||
help: Some(
|
||||
"supported units are ns, us/µs, ms, sec, min, hr, day, wk, month, yr, and dec"
|
||||
.to_string(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
fn action(
|
||||
input: &Value,
|
||||
convert_to_unit: &Option<Spanned<String>>,
|
||||
float_precision: usize,
|
||||
span: Span,
|
||||
) -> Value {
|
||||
fn action(input: &Value, span: Span) -> Value {
|
||||
match input {
|
||||
Value::Duration {
|
||||
val: val_num,
|
||||
span: value_span,
|
||||
} => {
|
||||
if let Some(to_unit) = convert_to_unit {
|
||||
let from_unit = "ns";
|
||||
let duration = *val_num;
|
||||
match convert_str_from_unit_to_unit(
|
||||
duration,
|
||||
from_unit,
|
||||
&to_unit.item,
|
||||
span,
|
||||
*value_span,
|
||||
) {
|
||||
Ok(d) => {
|
||||
let unit = if &to_unit.item == "us" {
|
||||
"µs"
|
||||
} else {
|
||||
&to_unit.item
|
||||
};
|
||||
if d.fract() == 0.0 {
|
||||
Value::String {
|
||||
val: format!("{} {}", d, unit),
|
||||
span: *value_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: format!("{:.float_precision$} {}", d, unit),
|
||||
span: *value_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: Box::new(e) },
|
||||
}
|
||||
} else {
|
||||
input.clone()
|
||||
}
|
||||
}
|
||||
Value::Duration { .. } => input.clone(),
|
||||
Value::String {
|
||||
val,
|
||||
span: value_span,
|
||||
} => {
|
||||
if let Some(to_unit) = convert_to_unit {
|
||||
if let Ok(dur) = string_to_unit_duration(val, span, *value_span) {
|
||||
let from_unit = dur.0;
|
||||
let duration = dur.1;
|
||||
match convert_str_from_unit_to_unit(
|
||||
duration,
|
||||
from_unit,
|
||||
&to_unit.item,
|
||||
span,
|
||||
*value_span,
|
||||
) {
|
||||
Ok(d) => {
|
||||
let unit = if &to_unit.item == "us" {
|
||||
"µs"
|
||||
} else {
|
||||
&to_unit.item
|
||||
};
|
||||
if d.fract() == 0.0 {
|
||||
Value::String {
|
||||
val: format!("{} {}", d, unit),
|
||||
span: *value_span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: format!("{:.float_precision$} {}", d, unit),
|
||||
span: *value_span,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: Box::new(e) },
|
||||
}
|
||||
} else {
|
||||
Value::Error {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "string".into(),
|
||||
from_type: "duration".into(),
|
||||
span,
|
||||
help: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match string_to_duration(val, span, *value_span) {
|
||||
Ok(val) => Value::Duration { val, span },
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
} => match compound_to_duration(val, *value_span) {
|
||||
Ok(val) => Value::Duration { val, span },
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(error),
|
||||
},
|
||||
},
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
@ -601,6 +257,7 @@ fn action(
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use rstest::rstest;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
@ -609,140 +266,33 @@ mod test {
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_ns_to_duration() {
|
||||
let span = Span::new(0, 2);
|
||||
let word = Value::test_string("3ns");
|
||||
let expected = Value::Duration { val: 3, span };
|
||||
let convert_duration = None;
|
||||
const NS_PER_SEC: i64 = 1_000_000_000;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
#[rstest]
|
||||
#[case("3ns", 3)]
|
||||
#[case("4us", 4*1000)]
|
||||
#[case("4\u{00B5}s", 4*1000)] // micro sign
|
||||
#[case("4\u{03BC}s", 4*1000)] // mu symbol
|
||||
#[case("5ms", 5 * 1000 * 1000)]
|
||||
#[case("1sec", 1 * NS_PER_SEC)]
|
||||
#[case("7min", 7 * 60 * NS_PER_SEC)]
|
||||
#[case("42hr", 42 * 60 * 60 * NS_PER_SEC)]
|
||||
#[case("123day", 123 * 24 * 60 * 60 * NS_PER_SEC)]
|
||||
#[case("3wk", 3 * 7 * 24 * 60 * 60 * NS_PER_SEC)]
|
||||
#[case("86hr 26ns", 86 * 3600 * NS_PER_SEC + 26)] // compound duration string
|
||||
#[case("14ns 3hr 17sec", 14 + 3 * 3600 * NS_PER_SEC + 17 * NS_PER_SEC)] // compound string with units in random order
|
||||
|
||||
#[test]
|
||||
fn turns_us_to_duration() {
|
||||
let span = Span::new(0, 2);
|
||||
let word = Value::test_string("4us");
|
||||
let expected = Value::Duration {
|
||||
val: 4 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_micro_sign_s_to_duration() {
|
||||
let span = Span::new(0, 2);
|
||||
let word = Value::test_string("4\u{00B5}s");
|
||||
let expected = Value::Duration {
|
||||
val: 4 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_mu_s_to_duration() {
|
||||
let span = Span::new(0, 2);
|
||||
let word = Value::test_string("4\u{03BC}s");
|
||||
let expected = Value::Duration {
|
||||
val: 4 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_ms_to_duration() {
|
||||
let span = Span::new(0, 2);
|
||||
let word = Value::test_string("5ms");
|
||||
let expected = Value::Duration {
|
||||
val: 5 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_sec_to_duration() {
|
||||
let span = Span::new(0, 3);
|
||||
let word = Value::test_string("1sec");
|
||||
let expected = Value::Duration {
|
||||
val: 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_min_to_duration() {
|
||||
let span = Span::new(0, 3);
|
||||
let word = Value::test_string("7min");
|
||||
let expected = Value::Duration {
|
||||
val: 7 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_hr_to_duration() {
|
||||
let span = Span::new(0, 3);
|
||||
let word = Value::test_string("42hr");
|
||||
let expected = Value::Duration {
|
||||
val: 42 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_day_to_duration() {
|
||||
let span = Span::new(0, 5);
|
||||
let word = Value::test_string("123day");
|
||||
let expected = Value::Duration {
|
||||
val: 123 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turns_wk_to_duration() {
|
||||
let span = Span::new(0, 2);
|
||||
let word = Value::test_string("3wk");
|
||||
let expected = Value::Duration {
|
||||
val: 3 * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000,
|
||||
span,
|
||||
};
|
||||
let convert_duration = None;
|
||||
|
||||
let actual = action(&word, &convert_duration, 2, span);
|
||||
assert_eq!(actual, expected);
|
||||
fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) {
|
||||
let actual = action(&Value::test_string(phrase), Span::new(0, phrase.len()));
|
||||
match actual {
|
||||
Value::Duration {
|
||||
val: observed_val, ..
|
||||
} => {
|
||||
assert_eq!(expected_duration_val, observed_val, "expected != observed")
|
||||
}
|
||||
other => {
|
||||
panic!("Expected Value::Duration, observed {other:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,7 +252,10 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
Value::Int { val: 0, span }
|
||||
}
|
||||
}
|
||||
Value::Date { val, .. } => {
|
||||
Value::Date {
|
||||
val,
|
||||
span: val_span,
|
||||
} => {
|
||||
if val
|
||||
< &FixedOffset::east_opt(0)
|
||||
.expect("constant")
|
||||
@ -267,7 +270,8 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
Value::Error {
|
||||
error: Box::new(ShellError::IncorrectValue {
|
||||
msg: "DateTime out of range for timestamp: 1677-09-21T00:12:43Z to 2262-04-11T23:47:16".to_string(),
|
||||
span
|
||||
val_span: *val_span,
|
||||
call_span: span,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
|
@ -83,21 +83,21 @@ impl Command for SubCommand {
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "convert duration to record",
|
||||
example: "-500day | into record",
|
||||
description: "convert duration to record (weeks max)",
|
||||
example: "(-500day - 4hr - 5sec) | into record",
|
||||
result: Some(Value::Record {
|
||||
cols: vec![
|
||||
"year".into(),
|
||||
"month".into(),
|
||||
"week".into(),
|
||||
"day".into(),
|
||||
"hour".into(),
|
||||
"second".into(),
|
||||
"sign".into(),
|
||||
],
|
||||
vals: vec![
|
||||
Value::Int { val: 1, span },
|
||||
Value::Int { val: 71, span },
|
||||
Value::Int { val: 3, span },
|
||||
Value::Int { val: 4, span },
|
||||
Value::Int { val: 2, span },
|
||||
Value::Int { val: 1, span },
|
||||
Value::Int { val: 5, span },
|
||||
Value::String {
|
||||
val: "-".into(),
|
||||
span,
|
||||
@ -261,8 +261,6 @@ fn parse_duration_into_record(duration: i64, span: Span) -> Value {
|
||||
"hr" => "hour".into(),
|
||||
"day" => "day".into(),
|
||||
"wk" => "week".into(),
|
||||
"month" => "month".into(),
|
||||
"yr" => "year".into(),
|
||||
_ => "unknown".into(),
|
||||
});
|
||||
|
||||
|
@ -40,6 +40,7 @@ impl Command for SubCommand {
|
||||
(Type::Bool, Type::String),
|
||||
(Type::Filesize, Type::String),
|
||||
(Type::Date, Type::String),
|
||||
(Type::Duration, Type::String),
|
||||
(
|
||||
Type::List(Box::new(Type::Any)),
|
||||
Type::List(Box::new(Type::String)),
|
||||
@ -129,12 +130,11 @@ impl Command for SubCommand {
|
||||
example: "true | into string",
|
||||
result: Some(Value::test_string("true")),
|
||||
},
|
||||
// TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032
|
||||
// Example {
|
||||
// description: "convert date to string",
|
||||
// example: "'2020-10-10 10:00:00 +02:00' | into datetime | into string",
|
||||
// result: Some(Value::test_string("Sat Oct 10 10:00:00 2020")),
|
||||
// },
|
||||
Example {
|
||||
description: "convert date to string",
|
||||
example: "'2020-10-10 10:00:00 +02:00' | into datetime | into string",
|
||||
result: Some(Value::test_string("Sat Oct 10 10:00:00 2020")),
|
||||
},
|
||||
Example {
|
||||
description: "convert filepath to string",
|
||||
example: "ls Cargo.toml | get name | into string",
|
||||
@ -145,6 +145,11 @@ impl Command for SubCommand {
|
||||
example: "1KiB | into string",
|
||||
result: Some(Value::test_string("1,024 B")),
|
||||
},
|
||||
Example {
|
||||
description: "convert duration to string",
|
||||
example: "9day | into string",
|
||||
result: Some(Value::test_string("1wk 2day")),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -239,6 +244,11 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
val: input.into_string(", ", config),
|
||||
span,
|
||||
},
|
||||
Value::Duration { val: _, .. } => Value::String {
|
||||
val: input.into_string("", config),
|
||||
span,
|
||||
},
|
||||
|
||||
Value::Error { error } => Value::String {
|
||||
val: into_code(error).unwrap_or_default(),
|
||||
span,
|
||||
|
@ -1,475 +0,0 @@
|
||||
use chrono::{DateTime, Local, Locale, TimeZone};
|
||||
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{Command, EngineState, Stack},
|
||||
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
|
||||
Value,
|
||||
};
|
||||
use nu_utils::locale::get_system_locale_string;
|
||||
use std::fmt::{Display, Write};
|
||||
|
||||
use super::utils::parse_date_from_string;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
|
||||
impl Command for SubCommand {
|
||||
fn name(&self) -> &str {
|
||||
"date format"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("date format")
|
||||
.input_output_types(vec![
|
||||
(Type::Date, Type::String),
|
||||
(Type::String, Type::String),
|
||||
])
|
||||
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
|
||||
.switch("list", "lists strftime cheatsheet", Some('l'))
|
||||
.optional(
|
||||
"format string",
|
||||
SyntaxShape::String,
|
||||
"the desired date format",
|
||||
)
|
||||
.category(Category::Date)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Format a given date using a format string."
|
||||
}
|
||||
|
||||
fn search_terms(&self) -> Vec<&str> {
|
||||
vec!["fmt", "strftime"]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<PipelineData, ShellError> {
|
||||
let head = call.head;
|
||||
if call.has_flag("list") {
|
||||
return Ok(PipelineData::Value(
|
||||
generate_strftime_list(head, false),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
let format = call.opt::<Spanned<String>>(engine_state, stack, 0)?;
|
||||
|
||||
// This doesn't match explicit nulls
|
||||
if matches!(input, PipelineData::Empty) {
|
||||
return Err(ShellError::PipelineEmpty { dst_span: head });
|
||||
}
|
||||
input.map(
|
||||
move |value| match &format {
|
||||
Some(format) => format_helper(value, format.item.as_str(), format.span, head),
|
||||
None => format_helper_rfc2822(value, head),
|
||||
},
|
||||
engine_state.ctrlc.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
// TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032
|
||||
// Example {
|
||||
// description: "Format a given date-time using the default format (RFC 2822).",
|
||||
// example: r#"'2021-10-22 20:00:12 +01:00' | into datetime | date format"#,
|
||||
// result: Some(Value::String {
|
||||
// val: "Fri, 22 Oct 2021 20:00:12 +0100".to_string(),
|
||||
// span: Span::test_data(),
|
||||
// }),
|
||||
// },
|
||||
Example {
|
||||
description:
|
||||
"Format a given date-time as a string using the default format (RFC 2822).",
|
||||
example: r#""2021-10-22 20:00:12 +01:00" | date format"#,
|
||||
result: Some(Value::String {
|
||||
val: "Fri, 22 Oct 2021 20:00:12 +0100".to_string(),
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "Format the current date-time using a given format string.",
|
||||
example: r#"date now | date format "%Y-%m-%d %H:%M:%S""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Format the current date using a given format string.",
|
||||
example: r#"date now | date format "%Y-%m-%d %H:%M:%S""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "Format a given date using a given format string.",
|
||||
example: r#""2021-10-22 20:00:12 +01:00" | date format "%Y-%m-%d""#,
|
||||
result: Some(Value::test_string("2021-10-22")),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn format_from<Tz: TimeZone>(date_time: DateTime<Tz>, formatter: &str, span: Span) -> Value
|
||||
where
|
||||
Tz::Offset: Display,
|
||||
{
|
||||
let mut formatter_buf = String::new();
|
||||
let locale: Locale = get_system_locale_string()
|
||||
.map(|l| l.replace('-', "_")) // `chrono::Locale` needs something like `xx_xx`, rather than `xx-xx`
|
||||
.unwrap_or_else(|| String::from("en_US"))
|
||||
.as_str()
|
||||
.try_into()
|
||||
.unwrap_or(Locale::en_US);
|
||||
let format = date_time.format_localized(formatter, locale);
|
||||
|
||||
match formatter_buf.write_fmt(format_args!("{format}")) {
|
||||
Ok(_) => Value::String {
|
||||
val: formatter_buf,
|
||||
span,
|
||||
},
|
||||
Err(_) => Value::Error {
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: "invalid format".to_string(),
|
||||
span,
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn format_helper(value: Value, formatter: &str, formatter_span: Span, head_span: Span) -> Value {
|
||||
match value {
|
||||
Value::Date { val, .. } => format_from(val, formatter, formatter_span),
|
||||
Value::String { val, .. } => {
|
||||
let dt = parse_date_from_string(&val, formatter_span);
|
||||
|
||||
match dt {
|
||||
Ok(x) => format_from(x, formatter, formatter_span),
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
_ => Value::Error {
|
||||
error: Box::new(ShellError::DatetimeParseError(
|
||||
value.debug_value(),
|
||||
head_span,
|
||||
)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn format_helper_rfc2822(value: Value, span: Span) -> Value {
|
||||
match value {
|
||||
Value::Date { val, span: _ } => Value::String {
|
||||
val: val.to_rfc2822(),
|
||||
span,
|
||||
},
|
||||
Value::String {
|
||||
val,
|
||||
span: val_span,
|
||||
} => {
|
||||
let dt = parse_date_from_string(&val, val_span);
|
||||
match dt {
|
||||
Ok(x) => Value::String {
|
||||
val: x.to_rfc2822(),
|
||||
span,
|
||||
},
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
_ => Value::Error {
|
||||
error: Box::new(ShellError::DatetimeParseError(value.debug_value(), span)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a table containing available datetime format specifiers
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `head` - use the call's head
|
||||
/// * `show_parse_only_formats` - whether parse-only format specifiers (that can't be outputted) should be shown. Should only be used for `into datetime`, not `date format`
|
||||
pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool) -> Value {
|
||||
let column_names = vec![
|
||||
"Specification".into(),
|
||||
"Example".into(),
|
||||
"Description".into(),
|
||||
];
|
||||
let now = Local::now();
|
||||
|
||||
struct FormatSpecification<'a> {
|
||||
spec: &'a str,
|
||||
description: &'a str,
|
||||
}
|
||||
|
||||
let specifications = vec![
|
||||
FormatSpecification {
|
||||
spec: "%Y",
|
||||
description: "The full proleptic Gregorian year, zero-padded to 4 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%C",
|
||||
description: "The proleptic Gregorian year divided by 100, zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%y",
|
||||
description: "The proleptic Gregorian year modulo 100, zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%m",
|
||||
description: "Month number (01--12), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%b",
|
||||
description: "Abbreviated month name. Always 3 letters.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%B",
|
||||
description: "Full month name. Also accepts corresponding abbreviation in parsing.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%h",
|
||||
description: "Same as %b.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%d",
|
||||
description: "Day number (01--31), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%e",
|
||||
description: "Same as %d but space-padded. Same as %_d.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%a",
|
||||
description: "Abbreviated weekday name. Always 3 letters.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%A",
|
||||
description: "Full weekday name. Also accepts corresponding abbreviation in parsing.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%w",
|
||||
description: "Sunday = 0, Monday = 1, ..., Saturday = 6.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%u",
|
||||
description: "Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%U",
|
||||
description: "Week number starting with Sunday (00--53), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%W",
|
||||
description:
|
||||
"Same as %U, but week 1 starts with the first Monday in that year instead.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%G",
|
||||
description: "Same as %Y but uses the year number in ISO 8601 week date.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%g",
|
||||
description: "Same as %y but uses the year number in ISO 8601 week date.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%V",
|
||||
description: "Same as %U but uses the week number in ISO 8601 week date (01--53).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%j",
|
||||
description: "Day of the year (001--366), zero-padded to 3 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%D",
|
||||
description: "Month-day-year format. Same as %m/%d/%y.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%x",
|
||||
description: "Locale's date representation (e.g., 12/31/99).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%F",
|
||||
description: "Year-month-day format (ISO 8601). Same as %Y-%m-%d.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%v",
|
||||
description: "Day-month-year format. Same as %e-%b-%Y.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%H",
|
||||
description: "Hour number (00--23), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%k",
|
||||
description: "Same as %H but space-padded. Same as %_H.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%I",
|
||||
description: "Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%l",
|
||||
description: "Same as %I but space-padded. Same as %_I.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%P",
|
||||
description: "am or pm in 12-hour clocks.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%p",
|
||||
description: "AM or PM in 12-hour clocks.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%M",
|
||||
description: "Minute number (00--59), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%S",
|
||||
description: "Second number (00--60), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%f",
|
||||
description: "The fractional seconds (in nanoseconds) since last whole second.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.f",
|
||||
description: "Similar to .%f but left-aligned. These all consume the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.3f",
|
||||
description: "Similar to .%f but left-aligned but fixed to a length of 3.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.6f",
|
||||
description: "Similar to .%f but left-aligned but fixed to a length of 6.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.9f",
|
||||
description: "Similar to .%f but left-aligned but fixed to a length of 9.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%3f",
|
||||
description: "Similar to %.3f but without the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%6f",
|
||||
description: "Similar to %.6f but without the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%9f",
|
||||
description: "Similar to %.9f but without the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%R",
|
||||
description: "Hour-minute format. Same as %H:%M.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%T",
|
||||
description: "Hour-minute-second format. Same as %H:%M:%S.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%X",
|
||||
description: "Locale's time representation (e.g., 23:13:48).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%r",
|
||||
description: "Hour-minute-second format in 12-hour clocks. Same as %I:%M:%S %p.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%Z",
|
||||
description:
|
||||
"Local time zone name. Skips all non-whitespace characters during parsing.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%z",
|
||||
description: "Offset from the local time to UTC (with UTC being +0000).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%:z",
|
||||
description: "Same as %z but with a colon.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%c",
|
||||
description: "Locale's date and time (e.g., Thu Mar 3 23:05:25 2005).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%+",
|
||||
description: "ISO 8601 / RFC 3339 date & time format.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%s",
|
||||
description: "UNIX timestamp, the number of seconds since 1970-01-01",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%t",
|
||||
description: "Literal tab (\\t).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%n",
|
||||
description: "Literal newline (\\n).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%%",
|
||||
description: "Literal percent sign.",
|
||||
},
|
||||
];
|
||||
|
||||
let mut records = specifications
|
||||
.iter()
|
||||
.map(|s| Value::Record {
|
||||
cols: column_names.clone(),
|
||||
vals: vec![
|
||||
Value::string(s.spec, head),
|
||||
Value::string(now.format(s.spec).to_string(), head),
|
||||
Value::string(s.description, head),
|
||||
],
|
||||
span: head,
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
|
||||
if show_parse_only_formats {
|
||||
// now.format("%#z") will panic since it is parse-only
|
||||
// so here we emulate how it will look:
|
||||
let example = now
|
||||
.format("%:z") // e.g. +09:30
|
||||
.to_string()
|
||||
.get(0..3) // +09:30 -> +09
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
records.push(Value::Record {
|
||||
cols: column_names,
|
||||
vals: vec![
|
||||
Value::string("%#z", head),
|
||||
Value::String {
|
||||
val: example,
|
||||
span: head,
|
||||
},
|
||||
Value::string(
|
||||
"Parsing only: Same as %z but allows minutes to be missing or present.",
|
||||
head,
|
||||
),
|
||||
],
|
||||
span: head,
|
||||
});
|
||||
}
|
||||
|
||||
Value::List {
|
||||
vals: records,
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SubCommand {})
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
mod date_;
|
||||
mod format;
|
||||
mod humanize;
|
||||
mod list_timezone;
|
||||
mod now;
|
||||
@ -10,12 +9,10 @@ mod to_timezone;
|
||||
mod utils;
|
||||
|
||||
pub use date_::Date;
|
||||
pub(crate) use format::generate_strftime_list;
|
||||
pub use format::SubCommand as DateFormat;
|
||||
pub use humanize::SubCommand as DateHumanize;
|
||||
pub use list_timezone::SubCommand as DateListTimezones;
|
||||
pub use now::SubCommand as DateNow;
|
||||
pub use to_record::SubCommand as DateToRecord;
|
||||
pub use to_table::SubCommand as DateToTable;
|
||||
pub use to_timezone::SubCommand as DateToTimezone;
|
||||
pub(crate) use utils::parse_date_from_string;
|
||||
pub(crate) use utils::{generate_strftime_list, parse_date_from_string};
|
||||
|
@ -46,7 +46,7 @@ impl Command for SubCommand {
|
||||
vec![
|
||||
Example {
|
||||
description: "Get the current date and display it in a given format string.",
|
||||
example: r#"date now | date format "%Y-%m-%d %H:%M:%S""#,
|
||||
example: r#"date now | format date "%Y-%m-%d %H:%M:%S""#,
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
|
@ -81,6 +81,34 @@ impl Command for SubCommand {
|
||||
Some(Value::Record { cols, vals, span })
|
||||
};
|
||||
|
||||
let example_result_2 = || {
|
||||
let span = Span::test_data();
|
||||
let cols = vec![
|
||||
"year".into(),
|
||||
"month".into(),
|
||||
"day".into(),
|
||||
"hour".into(),
|
||||
"minute".into(),
|
||||
"second".into(),
|
||||
"nanosecond".into(),
|
||||
"timezone".into(),
|
||||
];
|
||||
let vals = vec![
|
||||
Value::Int { val: 2020, span },
|
||||
Value::Int { val: 4, span },
|
||||
Value::Int { val: 12, span },
|
||||
Value::Int { val: 22, span },
|
||||
Value::Int { val: 10, span },
|
||||
Value::Int { val: 57, span },
|
||||
Value::Int { val: 0, span },
|
||||
Value::String {
|
||||
val: "+02:00".to_string(),
|
||||
span,
|
||||
},
|
||||
];
|
||||
Some(Value::Record { cols, vals, span })
|
||||
};
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert the current date into a record.",
|
||||
@ -97,12 +125,11 @@ impl Command for SubCommand {
|
||||
example: "'2020-04-12T22:10:57.123+02:00' | date to-record",
|
||||
result: example_result_1(),
|
||||
},
|
||||
// TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032
|
||||
// Example {
|
||||
// description: "Convert a date into a record.",
|
||||
// example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-record",
|
||||
// result: example_result_1(),
|
||||
// },
|
||||
Example {
|
||||
description: "Convert a date into a record.",
|
||||
example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-record",
|
||||
result: example_result_2(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,37 @@ impl Command for SubCommand {
|
||||
})
|
||||
};
|
||||
|
||||
let example_result_2 = || {
|
||||
let span = Span::test_data();
|
||||
let cols = vec![
|
||||
"year".into(),
|
||||
"month".into(),
|
||||
"day".into(),
|
||||
"hour".into(),
|
||||
"minute".into(),
|
||||
"second".into(),
|
||||
"nanosecond".into(),
|
||||
"timezone".into(),
|
||||
];
|
||||
let vals = vec![
|
||||
Value::Int { val: 2020, span },
|
||||
Value::Int { val: 4, span },
|
||||
Value::Int { val: 12, span },
|
||||
Value::Int { val: 22, span },
|
||||
Value::Int { val: 10, span },
|
||||
Value::Int { val: 57, span },
|
||||
Value::Int { val: 0, span },
|
||||
Value::String {
|
||||
val: "+02:00".to_string(),
|
||||
span,
|
||||
},
|
||||
];
|
||||
Some(Value::List {
|
||||
vals: vec![Value::Record { cols, vals, span }],
|
||||
span,
|
||||
})
|
||||
};
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "Convert the current date into a table.",
|
||||
@ -94,17 +125,14 @@ impl Command for SubCommand {
|
||||
},
|
||||
Example {
|
||||
description: "Convert a given date into a table.",
|
||||
//todo: resolve https://github.com/bspeice/dtparse/issues/40, which truncates nanosec bits
|
||||
// for now, change the example to use date literal rather than string conversion, as workaround
|
||||
example: "2020-04-12T22:10:57.000000789+02:00 | date to-table",
|
||||
result: example_result_1(),
|
||||
},
|
||||
// TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032
|
||||
// Example {
|
||||
// description: "Convert a given date into a table.",
|
||||
// example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-table",
|
||||
// result: example_result_1(),
|
||||
// },
|
||||
Example {
|
||||
description: "Convert a given date into a table.",
|
||||
example: "'2020-04-12 22:10:57 +0200' | into datetime | date to-table",
|
||||
result: example_result_2(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -99,12 +99,11 @@ impl Command for SubCommand {
|
||||
example: r#""2020-10-10 10:00:00 +02:00" | date to-timezone "+0500""#,
|
||||
result: example_result_1(),
|
||||
},
|
||||
// TODO: This should work but does not; see https://github.com/nushell/nushell/issues/7032
|
||||
// Example {
|
||||
// description: "Get the current date in Hawaii, from a datetime object",
|
||||
// example: r#""2020-10-10 10:00:00 +02:00" | into datetime | date to-timezone "+0500""#,
|
||||
// result: example_result_1(),
|
||||
// },
|
||||
Example {
|
||||
description: "Get the current date in Hawaii, from a datetime object",
|
||||
example: r#""2020-10-10 10:00:00 +02:00" | into datetime | date to-timezone "+0500""#,
|
||||
result: example_result_1(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -24,3 +24,280 @@ pub(crate) fn parse_date_from_string(
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a table containing available datetime format specifiers
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `head` - use the call's head
|
||||
/// * `show_parse_only_formats` - whether parse-only format specifiers (that can't be outputted) should be shown. Should only be used for `into datetime`, not `format date`
|
||||
pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool) -> Value {
|
||||
let column_names = vec![
|
||||
"Specification".into(),
|
||||
"Example".into(),
|
||||
"Description".into(),
|
||||
];
|
||||
let now = Local::now();
|
||||
|
||||
struct FormatSpecification<'a> {
|
||||
spec: &'a str,
|
||||
description: &'a str,
|
||||
}
|
||||
|
||||
let specifications = vec![
|
||||
FormatSpecification {
|
||||
spec: "%Y",
|
||||
description: "The full proleptic Gregorian year, zero-padded to 4 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%C",
|
||||
description: "The proleptic Gregorian year divided by 100, zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%y",
|
||||
description: "The proleptic Gregorian year modulo 100, zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%m",
|
||||
description: "Month number (01--12), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%b",
|
||||
description: "Abbreviated month name. Always 3 letters.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%B",
|
||||
description: "Full month name. Also accepts corresponding abbreviation in parsing.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%h",
|
||||
description: "Same as %b.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%d",
|
||||
description: "Day number (01--31), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%e",
|
||||
description: "Same as %d but space-padded. Same as %_d.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%a",
|
||||
description: "Abbreviated weekday name. Always 3 letters.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%A",
|
||||
description: "Full weekday name. Also accepts corresponding abbreviation in parsing.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%w",
|
||||
description: "Sunday = 0, Monday = 1, ..., Saturday = 6.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%u",
|
||||
description: "Monday = 1, Tuesday = 2, ..., Sunday = 7. (ISO 8601)",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%U",
|
||||
description: "Week number starting with Sunday (00--53), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%W",
|
||||
description:
|
||||
"Same as %U, but week 1 starts with the first Monday in that year instead.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%G",
|
||||
description: "Same as %Y but uses the year number in ISO 8601 week date.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%g",
|
||||
description: "Same as %y but uses the year number in ISO 8601 week date.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%V",
|
||||
description: "Same as %U but uses the week number in ISO 8601 week date (01--53).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%j",
|
||||
description: "Day of the year (001--366), zero-padded to 3 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%D",
|
||||
description: "Month-day-year format. Same as %m/%d/%y.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%x",
|
||||
description: "Locale's date representation (e.g., 12/31/99).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%F",
|
||||
description: "Year-month-day format (ISO 8601). Same as %Y-%m-%d.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%v",
|
||||
description: "Day-month-year format. Same as %e-%b-%Y.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%H",
|
||||
description: "Hour number (00--23), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%k",
|
||||
description: "Same as %H but space-padded. Same as %_H.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%I",
|
||||
description: "Hour number in 12-hour clocks (01--12), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%l",
|
||||
description: "Same as %I but space-padded. Same as %_I.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%P",
|
||||
description: "am or pm in 12-hour clocks.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%p",
|
||||
description: "AM or PM in 12-hour clocks.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%M",
|
||||
description: "Minute number (00--59), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%S",
|
||||
description: "Second number (00--60), zero-padded to 2 digits.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%f",
|
||||
description: "The fractional seconds (in nanoseconds) since last whole second.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.f",
|
||||
description: "Similar to .%f but left-aligned. These all consume the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.3f",
|
||||
description: "Similar to .%f but left-aligned but fixed to a length of 3.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.6f",
|
||||
description: "Similar to .%f but left-aligned but fixed to a length of 6.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%.9f",
|
||||
description: "Similar to .%f but left-aligned but fixed to a length of 9.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%3f",
|
||||
description: "Similar to %.3f but without the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%6f",
|
||||
description: "Similar to %.6f but without the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%9f",
|
||||
description: "Similar to %.9f but without the leading dot.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%R",
|
||||
description: "Hour-minute format. Same as %H:%M.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%T",
|
||||
description: "Hour-minute-second format. Same as %H:%M:%S.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%X",
|
||||
description: "Locale's time representation (e.g., 23:13:48).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%r",
|
||||
description: "Hour-minute-second format in 12-hour clocks. Same as %I:%M:%S %p.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%Z",
|
||||
description:
|
||||
"Local time zone name. Skips all non-whitespace characters during parsing.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%z",
|
||||
description: "Offset from the local time to UTC (with UTC being +0000).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%:z",
|
||||
description: "Same as %z but with a colon.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%c",
|
||||
description: "Locale's date and time (e.g., Thu Mar 3 23:05:25 2005).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%+",
|
||||
description: "ISO 8601 / RFC 3339 date & time format.",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%s",
|
||||
description: "UNIX timestamp, the number of seconds since 1970-01-01",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%t",
|
||||
description: "Literal tab (\\t).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%n",
|
||||
description: "Literal newline (\\n).",
|
||||
},
|
||||
FormatSpecification {
|
||||
spec: "%%",
|
||||
description: "Literal percent sign.",
|
||||
},
|
||||
];
|
||||
|
||||
let mut records = specifications
|
||||
.iter()
|
||||
.map(|s| Value::Record {
|
||||
cols: column_names.clone(),
|
||||
vals: vec![
|
||||
Value::string(s.spec, head),
|
||||
Value::string(now.format(s.spec).to_string(), head),
|
||||
Value::string(s.description, head),
|
||||
],
|
||||
span: head,
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
|
||||
if show_parse_only_formats {
|
||||
// now.format("%#z") will panic since it is parse-only
|
||||
// so here we emulate how it will look:
|
||||
let example = now
|
||||
.format("%:z") // e.g. +09:30
|
||||
.to_string()
|
||||
.get(0..3) // +09:30 -> +09
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
records.push(Value::Record {
|
||||
cols: column_names,
|
||||
vals: vec![
|
||||
Value::string("%#z", head),
|
||||
Value::String {
|
||||
val: example,
|
||||
span: head,
|
||||
},
|
||||
Value::string(
|
||||
"Parsing only: Same as %z but allows minutes to be missing or present.",
|
||||
head,
|
||||
),
|
||||
],
|
||||
span: head,
|
||||
});
|
||||
}
|
||||
|
||||
Value::List {
|
||||
vals: records,
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
|
@ -70,10 +70,10 @@ pub fn get_pipeline_elements(
|
||||
while i < pipeline.elements.len() {
|
||||
let pipeline_element = &pipeline.elements[i];
|
||||
let pipeline_expression = pipeline_element.expression().clone();
|
||||
let pipeline_span = &pipeline_element.span();
|
||||
let pipeline_span = pipeline_element.span();
|
||||
let element_str =
|
||||
String::from_utf8_lossy(engine_state.get_span_contents(pipeline_span));
|
||||
let value = Value::string(element_str.to_string(), *pipeline_span);
|
||||
let value = Value::string(element_str.to_string(), pipeline_span);
|
||||
let expr = pipeline_expression.expr.clone();
|
||||
let (command_name, command_args_value) = if let Expr::Call(call) = expr {
|
||||
let command = engine_state.get_decl(call.decl_id);
|
||||
|
@ -41,7 +41,7 @@ impl Command for ViewSource {
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
if let Some(span) = block.span {
|
||||
let contents = engine_state.get_span_contents(&span);
|
||||
let contents = engine_state.get_span_contents(span);
|
||||
Ok(Value::string(String::from_utf8_lossy(contents), call.head)
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
@ -61,7 +61,7 @@ impl Command for ViewSource {
|
||||
if let Some(block_id) = decl.get_block_id() {
|
||||
let block = engine_state.get_block(block_id);
|
||||
if let Some(block_span) = block.span {
|
||||
let contents = engine_state.get_span_contents(&block_span);
|
||||
let contents = engine_state.get_span_contents(block_span);
|
||||
// name of function
|
||||
let mut final_contents = format!("def {val} [ ");
|
||||
for n in vec_of_required {
|
||||
@ -117,7 +117,7 @@ impl Command for ViewSource {
|
||||
// arg is a module
|
||||
let module = engine_state.get_module(module_id);
|
||||
if let Some(module_span) = module.span {
|
||||
let contents = engine_state.get_span_contents(&module_span);
|
||||
let contents = engine_state.get_span_contents(module_span);
|
||||
Ok(Value::string(String::from_utf8_lossy(contents), call.head)
|
||||
.into_pipeline_data())
|
||||
} else {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user