Compare commits

...

104 Commits

Author SHA1 Message Date
JT
c6cb491e77 bump to 0.66 (#6137) 2022-07-27 07:56:14 +12:00
JT
e2a4632159 move to latest stable reedline (#6136) 2022-07-27 07:19:38 +12:00
65f0edd14b Allow multiple patterns in ls command (#6098)
* Allow multiple patterns in ls command

* Run formatter

* Comply with style

* Fix format error
2022-07-26 13:08:19 -05:00
b2c466bca6 Make login.nu work when using nu as a login shell (#6134)
* Make login.nu work when using nu as a login shell

Fixes #6055

Signed-off-by: nibon7 <nibon7@163.com>

* fix clippy warning

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-26 09:41:05 -05:00
6b4e577032 plugin show signature (#6126)
* plugin show signature

* remove expect from macro

* use fold to create string
2022-07-26 14:47:54 +01:00
b12a3dd0e5 allow view-source to view aliases (#6135) 2022-07-26 08:06:16 -05:00
d856ac92f4 expand durations to include month, year, decade (#6123)
* expand durations to include month, year, decade

* remove commented out fn

* oops, found more debug comments

* tweaked tests for the new way, borrowed heavily from chrono-humanize-rs

* clippy

* grammar
2022-07-26 08:05:37 -05:00
f5856b0914 Use local time for logger (#6132)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-26 06:20:35 -05:00
8c675a0d31 update some dependencies (#6131) 2022-07-25 21:09:32 -05:00
86a0e77065 Fix print_table_or_error when table is overridden (#6130)
Related #6113

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 20:11:46 -05:00
72c27bd095 Fix PipelineData::print when table is overridden (#6129)
* Fix PipelineData::print when `table` is overridden

Fixes #6113

Signed-off-by: nibon7 <nibon7@163.com>

* don't use deprecated `is_custom_command`

Signed-off-by: nibon7 <nibon7@163.com>

* add test

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 18:41:30 -05:00
e4e27b6e11 Use official virtualenv repo for the CI tests (#6127) 2022-07-26 10:20:03 +12:00
JT
475d32045f Revert "Refactor external command (#6083)" (#6116)
This reverts commit 0646f1118c.
2022-07-26 05:37:15 +12:00
3643ee6dfd Simplify print_table_or_error (#6122)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 12:01:10 -05:00
32e4535f24 Simplify eval_block (#6121)
Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 12:00:31 -05:00
daa2148136 Add CustomValue support to plugins (#6070)
* Skeleton implementation

Lots and lots of TODOs

* Bootstrap simple CustomValue plugin support test

* Create nu_plugin_custom_value

* Skeleton for nu_plugin_custom_values

* Return a custom value from plugin

* Encode CustomValues from plugin calls as PluginResponse::PluginData

* Add new PluginCall variant CollapseCustomValue

* Handle CollapseCustomValue plugin calls

* Add CallInput::Data variant to CallInfo inputs

* Handle CallInfo with CallInput::Data plugin calls

* Send CallInput::Data if Value is PluginCustomValue from plugin calls

* Remove unnecessary boxing of plugins CallInfo

* Add fields needed to collapse PluginCustomValue to it

* Document PluginCustomValue and its purpose

* Impl collapsing using plugin calls in PluginCustomValue::to_base_value

* Implement proper typetag based deserialization for CoolCustomValue

* Test demonstrating that passing back a custom value to plugin works

* Added a failing test for describing plugin CustomValues

* Support describe for PluginCustomValues

- Add name to PluginResponse::PluginData
  - Also turn it into a struct for clarity
- Add name to PluginCustomValue
- Return name field from PluginCustomValue

* Demonstrate that plugins can create and handle multiple CustomValues

* Add bincode to nu-plugin dependencies

This is for demonstration purposes, any schemaless binary seralization
format will work. I picked bincode since it's the most popular for Rust
but there are defintely better options out there for this usecase

* serde_json::Value -> Vec<u8>

* Update capnp schema for new CallInfo.input field

* Move call_input capnp serialization and deserialization into new file

* Deserialize Value's span from Value itself instead of passing call.head

I am not sure if this was correct and I am breaking it or if it was a
bug, I don't fully understand how nu creates and uses Spans. What should
reuse spans and what should recreate new ones?
But yeah it felt weird that the Value's Span was being ignored since in
the json serializer just uses the Value's Span

* Add call_info value round trip test

* Add capnp CallInput::Data serialization and deserialization support

* Add CallInfo::CollapseCustomValue to capnp schema

* Add capnp PluginCall::CollapseCustomValue serialization and deserialization support

* Add PluginResponse::PluginData to capnp schema

* Add capnp PluginResponse::PluginData serialization and deserialization support

* Switch plugins::custom_values tests to capnp

Both json and capnp would work now! Sadly I can't choose both at the
same time :(

* Add missing JsonSerializer round trip tests

* Handle plugin returning PluginData as a response to CollapseCustomValue

* Refactor plugin calling into a reusable function

Many less levels of indentation now!

* Export PluginData from nu_plugin

So plugins can create their very own serve_plugin with whatever
CustomValue behavior they may desire

* Error if CustomValue cannot be handled by Plugin
2022-07-25 17:32:56 +01:00
9097e865ca fix typo of port command (#6120) 2022-07-25 07:07:26 -05:00
894d3e7452 try make port test more reliable (#6117) 2022-07-25 06:42:06 -05:00
5a5c65ee4b Simplify PipelineData::print (#6119)
* Simplify PipelineData::print

Signed-off-by: nibon7 <nibon7@163.com>

* make write_all_and_flush to be associated function

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-25 06:38:21 -05:00
8b35239bce remove misleading example from source (#6118) 2022-07-25 11:52:16 +03:00
87e2fa137a Allow cp multiple files at once (#6114)
* Allow cp multiple files at once

* Expand destination with expand_ndots
2022-07-25 10:42:25 +03:00
JT
46f64c6fdc exit with non-zero exit code when script ends with non-zero exit (#6115) 2022-07-25 10:57:10 +12:00
10536f70f3 move the shell integration title setting to the right place (#6112) 2022-07-24 09:01:59 -05:00
0812a08bfb Don't panic if nu failed to create config files (#6104)
* Don't panic if nu failed to create config files

Signed-off-by: nibon7 <nibon7@163.com>

* eval default config

Signed-off-by: nibon7 <nibon7@163.com>

* tweak words

Signed-off-by: nibon7 <nibon7@163.com>

* tweak words again

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-24 07:00:52 -05:00
5706eddee3 throw error if any? or all? expression invokes invalid command (#6110)
* throw error if any? or all? expression invokes invalid command

* fix tests for windows
2022-07-24 06:28:12 -05:00
0b429fde24 Log warning message if nu failed to sync history (#6106)
Fixes #6088

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-23 11:35:43 -05:00
388ff78a26 trim spaces when converting strings to ints (#6105) 2022-07-23 09:23:04 -05:00
7d46177cf3 Allow mv multiple files at once (#6103)
* Allow mv multiple files at once

* Expand dots in mv src + dst
2022-07-23 07:51:41 -05:00
8a0bd20e84 Prevents panic when parsing JSON containing large number (#6096)
* prevents panic when parsing JSON containing large number

* fmt

* check for '-' sign first

* fmt

* clippy
2022-07-23 13:31:06 +12:00
a1a5a3646b Bump powierza-coefficient to 1.0.1 (#6099) 2022-07-22 19:12:41 -05:00
453c11b4b5 nu-table/ Bump tabled version (#6097)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-22 10:33:29 -05:00
c66b97126f Restore nu_with_plugins test macro (#6065)
* Updated nu_with_plugins to handle new nushell

- Now it requires the plugin format and name to be passed in, because
  we can't really guess the format
- It calls `register` with format and plugin path
- It creates a temporary folder and in it an empty temporary plugin.nu
  so that the tests don't conflict with each other or with local copy of
  plugin.nu
- Instead of passing the commands via stdin it passes them via the new
  --commands command line argument

* Rename path to command for clarity

* Enable core_inc tests

Remove deprecated inc feature and replace with new plugin feature

* Update core_inc tests for new nu_with_plugins syntax

* Rework core_inc::can_only_apply_one

The new inc plugin doesn't error if passed more than one but instead
chooses the highest increment

* Gate all plugin tests behind feature = "plugin" instead of one by one

* Remove format!-like behavior from nu_with_plugins

nu_with_plugins had format!-like behavior where it would allow calls
such as this:
```rs
nu_with_plugins!(
  cwd: "dir/",
  "open {} | get {}",
  "Cargo.toml",
  "package.version"
)
```
And although nifty it seems to have never been used before and the same
can be achieved with a format! like so:
```rs
nu_with_plugins!(
  cwd: "dir/",
  format!("open {} | get {}", "Cargo.toml", "package.version")
)
```
So I am removing it to keep the complexity of the macro in check

* Add multi-plugin support to nu_with_plugins

Useful for testing interactions between plugins

* Alternative 1: run `cargo build` inside of tests

* Handle Windows by canonicalizing paths and add .exe

One VM install later and lots of learning about how command line
arguments work and here we are
2022-07-22 00:14:37 -04:00
0646f1118c Refactor external command (#6083)
Co-authored-by: Frank <v-frankz@microsoft.com>
2022-07-21 19:56:57 -04:00
0bcfa12e0d enable find to work on some external streams (#6094) 2022-07-21 13:19:16 -05:00
b2ec32fdf0 concat string with lazy expressions (#6093) 2022-07-21 18:05:56 +01:00
8f00848ff9 add a fair amount ofsearch terms (#6090) 2022-07-21 06:29:41 -05:00
604025fe34 append string to series (#6089) 2022-07-21 10:42:12 +01:00
98126e2981 add more shell integration ansi escapes in support of vscode (#6087)
* add more shell integration ansi escapes in support of vscode

* clippy
2022-07-20 15:03:29 -05:00
db9b88089e enable find to be able to highlight some hits (#6086)
* enable find to be able to highlight some hits

* oops, deps in the wrong place
2022-07-20 10:09:33 -05:00
a35a71fd82 Make Semicolon stop on error (#6079)
* introduce external command runs to failed error, and implement semicolon relative logic

* ignore test due to semicolon works

* not raise ShellError for external commands

* update comment

* add relative test in for windows

* fix type-o

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-20 07:44:42 -05:00
558cd58d09 make into string --decimals add decimals to integer numbers (#6084)
* make `into string --decimals` add decimals to integer numbers

* add exception for 0
2022-07-20 06:16:35 -05:00
410f3ef0f0 nu-table: Update tests after #6080 (#6082)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-19 15:16:12 -05:00
ae765c71fd add config option to limit external command completions (#6076)
* add config option to limit external command completions

* fmt

* small change

* change name in config

* change name in config again
2022-07-19 12:39:50 -05:00
e5684bc34c Consider space for single ... column not enough space (#6080)
* nu-table: Refactoring

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: consider space for single `...` column not enough space

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-19 12:35:25 -05:00
b4a7e7e6e9 nu-table: Add a few tests (#6074)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-19 12:35:13 -05:00
41669e60c8 nu-table: Fix header style (again 2x) (#6073)
* nu-table: Fix header style

It did appeared again after my small change...

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Add a empty header style test

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-18 11:45:21 -05:00
eeaca50dee Conditionally disable expansion for external command (#6014)
* Fix 5978

* Add unit test for explicit glob

* Format

* Expansion vs none-expansion

* Add unit tests

* Fix format..

* Add debug message for MacOS

* Fix UT on Mac and add tests for windows

* cleanup

* clean up windows test

* single and double qoutes tests

* format...

* Save format.

* Add log to failed windows unit tests

* try `touch` a file

* PS or CMD

* roll back some change

* format

* Remove log and test case

* Add unit test comments

* Fix

Co-authored-by: Frank <v-frankz@microsoft.com>
2022-07-17 16:30:33 -05:00
d8d88cd395 nu-table: Add suffix coloring (#6071)
* nu-table: Bump tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Add suffix coloring while truncating

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix cargo fmt

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-17 13:56:31 -05:00
9aabafeb41 Add plugin CLI argument (#6064)
* Add plugin CLI argument

While working on supporting CustomValues in Plugins I stumbled upon the
test utilities defined in [nu-test-support][nu-test-support]
and thought these will come in handy, but they end up being outdated.
They haven't been used or since engine-q's was merged, so they are
currently using the old way engine-q handled plugins, where it would
just look into a specific folder for plugins and call them without
signatures or registration. While fixing that I realized that there is
currently no way to tell nushell to load and save signatures into a
specific path, and so those integration tests could end up potentially
conflicting with each other and with the local plugins the person
running them is using.

So this adds a new CLI argument to specify where to store and load
plugin signatures from

I am not super sure of the way I implemented this, mainly
I was a bit confused about the distinction between
[src/config_files.rs][src/config_files.rs] and
[crates/nu-cli/src/config_files.rs][crates/nu-cli/src/config_files.rs].
Should I be moving the plugin loading function from the `nu-cli` one to
the root one?

[nu-test-support]: 9d0be7d96f/crates/nu-test-support/src/macros.rs (L106)
[src/config_files.rs]: 9d0be7d96f/src/config_files.rs
[crates/nu-cli/src/config_files.rs]: 9d0be7d96f/crates/nu-cli/src/config_files.rs

* Gate new CLI option behind plugin feature

* Rename option to plugin-config
2022-07-17 13:29:19 -05:00
9ced5915ff Fix short-flag completion (#6067) 2022-07-17 07:46:40 -05:00
9d0be7d96f check column type during aggregation (#6058)
* check column type during aggregation

* check first if there is schema
2022-07-16 15:34:12 +01:00
57a6465ba0 add split list subcommand to split up lists (#6062)
* add `split list` subcommand to split up lists

* fmt

* fix shoddy signature
2022-07-16 06:24:37 -05:00
5cc6505512 Handle Windows drive paths in auto-cd (#6051)
* Handle Windows drive paths in auto-cd

* Limit `use regex` to Windows

* Use lazy_static for Windows drive path regex

* try fixing Clippy on *nix
2022-07-15 19:01:38 -07:00
3d45f77692 add wc search term for size and length (#6056) 2022-07-15 10:17:14 -05:00
e01974b7ab Ensure users colors are maintained when highlighting find matches (#6054) 2022-07-15 08:06:29 -05:00
1f01677b7b allow into int to convert octal numbers and 0 padded strings (#6053)
* allow `into int` to convert octal numbers and 0 padded strings

* added some tests in examples
2022-07-15 07:47:33 -05:00
58ee2bf06a fix documentation of plugin encodings (#6052)
Co-authored-by: Benjamin Lee <benjamin@computer.surgery>
2022-07-15 05:28:14 -05:00
7bf09559a6 Refactoring nu_table (#6049)
* nu-table: Remove unused dependencies

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Small refactoring

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Refactoring

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Refactoring alignments

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Add width check

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table/ Use commit instead of branch of tabled

To be safe

* Update Cargo.lock

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu-table: Bump tabled

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-14 15:24:32 -05:00
8dea08929a Cargo.lock was not checked in on typetag revert (#6050) 2022-07-14 13:30:25 -05:00
26f31da711 Split merging of parser delta and stack environment (#6005)
* Remove comment

* Split delta and environment merging

* Move table mode to a more logical place

* Cleanup

* Merge environment after reading default_env.nu

* Fmt
2022-07-14 17:09:27 +03:00
d95a065e3d Fix ps command on linux (#6047)
Fixes #6042

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-14 06:20:54 -05:00
ed50210832 load default env when user don't specified env path (#6040) 2022-07-14 08:53:13 +03:00
ceafe434b5 Downgrade crate typetag to 0.1.8 (#6044)
Co-authored-by: Frank <v-frankz@microsoft.com>
2022-07-13 14:38:29 -05:00
89b374cb16 allow for easy reset of config files with a single command (#6041)
* allow for easy config reset with a single command

* add slightly better help, rebase

* add option to make no backups, make all backups unique through including UNIX Epoch Time in the filename

* time is now formatted in rfc3339

* time is now formatted in a window-friendly format
2022-07-13 10:03:42 -05:00
47c1f475bf Fix panic when opening symlink which points to an inaccessible directory (#6034)
* Fix panic when opening symlink which points to an inaccessible directory

Fixes #6027

Signed-off-by: nibon7 <nibon7@163.com>

* tweak words

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-13 07:00:30 -05:00
61e027b227 nu-table: Bump tabled to master (#6038)
There was aparently some debug message on the target commit?

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:54:49 -05:00
58ab5aa887 nu-table: Remove width estimation logic (#6037)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:54:03 -05:00
2b2117173c nu-table: Restore atty check (#6036)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:49:43 -05:00
f2a79cf381 nu-table: Don't show empty header (#6035)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-13 06:43:39 -05:00
ad9449bf00 add ability to do into int on floats using a radix (#6033) 2022-07-12 20:37:57 -05:00
c2f8f4bd9b fix small bug converting string to int (#6031) 2022-07-12 19:34:26 -05:00
8b6232ac87 nu_table: Fix truncating logic (#6028)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-12 13:35:05 -05:00
93a965e3e2 nu_table: Fix style of tables with no header (#6025)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-12 20:56:36 +03:00
217c2bae99 Move wrap responsibility on tabled (#5999)
* nu_table/ Replace wrap.rs logic by tabled::Width::wrap

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu_table: Rename wrap.rs to width_control.rs

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu_table: Add configuration of trimming

```
let-env config = ($env.config | upsert table_trim { methodology: 'wrapping', wrapping_try_keep_words: false })
let-env config = ($env.config | upsert table_trim { methodology: 'truncating', truncatting_suffix: '...@@...' })
```

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu_table: Fix right padding issue

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu_table: Fix trancate issue

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu_table: Fix spelling in config

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* nu_table: Update tabled dependency

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Update default_config.nu with a table_trim options

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-12 11:23:50 -05:00
b9bbf0c10f make auto-cd change $env.OLDPWD (#6019)
* make auto-cd change `$env.OLDPWD`

* fmt

* use Config

* make auto-cd change `.OLDPWD`
2022-07-12 06:05:19 -05:00
a54f9719e5 add unspanned flag to error make, add tests (#6017)
* add `unspanned` flag to error make, add tests

* fmt
2022-07-12 06:03:50 -05:00
JT
a5470b2362 use simpler reedline (#6016) 2022-07-12 13:25:31 +12:00
c1bf9fd897 fixes ansi escape leakage from ill-behaved externals, again! (#6012)
* this fixes ansi escape leakage from ill-behaved externals

* cross-platform fix
2022-07-11 16:01:49 -05:00
f3036b8cfd Allow keeping selected environment variables from removed overlay (#6007)
* Allow keeping selected env from removed overlay

* Remove some duplicate code

* Change --keep-all back to --keep-custom

Because, apparently, you cannot have a named flag called --keep-all,
otherwise tests fail?

* Fix missing line and wrong test value
2022-07-11 23:58:28 +03:00
9b6b817276 update some dependencies (#6009)
* update some dependencies

* there may be some bugs here but it seems to compile and run

* clippy
2022-07-11 11:18:06 -05:00
9e3c64aa84 Add bytes collect, bytes remove, bytes build cmd (#6008)
* add bytes collect

* index_of support searching from end

* add bytes remove

* make bytes replace work better for empty pattern

* add bytes build

* remove comment

* tweak words

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-11 06:26:00 -05:00
920e0acb85 Fix load order of config files (#6006) 2022-07-10 18:12:24 +03:00
b7d3623e53 Revert "make module imports in scripts used for relative path. (#5913)" (#6002)
This reverts commit 6dde231dde.
2022-07-10 15:16:46 +03:00
3676a8a48d Expand Hooks Functionality (#5982)
* (WIP) Initial messy support for hooks as strings

* Cleanup after running condition & hook code

Also, remove prints

* Move env hooks eval into its own function

* Add env change hooks to simulator

* Fix hooks simulator not running env hooks properly

* Add missing hooks test file

* Expand hooks tests

* Add blocks as env hooks; Preserve hook environment

* Add full eval to pre prompt/exec hooks; Fix panic

* Rename env change hook back to orig. name

* Print err on test failure; Add list of hooks test

* Consolidate condition block; Fix panic; Misc

* CHange test to use real file

* Remove unused stuff

* Fix potential panics; Clean up errors

* Remove commented unused code

* Clippy: Fix extra references

* Add back support for old-style hooks

* Reorder functions; Fmt

* Fix test on Windows

* Add more test cases; Simplify some error reporting

* Add more tests for setting correct before/after

* Move pre_prompt hook to the beginning

Since we don't have a prompt or blocking on user input, all hooks just
follow after each other.
2022-07-10 13:45:46 +03:00
f85a1d003c throw parser error when multiple short flags are defined without whitespace (#6000)
* throw error when multiple short flags are defined without whitespace

* add tests
2022-07-10 20:32:52 +12:00
121e8678b6 nu-table: Fix a term_width value (#5997)
Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-09 14:55:47 -05:00
e4c512e33d nu-table: Fix wrap logic (#5998)
Adding space may overflow a cell_width.

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-09 14:55:39 -05:00
81df42d63b add more bytes cmd (#5989) 2022-07-08 21:42:31 -05:00
6802a4ee21 nu-table: Remove a error prone assertion (#5993) 2022-07-08 17:00:01 -05:00
c0ce78f892 add the ability to highlight with regular expressiosn (#5992) 2022-07-08 16:28:10 -05:00
221f36ca65 Add --directory (-D) flag to ls, list the directory itself instead of its contents (#5970)
* Avoid extending the directory without globs in `nu_engine::glob_from`

* avoid joining a `*` to the directory without globs

* remove checks on directory permission and whether it is empty

The previous implemention of `nu_engine::glob_from` will extend the
given directory even if it containes no glob pattern. This commit
overcomes lack of consistency with the function `nu_glob::glob`.

* Add flag -D to ls, to list the directory itself instead of its contents

* add --directory (-d) flag to ls

* correct the difference between the given path and the cwd

* set default path to `.` instead of `./*` when --directory (-d) flag is true

* add comments

* add an example

* add tests

* fmt
2022-07-08 14:15:34 -05:00
125e60d06a Add search terms to 'math' commands (#5990)
* Remove 'average' from search_terms

* Add search_terms to 'floor' and 'variance'
2022-07-08 09:14:51 -05:00
83458510a9 Revert "Return error when external command core dumped (#5908)" (#5987)
This reverts commit 5d00ecef56.
2022-07-07 20:00:04 -04:00
eac5f62959 tweak the find hit highlighting (#5981) 2022-07-07 11:32:58 -05:00
b19cc799aa make history.txt and history.sqlite3 tables have same command column (#5980) 2022-07-07 07:59:00 -05:00
efa56d0147 add the ability to highlight searched for terms (#5979) 2022-07-07 07:14:06 -05:00
47f6d20131 adds better error for failed string-to-duration conversions (#5977)
* adds better error for failed string-to-duration conversions

* makes error multi-spanned, conveys literally all the information available now
2022-07-07 05:54:38 -05:00
e0b4ab09eb compatible with old rust (#5974) 2022-07-06 18:22:45 -05:00
8abf28093a Bump openssl-src from 111.20.0+1.1.1o to 111.22.0+1.1.1q (#5971)
Bumps [openssl-src](https://github.com/alexcrichton/openssl-src-rs) from 111.20.0+1.1.1o to 111.22.0+1.1.1q.
- [Release notes](https://github.com/alexcrichton/openssl-src-rs/releases)
- [Commits](https://github.com/alexcrichton/openssl-src-rs/commits)

---
updated-dependencies:
- dependency-name: openssl-src
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-06 16:12:04 -05:00
d1687df067 Give tabled a try (#5969)
* Drop in replacement from nu-table to tabled.

Must act the same way as original nu-table.

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

Fix some issues

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Bump ansi-str version

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Update to latest

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix footer issue

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix header alignment

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix header style

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Use latest tabled/ansi-str

* Refactorings

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>

* Fix clippy warnings

Signed-off-by: Maxim Zhiburt <zhiburt@gmail.com>
2022-07-06 14:57:40 -05:00
e77219a59f allow where to work with variables (#5955)
* allow `where` to work with variables; breaking change

* change is no longer breaking, adds named to allow passage of blocks

* adds tests

* fmt
2022-07-06 08:49:07 -05:00
22edb37162 Add some bytes relative cmd (#5967)
* add reverse, ends_with command

* add bytes replace, make little refactor

* add bytes add
2022-07-06 08:25:37 -05:00
1ac87715ff add bytes root command (#5956)
* add bytes root command

* fixed type-o
2022-07-06 16:46:56 +12:00
de162c9aea Bump to 0.65.1 dev version (#5962) 2022-07-06 16:25:09 +12:00
193 changed files with 10114 additions and 3371 deletions

View File

@ -135,10 +135,7 @@ jobs:
- run: python -m pip install tox
- name: Install virtualenv
run: |
git clone https://github.com/kubouch/virtualenv.git && \
cd virtualenv && \
git checkout engine-q-update
run: git clone https://github.com/pypa/virtualenv.git
shell: bash
- name: Test Nushell in virtualenv

1138
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
rust-version = "1.60"
version = "0.65.0"
version = "0.66.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -28,6 +28,7 @@ members = [
"crates/nu_plugin_gstat",
"crates/nu_plugin_example",
"crates/nu_plugin_query",
"crates/nu_plugin_custom_values",
"crates/nu-utils",
]
@ -38,21 +39,21 @@ ctrlc = "3.2.1"
log = "0.4"
miette = "5.1.0"
nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.65.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.65.0" }
nu-command = { path="./crates/nu-command", version = "0.65.0" }
nu-engine = { path="./crates/nu-engine", version = "0.65.0" }
nu-json = { path="./crates/nu-json", version = "0.65.0" }
nu-parser = { path="./crates/nu-parser", version = "0.65.0" }
nu-path = { path="./crates/nu-path", version = "0.65.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.65.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.65.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.65.0" }
nu-system = { path = "./crates/nu-system", version = "0.65.0" }
nu-table = { path = "./crates/nu-table", version = "0.65.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.65.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.65.0" }
reedline = { version = "0.8.0", features = ["bashisms", "sqlite"]}
nu-cli = { path="./crates/nu-cli", version = "0.66.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.66.0" }
nu-command = { path="./crates/nu-command", version = "0.66.0" }
nu-engine = { path="./crates/nu-engine", version = "0.66.0" }
nu-json = { path="./crates/nu-json", version = "0.66.0" }
nu-parser = { path="./crates/nu-parser", version = "0.66.0" }
nu-path = { path="./crates/nu-path", version = "0.66.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.66.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.66.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.66.0" }
nu-system = { path = "./crates/nu-system", version = "0.66.0" }
nu-table = { path = "./crates/nu-table", version = "0.66.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.66.0" }
nu-utils = { path = "./crates/nu-utils", version = "0.66.0" }
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
pretty_env_logger = "0.4.0"
rayon = "1.5.1"
is_executable = "1.0.1"
@ -63,13 +64,13 @@ openssl = { version = "0.10.38", features = ["vendored"], optional = true }
signal-hook = { version = "0.3.14", default-features = false }
[dev-dependencies]
nu-test-support = { path="./crates/nu-test-support", version = "0.65.0" }
nu-test-support = { path="./crates/nu-test-support", version = "0.66.0" }
tempfile = "3.2.0"
assert_cmd = "2.0.2"
pretty_assertions = "1.0.0"
serial_test = "0.5.1"
serial_test = "0.8.0"
hamcrest2 = "0.3.0"
rstest = "0.12.0"
rstest = "0.15.0"
itertools = "0.10.3"
[target.'cfg(windows)'.build-dependencies]

View File

@ -4,29 +4,32 @@ description = "CLI-related functionality for Nushell"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.65.0"
version = "0.66.0"
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.65.0" }
nu-command = { path = "../nu-command", version = "0.65.0" }
nu-test-support = { path="../nu-test-support", version = "0.66.0" }
nu-command = { path = "../nu-command", version = "0.66.0" }
rstest = "0.15.0"
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.65.0" }
nu-path = { path = "../nu-path", version = "0.65.0" }
nu-parser = { path = "../nu-parser", version = "0.65.0" }
nu-protocol = { path = "../nu-protocol", version = "0.65.0" }
nu-utils = { path = "../nu-utils", version = "0.65.0" }
nu-engine = { path = "../nu-engine", version = "0.66.0" }
nu-path = { path = "../nu-path", version = "0.66.0" }
nu-parser = { path = "../nu-parser", version = "0.66.0" }
nu-protocol = { path = "../nu-protocol", version = "0.66.0" }
nu-utils = { path = "../nu-utils", version = "0.66.0" }
nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.65.0" }
reedline = { version = "0.8.0", features = ["bashisms", "sqlite"]}
nu-color-config = { path = "../nu-color-config", version = "0.66.0" }
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
crossterm = "0.23.0"
miette = { version = "5.1.0", features = ["fancy"] }
thiserror = "1.0.31"
fuzzy-matcher = "0.3.7"
log = "0.4"
is_executable = "1.0.1"
chrono = "0.4.19"
is_executable = "1.0.1"
lazy_static = "1.4.0"
log = "0.4"
regex = "1.5.4"
sysinfo = "0.24.1"
[features]

View File

@ -5,21 +5,27 @@ use nu_engine::{convert_env_values, eval_block};
use nu_parser::parse;
use nu_protocol::engine::Stack;
use nu_protocol::{
engine::{EngineState, StateDelta, StateWorkingSet},
engine::{EngineState, StateWorkingSet},
PipelineData, Spanned, Value,
};
use std::path::Path;
/// Run a command (or commands) given to us by the user
pub fn evaluate_commands(
commands: &Spanned<String>,
init_cwd: &Path,
engine_state: &mut EngineState,
stack: &mut Stack,
input: PipelineData,
is_perf_true: bool,
table_mode: Option<Value>,
) -> Result<Option<i64>> {
// Run a command (or commands) given to us by the user
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
// Parse the source code
let (block, delta) = {
if let Some(ref t_mode) = table_mode {
let mut config = engine_state.get_config().clone();
@ -39,43 +45,19 @@ pub fn evaluate_commands(
(output, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) {
// Update permanent state
if let Err(err) = engine_state.merge_delta(delta) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
let mut config = engine_state.get_config().clone();
if let Some(t_mode) = table_mode {
config.table_mode = t_mode.as_string()?;
}
// Merge the delta in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => {
if let Err(e) =
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
{
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
}
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
}
// Translate environment variables from Strings to Values
if let Some(e) = convert_env_values(engine_state, stack) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
std::process::exit(1);
}
// Run the block
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
Ok(pipeline_data) => {
let mut config = engine_state.get_config().clone();
if let Some(t_mode) = table_mode {
config.table_mode = t_mode.as_string()?;
}
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &mut config)
}
Err(err) => {

View File

@ -43,19 +43,21 @@ impl CommandCompletion {
if let Ok(mut contents) = std::fs::read_dir(path) {
while let Some(Ok(item)) = contents.next() {
if !executables.contains(
&item
.path()
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_default(),
) && matches!(
item.path()
.file_name()
.map(|x| match_algorithm
if self.engine_state.config.max_external_completion_results
> executables.len() as i64
&& !executables.contains(
&item
.path()
.file_name()
.map(|x| x.to_string_lossy().to_string())
.unwrap_or_default(),
)
&& matches!(
item.path().file_name().map(|x| match_algorithm
.matches_str(&x.to_string_lossy(), prefix)),
Some(true)
) && is_executable::is_executable(&item.path())
Some(true)
)
&& is_executable::is_executable(&item.path())
{
if let Ok(name) = item.file_name().into_string() {
executables.push(name);

View File

@ -1,7 +1,13 @@
use crate::util::{eval_source, report_error};
#[cfg(feature = "plugin")]
use log::info;
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
#[cfg(feature = "plugin")]
use nu_parser::ParseError;
#[cfg(feature = "plugin")]
use nu_path::canonicalize_with;
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet};
#[cfg(feature = "plugin")]
use nu_protocol::Spanned;
use nu_protocol::{HistoryFileFormat, PipelineData, Span};
use std::path::PathBuf;
@ -15,12 +21,13 @@ const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
pub fn read_plugin_file(
engine_state: &mut EngineState,
stack: &mut Stack,
plugin_file: Option<Spanned<String>>,
storage_path: &str,
is_perf_true: bool,
) {
// Reading signatures from signature file
// The plugin.nu file stores the parsed signature collected from each registered plugin
add_plugin_file(engine_state, storage_path);
add_plugin_file(engine_state, plugin_file, storage_path);
let plugin_path = engine_state.plugin_signatures.clone();
if let Some(plugin_path) = plugin_path {
@ -43,8 +50,23 @@ pub fn read_plugin_file(
}
#[cfg(feature = "plugin")]
pub fn add_plugin_file(engine_state: &mut EngineState, storage_path: &str) {
if let Some(mut plugin_path) = nu_path::config_dir() {
pub fn add_plugin_file(
engine_state: &mut EngineState,
plugin_file: Option<Spanned<String>>,
storage_path: &str,
) {
if let Some(plugin_file) = plugin_file {
let working_set = StateWorkingSet::new(engine_state);
let cwd = working_set.get_cwd();
match canonicalize_with(&plugin_file.item, cwd) {
Ok(path) => engine_state.plugin_signatures = Some(path),
Err(_) => {
let e = ParseError::FileNotFound(plugin_file.item, plugin_file.span);
report_error(&working_set, &e);
}
}
} else if let Some(mut plugin_path) = nu_path::config_dir() {
// Path to store plugins signatures
plugin_path.push(storage_path);
plugin_path.push(PLUGIN_FILE);
@ -69,12 +91,10 @@ pub fn eval_config_contents(
PipelineData::new(Span::new(0, 0)),
);
// Merge the delta in case env vars changed in the config
// Merge the environment in case env vars changed in the config
match nu_engine::env::current_dir(engine_state, stack) {
Ok(cwd) => {
if let Err(e) =
engine_state.merge_delta(StateDelta::new(engine_state), Some(stack), cwd)
{
if let Err(e) = engine_state.merge_env(stack, cwd) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
}

View File

@ -77,54 +77,33 @@ pub fn print_table_or_error(
match engine_state.find_decl("table".as_bytes(), &[]) {
Some(decl_id) => {
let table = engine_state.get_decl(decl_id).run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
pipeline_data,
);
let command = engine_state.get_decl(decl_id);
if command.get_block_id().is_some() {
print_or_exit(pipeline_data, engine_state, config);
} else {
let table = command.run(
engine_state,
stack,
&Call::new(Span::new(0, 0)),
pipeline_data,
);
match table {
Ok(table) => {
for item in table {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
let mut out = item.into_string("\n", config);
out.push('\n');
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
match table {
Ok(table) => {
print_or_exit(table, engine_state, config);
}
}
Err(error) => {
let working_set = StateWorkingSet::new(engine_state);
Err(error) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
report_error(&working_set, &error);
std::process::exit(1);
std::process::exit(1);
}
}
}
}
None => {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
let mut out = item.into_string("\n", config);
out.push('\n');
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
}
print_or_exit(pipeline_data, engine_state, config);
}
};
@ -141,3 +120,20 @@ pub fn print_table_or_error(
None
}
}
fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
for item in pipeline_data {
if let Value::Error { error } = item {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &error);
std::process::exit(1);
}
let mut out = item.into_string("\n", config);
out.push('\n');
let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{}", err));
}
}

View File

@ -22,8 +22,9 @@ pub use nu_highlight::NuHighlight;
pub use print::Print;
pub use prompt::NushellPrompt;
pub use repl::evaluate_repl;
pub use repl::{eval_env_change_hook, eval_hook};
pub use syntax_highlight::NuHighlighter;
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error};
pub use util::{eval_source, gather_parent_env_vars, get_init_cwd, report_error, report_error_new};
pub use validation::NuValidator;
#[cfg(feature = "plugin")]

View File

@ -1,5 +1,6 @@
#[cfg(windows)]
use nu_utils::enable_vt_processing;
use reedline::DefaultPrompt;
use {
reedline::{
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
@ -86,6 +87,11 @@ impl NushellPrompt {
impl Prompt for NushellPrompt {
fn render_prompt_left(&self) -> Cow<str> {
#[cfg(windows)]
{
let _ = enable_vt_processing();
}
if let Some(prompt_string) = &self.left_prompt_string {
prompt_string.replace('\n', "\r\n").into()
} else {

View File

@ -15,6 +15,10 @@ pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
// According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output>
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
fn get_prompt_string(
prompt: &str,
@ -98,6 +102,20 @@ pub(crate) fn update_prompt<'prompt>(
is_perf_true,
);
// Now that we have the prompt string lets ansify it.
// <133 A><prompt><133 B><command><133 C><command output>
let left_prompt_string = if config.shell_integration {
match left_prompt_string {
Some(prompt_string) => Some(format!(
"{}{}{}",
PRE_PROMPT_MARKER, prompt_string, POST_PROMPT_MARKER
)),
None => left_prompt_string,
}
} else {
left_prompt_string
};
let right_prompt_string = get_prompt_string(
PROMPT_COMMAND_RIGHT,
config,

View File

@ -2,26 +2,34 @@ use crate::{
completions::NuCompleter,
prompt_update,
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
util::{eval_source, report_error},
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
NuHighlighter, NuValidator, NushellPrompt,
};
use log::{info, trace};
use lazy_static::lazy_static;
use log::{info, trace, warn};
use miette::{IntoDiagnostic, Result};
use nu_color_config::get_color_config;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::lex;
use nu_parser::{lex, parse};
use nu_protocol::{
ast::PathMember,
engine::{EngineState, Stack, StateWorkingSet},
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Value,
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
};
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
use regex::Regex;
use std::io::{self, Write};
use std::{sync::atomic::Ordering, time::Instant};
use sysinfo::SystemExt;
const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
// According to Daniel Imms @Tyriar, we need to do these this way:
// <133 A><prompt><133 B><command><133 C><command output>
// These first two have been moved to prompt_update to get as close as possible to the prompt.
// const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
// const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
const PRE_EXECUTE_MARKER: &str = "\x1b]133;C\x1b\\";
const CMD_FINISHED_MARKER: &str = "\x1b]133;D\x1b\\";
// This one is in get_command_finished_marker() now so we can capture the exit codes properly.
// const CMD_FINISHED_MARKER: &str = "\x1b]133;D;{}\x1b\\";
const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
pub fn evaluate_repl(
@ -79,7 +87,7 @@ pub fn evaluate_repl(
// Get the config once for the history `max_history_size`
// Updating that will not be possible in one session
let mut config = engine_state.get_config();
let config = engine_state.get_config();
if is_perf_true {
info!("setup reedline {}:{}:{}", file!(), line!(), column!());
@ -121,6 +129,14 @@ pub fn evaluate_repl(
);
}
let cwd = get_guaranteed_cwd(engine_state, stack);
// Before doing anything, merge the environment from the previous REPL iteration into the
// permanent state.
if let Err(err) = engine_state.merge_env(stack, cwd) {
report_error_new(engine_state, &err);
}
//Reset the ctrl-c handler
if let Some(ctrlc) = &mut engine_state.ctrlc {
ctrlc.store(false, Ordering::SeqCst);
@ -130,7 +146,7 @@ pub fn evaluate_repl(
sig_quit.store(false, Ordering::SeqCst);
}
config = engine_state.get_config();
let config = engine_state.get_config();
if is_perf_true {
info!("setup colors {}:{}:{}", file!(), line!(), column!());
@ -147,7 +163,6 @@ pub fn evaluate_repl(
engine_state: engine_state.clone(),
config: config.clone(),
}))
.with_animation(config.animate_prompt)
.with_validator(Box::new(NuValidator {
engine_state: engine_state.clone(),
}))
@ -201,7 +216,10 @@ pub fn evaluate_repl(
if is_perf_true {
info!("sync history {}:{}:{}", file!(), line!(), column!());
}
line_editor.sync_history().into_diagnostic()?;
if let Err(e) = line_editor.sync_history() {
warn!("Failed to sync history: {}", e);
}
}
if is_perf_true {
@ -236,64 +254,22 @@ pub fn evaluate_repl(
// Right before we start our prompt and take input from the user,
// fire the "pre_prompt" hook
if let Some(hook) = &config.hooks.pre_prompt {
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
if let Some(hook) = config.hooks.pre_prompt.clone() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
// Next, check all the environment variables they ask for
// fire the "env_change" hook
if let Some(hook) = config.hooks.env_change.clone() {
match hook {
Value::Record {
cols, vals: blocks, ..
} => {
for (idx, env_var) in cols.iter().enumerate() {
let before = engine_state
.previous_env_vars
.get(env_var)
.cloned()
.unwrap_or_default();
let after = stack.get_env_var(engine_state, env_var).unwrap_or_default();
if before != after {
if let Err(err) = run_hook(
engine_state,
stack,
vec![before, after.clone()],
&blocks[idx],
) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
}
engine_state
.previous_env_vars
.insert(env_var.to_string(), after);
}
}
}
x => {
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::TypeMismatch(
"record for 'env_change' hook".to_string(),
x.span().unwrap_or_else(|_| Span::new(0, 0)),
),
)
}
}
}
config = engine_state.get_config();
let shell_integration = config.shell_integration;
if shell_integration {
run_ansi_sequence(PRE_PROMPT_MARKER)?;
let config = engine_state.get_config();
if let Err(error) =
eval_env_change_hook(config.hooks.env_change.clone(), engine_state, stack)
{
report_error_new(engine_state, &error)
}
let config = engine_state.get_config();
let prompt =
prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt, is_perf_true);
@ -309,6 +285,7 @@ pub fn evaluate_repl(
}
let input = line_editor.read_line(prompt);
let shell_integration = config.shell_integration;
match input {
Ok(Signal::Success(s)) => {
@ -328,32 +305,31 @@ pub fn evaluate_repl(
// Right before we start running the code the user gave us,
// fire the "pre_execution" hook
if let Some(hook) = &config.hooks.pre_execution {
if let Err(err) = run_hook(engine_state, stack, vec![], hook) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
if let Some(hook) = config.hooks.pre_execution.clone() {
if let Err(err) = eval_hook(engine_state, stack, vec![], &hook) {
report_error_new(engine_state, &err);
}
}
if shell_integration {
run_ansi_sequence(RESET_APPLICATION_MODE)?;
run_ansi_sequence(PRE_EXECUTE_MARKER)?;
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
} else {
path
};
// if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
// let path = cwd.as_string()?;
// // Try to abbreviate string for windows title
// let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
// path.replace(&p.as_path().display().to_string(), "~")
// } else {
// path
// };
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
}
// // Set window title too
// // https://tldp.org/HOWTO/Xterm-Title-3.html
// // ESC]0;stringBEL -- Set icon name and window title to string
// // ESC]1;stringBEL -- Set icon name to string
// // ESC]2;stringBEL -- Set window title to string
// run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
// }
}
let start_time = Instant::now();
@ -364,13 +340,7 @@ pub fn evaluate_repl(
let orig = s.clone();
if (orig.starts_with('.')
|| orig.starts_with('~')
|| orig.starts_with('/')
|| orig.starts_with('\\'))
&& path.is_dir()
&& tokens.0.len() == 1
{
if looks_like_path(&orig) && path.is_dir() && tokens.0.len() == 1 {
// We have an auto-cd
let (path, span) = {
if !path.exists() {
@ -386,6 +356,14 @@ pub fn evaluate_repl(
(path.to_string_lossy().to_string(), tokens.0[0].span)
};
stack.add_env_var(
"OLDPWD".into(),
Value::String {
val: cwd.clone(),
span: Span { start: 0, end: 0 },
},
);
//FIXME: this only changes the current scope, but instead this environment variable
//should probably be a block that loads the information from the state in the overlay
stack.add_env_var(
@ -437,14 +415,6 @@ pub fn evaluate_repl(
},
);
// FIXME: permanent state changes like this hopefully in time can be removed
// and be replaced by just passing the cwd in where needed
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
let _ = std::env::set_current_dir(path);
engine_state.add_env_var("PWD".into(), cwd);
}
if history_supports_meta && !s.is_empty() {
line_editor
.update_last_command_context(&|mut c| {
@ -458,20 +428,35 @@ pub fn evaluate_repl(
}
if shell_integration {
// FIXME: use variant with exit code, if apropriate
run_ansi_sequence(CMD_FINISHED_MARKER)?;
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
if let Some(cwd) = stack.get_env_var(engine_state, "PWD") {
let path = cwd.as_string()?;
// Try to abbreviate string for windows title
let maybe_abbrev_path = if let Some(p) = nu_path::home_dir() {
path.replace(&p.as_path().display().to_string(), "~")
} else {
path
};
// Set window title too
// https://tldp.org/HOWTO/Xterm-Title-3.html
// ESC]0;stringBEL -- Set icon name and window title to string
// ESC]1;stringBEL -- Set icon name to string
// ESC]2;stringBEL -- Set window title to string
run_ansi_sequence(&format!("\x1b]2;{}\x07", maybe_abbrev_path))?;
}
}
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
if shell_integration {
run_ansi_sequence(CMD_FINISHED_MARKER)?;
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
}
}
Ok(Signal::CtrlD) => {
// When exiting clear to a new line
if shell_integration {
run_ansi_sequence(CMD_FINISHED_MARKER)?;
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
}
println!();
break;
@ -482,7 +467,7 @@ pub fn evaluate_repl(
println!("Error: {:?}", err);
}
if shell_integration {
run_ansi_sequence(CMD_FINISHED_MARKER)?;
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
}
}
}
@ -491,6 +476,285 @@ pub fn evaluate_repl(
Ok(())
}
pub fn get_command_finished_marker(stack: &Stack, engine_state: &EngineState) -> String {
let exit_code = stack
.get_env_var(engine_state, "LAST_EXIT_CODE")
.and_then(|e| e.as_i64().ok());
format!("\x1b]133;D;{}\x1b\\", exit_code.unwrap_or(0))
}
pub fn eval_env_change_hook(
env_change_hook: Option<Value>,
engine_state: &mut EngineState,
stack: &mut Stack,
) -> Result<(), ShellError> {
if let Some(hook) = env_change_hook {
match hook {
Value::Record {
cols: env_names,
vals: hook_values,
..
} => {
for (env_name, hook_value) in env_names.iter().zip(hook_values.iter()) {
let before = engine_state
.previous_env_vars
.get(env_name)
.cloned()
.unwrap_or_default();
let after = stack
.get_env_var(engine_state, env_name)
.unwrap_or_default();
if before != after {
eval_hook(
engine_state,
stack,
vec![("$before".into(), before), ("$after".into(), after.clone())],
hook_value,
)?;
engine_state
.previous_env_vars
.insert(env_name.to_string(), after);
}
}
}
x => {
return Err(ShellError::TypeMismatch(
"record for the 'env_change' hook".to_string(),
x.span()?,
));
}
}
}
Ok(())
}
pub fn eval_hook(
engine_state: &mut EngineState,
stack: &mut Stack,
arguments: Vec<(String, Value)>,
value: &Value,
) -> Result<(), ShellError> {
let value_span = value.span()?;
let condition_path = PathMember::String {
val: "condition".to_string(),
span: value_span,
};
let code_path = PathMember::String {
val: "code".to_string(),
span: value_span,
};
match value {
Value::List { vals, .. } => {
for val in vals {
eval_hook(engine_state, stack, arguments.clone(), val)?
}
}
Value::Record { .. } => {
let do_run_hook =
if let Ok(condition) = value.clone().follow_cell_path(&[condition_path], false) {
match condition {
Value::Block {
val: block_id,
span: block_span,
..
} => {
match run_hook_block(
engine_state,
stack,
block_id,
arguments.clone(),
block_span,
) {
Ok(value) => match value {
Value::Bool { val, .. } => val,
other => {
return Err(ShellError::UnsupportedConfigValue(
"boolean output".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
},
Err(err) => {
return Err(err);
}
}
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
} else {
// always run the hook
true
};
if do_run_hook {
match value.clone().follow_cell_path(&[code_path], false)? {
Value::String {
val,
span: source_span,
} => {
let (block, delta, vars) = {
let mut working_set = StateWorkingSet::new(engine_state);
let mut vars: Vec<(VarId, Value)> = vec![];
for (name, val) in arguments {
let var_id = working_set.add_variable(
name.as_bytes().to_vec(),
val.span()?,
Type::Any,
);
vars.push((var_id, val));
}
let (output, err) =
parse(&mut working_set, Some("hook"), val.as_bytes(), false, &[]);
if let Some(err) = err {
report_error(&working_set, &err);
return Err(ShellError::UnsupportedConfigValue(
"valid source code".into(),
"source code with syntax errors".into(),
source_span,
));
}
(output, working_set.render(), vars)
};
engine_state.merge_delta(delta)?;
let input = PipelineData::new(value_span);
let var_ids: Vec<VarId> = vars
.into_iter()
.map(|(var_id, val)| {
stack.add_var(var_id, val);
var_id
})
.collect();
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(_) => {}
Err(err) => {
report_error_new(engine_state, &err);
}
}
for var_id in var_ids.iter() {
stack.vars.remove(var_id);
}
let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?;
}
Value::Block {
val: block_id,
span: block_span,
..
} => {
run_hook_block(engine_state, stack, block_id, arguments, block_span)?;
let cwd = get_guaranteed_cwd(engine_state, stack);
engine_state.merge_env(stack, cwd)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block or string".to_string(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
}
}
Value::Block {
val: block_id,
span: block_span,
..
} => {
run_hook_block(engine_state, stack, *block_id, arguments, *block_span)?;
}
other => {
return Err(ShellError::UnsupportedConfigValue(
"block, record, or list of records".into(),
format!("{}", other.get_type()),
other.span()?,
));
}
}
Ok(())
}
pub fn run_hook_block(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
arguments: Vec<(String, Value)>,
span: Span,
) -> Result<Value, ShellError> {
let block = engine_state.get_block(block_id);
let input = PipelineData::new(span);
let mut callee_stack = stack.gather_captures(&block.captures);
for (idx, PositionalArg { var_id, .. }) in
block.signature.required_positional.iter().enumerate()
{
if let Some(var_id) = var_id {
if let Some(arg) = arguments.get(idx) {
callee_stack.add_var(*var_id, arg.1.clone())
} else {
return Err(ShellError::IncompatibleParametersSingle(
"This hook block has too many parameters".into(),
span,
));
}
}
}
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
Ok(pipeline_data) => match pipeline_data.into_value(span) {
Value::Error { error } => Err(error),
val => {
// If all went fine, preserve the environment of the called block
let caller_env_vars = stack.get_env_var_names(engine_state);
// remove env vars that are present in the caller but not in the callee
// (the callee hid them)
for var in caller_env_vars.iter() {
if !callee_stack.has_env_var(engine_state, var) {
stack.remove_env_var(engine_state, var);
}
}
// add new env vars from callee to caller
for (var, value) in callee_stack.get_stack_env_vars() {
stack.add_env_var(var, value);
}
Ok(val)
}
},
Err(err) => Err(err),
}
}
fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
match io::stdout().write_all(seq.as_bytes()) {
Ok(it) => it,
@ -515,62 +779,33 @@ fn run_ansi_sequence(seq: &str) -> Result<(), ShellError> {
})
}
pub fn run_hook(
engine_state: &EngineState,
stack: &mut Stack,
arguments: Vec<Value>,
value: &Value,
) -> Result<(), ShellError> {
match value {
Value::List { vals, .. } => {
for val in vals {
run_hook(engine_state, stack, arguments.clone(), val)?
}
Ok(())
}
Value::Block {
val: block_id,
span,
..
} => run_hook_block(engine_state, stack, *block_id, arguments, *span),
x => match x.span() {
Ok(span) => Err(ShellError::MissingConfigValue(
"block for hook in config".into(),
span,
)),
_ => Err(ShellError::MissingConfigValue(
"block for hook in config".into(),
Span { start: 0, end: 0 },
)),
},
}
lazy_static! {
// Absolute paths with a drive letter, like 'C:', 'D:\', 'E:\foo'
static ref DRIVE_PATH_REGEX: Regex =
Regex::new(r"^[a-zA-Z]:[/\\]?").expect("Internal error: regex creation");
}
pub fn run_hook_block(
engine_state: &EngineState,
stack: &mut Stack,
block_id: BlockId,
arguments: Vec<Value>,
span: Span,
) -> Result<(), ShellError> {
let block = engine_state.get_block(block_id);
let input = PipelineData::new(span);
let mut callee_stack = stack.gather_captures(&block.captures);
for (idx, PositionalArg { var_id, .. }) in
block.signature.required_positional.iter().enumerate()
// A best-effort "does this string look kinda like a path?" function to determine whether to auto-cd
fn looks_like_path(orig: &str) -> bool {
#[cfg(windows)]
{
if let Some(var_id) = var_id {
callee_stack.add_var(*var_id, arguments[idx].clone())
if DRIVE_PATH_REGEX.is_match(orig) {
return true;
}
}
match eval_block(engine_state, &mut callee_stack, block, input, false, false) {
Ok(pipeline_data) => match pipeline_data.into_value(span) {
Value::Error { error } => Err(error),
_ => Ok(()),
},
Err(err) => Err(err),
}
orig.starts_with('.')
|| orig.starts_with('~')
|| orig.starts_with('/')
|| orig.starts_with('\\')
}
#[test]
fn looks_like_path_windows_drive_path_works() {
let on_windows = cfg!(windows);
assert_eq!(looks_like_path("C:"), on_windows);
assert_eq!(looks_like_path("D:\\"), on_windows);
assert_eq!(looks_like_path("E:/"), on_windows);
assert_eq!(looks_like_path("F:\\some_dir"), on_windows);
assert_eq!(looks_like_path("G:/some_dir"), on_windows);
}

View File

@ -224,16 +224,11 @@ pub fn eval_source(
(output, working_set.render())
};
let cwd = match nu_engine::env::current_dir(engine_state, stack) {
Ok(p) => p,
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
get_init_cwd()
}
};
let _ = engine_state.merge_delta(delta, Some(stack), &cwd);
if let Err(err) = engine_state.merge_delta(delta) {
set_last_exit_code(stack, 1);
report_error_new(engine_state, &err);
return false;
}
match eval_block(engine_state, stack, &block, input, false, false) {
Ok(mut pipeline_data) => {
@ -297,6 +292,15 @@ pub fn report_error(
}
}
pub fn report_error_new(
engine_state: &EngineState,
error: &(dyn miette::Diagnostic + Send + Sync + 'static),
) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, error);
}
pub fn get_init_cwd() -> PathBuf {
match std::env::current_dir() {
Ok(cwd) => cwd,
@ -310,6 +314,17 @@ pub fn get_init_cwd() -> PathBuf {
}
}
pub fn get_guaranteed_cwd(engine_state: &EngineState, stack: &Stack) -> PathBuf {
match nu_engine::env::current_dir(engine_state, stack) {
Ok(p) => p,
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
get_init_cwd()
}
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -2,10 +2,24 @@ pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use rstest::{fixture, rstest};
use support::{match_suggestions, new_engine};
#[test]
fn variables_completions() {
#[fixture]
fn completer() -> NuCompleter {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = "def tst [--mod -s] {}";
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
NuCompleter::new(std::sync::Arc::new(engine), stack)
}
#[fixture]
fn completer_strings() -> NuCompleter {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
@ -15,15 +29,41 @@ fn variables_completions() {
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instantiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
NuCompleter::new(std::sync::Arc::new(engine), stack)
}
// Test completions for $nu
let suggestions = completer.complete("my-command ", 11);
assert_eq!(3, suggestions.len());
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
// Match results
#[rstest]
fn variables_completions_double_dash_argument(mut completer: NuCompleter) {
let suggestions = completer.complete("tst --", 6);
let expected: Vec<String> = vec!["--help".into(), "--mod".into()];
// dbg!(&expected, &suggestions);
match_suggestions(expected, suggestions);
}
#[rstest]
fn variables_completions_single_dash_argument(mut completer: NuCompleter) {
let suggestions = completer.complete("tst -", 5);
let expected: Vec<String> = vec!["--help".into(), "--mod".into(), "-h".into(), "-s".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn variables_completions_command(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 9);
let expected: Vec<String> = vec!["my-command".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn variables_completions_subcommands(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}
#[rstest]
fn variables_completions_subcommands_2(mut completer_strings: NuCompleter) {
let suggestions = completer_strings.complete("my-command ", 11);
let expected: Vec<String> = vec!["cat".into(), "dog".into(), "eel".into()];
match_suggestions(expected, suggestions);
}

View File

@ -30,8 +30,8 @@ fn file_completions() {
// Match the results
match_suggestions(expected_paths, suggestions);
// Test completions for the completions/another folder
let target_dir = format!("cd {}", folder(dir.join("another")));
// Test completions for a file
let target_dir = format!("cp {}", folder(dir.join("another")));
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values

View File

@ -14,15 +14,17 @@ fn flag_completions() {
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4);
assert_eq!(12, suggestions.len());
assert_eq!(14, suggestions.len());
let expected: Vec<String> = vec![
"--all".into(),
"--directory".into(),
"--du".into(),
"--full-paths".into(),
"--help".into(),
"--long".into(),
"--short-names".into(),
"-D".into(),
"-a".into(),
"-d".into(),
"-f".into(),

View File

@ -4,7 +4,7 @@ use nu_command::create_default_context;
use nu_engine::eval_block;
use nu_parser::parse;
use nu_protocol::{
engine::{EngineState, Stack, StateDelta, StateWorkingSet},
engine::{EngineState, Stack, StateWorkingSet},
PipelineData, ShellError, Span, Value,
};
use nu_test_support::fs;
@ -23,14 +23,11 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
dir_str.push(SEP);
// Create a new engine with default context
let mut engine_state = create_default_context(&dir);
let mut engine_state = create_default_context();
// New stack
let mut stack = Stack::new();
// New delta state
let delta = StateDelta::new(&engine_state);
// Add pwd as env var
stack.add_env_var(
"PWD".to_string(),
@ -53,8 +50,8 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
},
);
// Merge delta
let merge_result = engine_state.merge_delta(delta, Some(&mut stack), &dir);
// Merge environment into the permanent state
let merge_result = engine_state.merge_env(&mut stack, &dir);
assert!(merge_result.is_ok());
(dir, dir_str, engine_state, stack)
@ -62,6 +59,15 @@ pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
// match a list of suggestions with the expected values
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
let expected_len = expected.len();
let suggestions_len = suggestions.len();
if expected_len != suggestions_len {
panic!(
"\nexpected {expected_len} suggestions but got {suggestions_len}: \n\
Suggestions: {suggestions:#?} \n\
Expected: {expected:#?}\n"
)
}
expected.iter().zip(suggestions).for_each(|it| {
assert_eq!(it.0, &it.1.value);
});
@ -97,6 +103,11 @@ pub fn merge_input(
(block, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta) {
return Err(err);
}
assert!(eval_block(
engine_state,
stack,
@ -112,6 +123,6 @@ pub fn merge_input(
)
.is_ok());
// Merge delta
engine_state.merge_delta(delta, Some(stack), &dir)
// Merge environment into the permanent state
engine_state.merge_env(stack, &dir)
}

View File

@ -4,11 +4,11 @@ description = "Color configuration code used by Nushell"
edition = "2021"
license = "MIT"
name = "nu-color-config"
version = "0.65.0"
version = "0.66.0"
[dependencies]
nu-protocol = { path = "../nu-protocol", version = "0.65.0" }
nu-protocol = { path = "../nu-protocol", version = "0.66.0" }
nu-ansi-term = "0.46.0"
nu-json = { path = "../nu-json", version = "0.65.0" }
nu-table = { path = "../nu-table", version = "0.65.0" }
nu-json = { path = "../nu-json", version = "0.66.0" }
nu-table = { path = "../nu-table", version = "0.66.0" }
serde = { version="1.0.123", features=["derive"] }

View File

@ -4,25 +4,25 @@ description = "Nushell's built-in commands"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.65.0"
version = "0.66.0"
build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-color-config = { path = "../nu-color-config", version = "0.65.0" }
nu-engine = { path = "../nu-engine", version = "0.65.0" }
nu-glob = { path = "../nu-glob", version = "0.65.0" }
nu-json = { path = "../nu-json", version = "0.65.0" }
nu-parser = { path = "../nu-parser", version = "0.65.0" }
nu-path = { path = "../nu-path", version = "0.65.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.65.0" }
nu-protocol = { path = "../nu-protocol", version = "0.65.0" }
nu-system = { path = "../nu-system", version = "0.65.0" }
nu-table = { path = "../nu-table", version = "0.65.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.65.0" }
nu-test-support = { path = "../nu-test-support", version = "0.65.0" }
nu-utils = { path = "../nu-utils", version = "0.65.0" }
nu-color-config = { path = "../nu-color-config", version = "0.66.0" }
nu-engine = { path = "../nu-engine", version = "0.66.0" }
nu-glob = { path = "../nu-glob", version = "0.66.0" }
nu-json = { path = "../nu-json", version = "0.66.0" }
nu-parser = { path = "../nu-parser", version = "0.66.0" }
nu-path = { path = "../nu-path", version = "0.66.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.66.0" }
nu-protocol = { path = "../nu-protocol", version = "0.66.0" }
nu-system = { path = "../nu-system", version = "0.66.0" }
nu-table = { path = "../nu-table", version = "0.66.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.66.0" }
nu-test-support = { path = "../nu-test-support", version = "0.66.0" }
nu-utils = { path = "../nu-utils", version = "0.66.0" }
nu-ansi-term = "0.46.0"
# Potential dependencies for extras
@ -52,15 +52,15 @@ is-root = "0.1.2"
itertools = "0.10.0"
lazy_static = "1.4.0"
log = "0.4.14"
lscolors = { version = "0.9.0", features = ["crossterm"]}
lscolors = { version = "0.10.0", features = ["crossterm"]}
md5 = { package = "md-5", version = "0.10.0" }
meval = "0.2.0"
mime = "0.3.16"
notify = "4.0.17"
num = { version = "0.4.0", optional = true }
pathdiff = "0.2.1"
powierza-coefficient = "1.0"
quick-xml = "0.22"
powierza-coefficient = "1.0.1"
quick-xml = "0.23.0"
rand = "0.8"
rayon = "1.5.1"
regex = "1.5.4"
@ -73,26 +73,25 @@ serde_urlencoded = "0.7.0"
serde_yaml = "0.8.16"
sha2 = "0.10.0"
# Disable default features b/c the default features build Git (very slow to compile)
shadow-rs = { version = "0.11.0", default-features = false }
shadow-rs = { version = "0.16.1", default-features = false }
strip-ansi-escapes = "0.1.1"
sysinfo = "0.23.5"
terminal_size = "0.1.17"
sysinfo = "0.24.6"
terminal_size = "0.2.1"
thiserror = "1.0.31"
titlecase = "1.1.0"
titlecase = "2.0.0"
toml = "0.5.8"
unicode-segmentation = "1.8.0"
url = "2.2.1"
uuid = { version = "0.8.2", features = ["v4"] }
uuid = { version = "1.1.2", features = ["v4"] }
which = { version = "4.2.2", optional = true }
reedline = { version = "0.8.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.4.0", features = ["diagnostics"] }
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
wax = { version = "0.5.0", features = ["diagnostics"] }
rusqlite = { version = "0.28.0", features = ["bundled"], optional = true }
sqlparser = { version = "0.16.0", features = ["serde"], optional = true }
[target.'cfg(unix)'.dependencies]
umask = "2.0.0"
users = "0.11.0"
signal-hook = { version = "0.3.14", default-features = false }
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
version = "2.1.3"
@ -106,8 +105,8 @@ features = [
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
"rolling_window", "strings", "rows", "random",
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
"dynamic_groupby", "dtype-categorical"
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
"dynamic_groupby", "dtype-categorical", "concat_str"
]
[target.'cfg(windows)'.dependencies.windows]
@ -127,11 +126,11 @@ dataframe = ["polars", "num"]
database = ["sqlparser", "rusqlite"]
[build-dependencies]
shadow-rs = { version = "0.11.0", default-features = false }
shadow-rs = { version = "0.16.1", default-features = false }
[dev-dependencies]
hamcrest2 = "0.3.0"
dirs-next = "2.0.0"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
rstest = "0.12.0"
rstest = "0.15.0"

View File

@ -0,0 +1,167 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
struct Arguments {
added_data: Vec<u8>,
index: Option<usize>,
end: bool,
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesAdd;
impl Command for BytesAdd {
fn name(&self) -> &str {
"bytes add"
}
fn signature(&self) -> Signature {
Signature::build("bytes add")
.required("data", SyntaxShape::Binary, "the binary to add")
.named(
"index",
SyntaxShape::Int,
"index to insert binary data",
Some('i'),
)
.switch("end", "add to the end of binary", Some('e'))
.rest(
"rest",
SyntaxShape::CellPath,
"optionally matches prefix of text by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"add specified bytes to the input"
}
fn search_terms(&self) -> Vec<&str> {
vec!["append", "truncate", "padding"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let added_data: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let index: Option<usize> = call.get_flag(engine_state, stack, "index")?;
let end = call.has_flag("end");
let arg = Arguments {
added_data,
index,
end,
column_paths,
};
operate(add, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Add bytes `0x[AA]` to `0x[1F FF AA AA]`",
example: "0x[1F FF AA AA] | bytes add 0x[AA]",
result: Some(Value::Binary {
val: vec![0xAA, 0x1F, 0xFF, 0xAA, 0xAA],
span: Span::test_data(),
}),
},
Example {
description: "Add bytes `0x[AA BB]` to `0x[1F FF AA AA]` at index 1",
example: "0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1",
result: Some(Value::Binary {
val: vec![0x1F, 0xAA, 0xBB, 0xFF, 0xAA, 0xAA],
span: Span::test_data(),
}),
},
Example {
description: "Add bytes `0x[11]` to `0x[FF AA AA]` at the end",
example: "0x[FF AA AA] | bytes add 0x[11] -e",
result: Some(Value::Binary {
val: vec![0xFF, 0xAA, 0xAA, 0x11],
span: Span::test_data(),
}),
},
Example {
description: "Add bytes `0x[11 22 33]` to `0x[FF AA AA]` at the end, at index 1(the index is start from end)",
example: "0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1",
result: Some(Value::Binary {
val: vec![0xFF, 0xAA, 0x11, 0x22, 0x33, 0xBB],
span: Span::test_data(),
}),
},
]
}
}
fn add(input: &[u8], args: &Arguments, span: Span) -> Value {
match args.index {
None => {
if args.end {
let mut added_data = args.added_data.clone();
let mut result = input.to_vec();
result.append(&mut added_data);
Value::Binary { val: result, span }
} else {
let mut result = args.added_data.clone();
let mut input = input.to_vec();
result.append(&mut input);
Value::Binary { val: result, span }
}
}
Some(mut indx) => {
let inserted_index = if args.end {
input.len().saturating_sub(indx)
} else {
if indx > input.len() {
indx = input.len()
}
indx
};
let mut result = vec![];
let mut prev_data = input[..inserted_index].to_vec();
result.append(&mut prev_data);
let mut added_data = args.added_data.clone();
result.append(&mut added_data);
let mut after_data = input[inserted_index..].to_vec();
result.append(&mut after_data);
Value::Binary { val: result, span }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesAdd {})
}
}

View File

@ -0,0 +1,281 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
use std::cmp::Ordering;
#[derive(Clone)]
pub struct BytesAt;
struct Arguments {
start: isize,
end: isize,
arg_span: Span,
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
/// ensure given `range` is valid, and returns [start, end, val_span] pair.
fn parse_range(range: Value, head: Span) -> Result<(isize, isize, Span), ShellError> {
let (start, end, span) = match range {
Value::List { mut vals, span } => {
if vals.len() != 2 {
return Err(ShellError::UnsupportedInput(
"More than two indices given".to_string(),
span,
));
} else {
let end = vals.pop().expect("Already check has size 2");
let end = match end {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val,
other => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes. Expecting a string or int".to_string(),
other.span().unwrap_or(head),
))
}
};
let start = vals.pop().expect("Already check has size 1");
let start = match start {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => val,
other => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes. Expecting a string or int".to_string(),
other.span().unwrap_or(head),
))
}
};
(start, end, span)
}
}
Value::String { val, span } => {
let splitted_result = val.split_once(',');
match splitted_result {
Some((start, end)) => (start.to_string(), end.to_string(), span),
None => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
span,
))
}
}
}
other => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
other.span().unwrap_or(head),
))
}
};
let start: isize = if start.is_empty() || start == "_" {
0
} else {
match start.trim().parse() {
Ok(s) => s,
Err(_) => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
span,
))
}
}
};
let end: isize = if end.is_empty() || end == "_" {
isize::max_value()
} else {
match end.trim().parse() {
Ok(s) => s,
Err(_) => {
return Err(ShellError::UnsupportedInput(
"could not perform subbytes".to_string(),
span,
))
}
}
};
Ok((start, end, span))
}
impl Command for BytesAt {
fn name(&self) -> &str {
"bytes at"
}
fn signature(&self) -> Signature {
Signature::build("bytes at")
.required("range", SyntaxShape::Any, "the indexes to get bytes")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally get bytes by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Get bytes defined by a range. Note that the start is included but the end is excluded, and that the first byte is index 0."
}
fn search_terms(&self) -> Vec<&str> {
vec!["slice"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let range: Value = call.req(engine_state, stack, 0)?;
let (start, end, arg_span) = parse_range(range, call.head)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let arg = Arguments {
start,
end,
arg_span,
column_paths,
};
operate(at, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get a subbytes `0x[10 01]` from the bytes `0x[33 44 55 10 01 13]`",
example: " 0x[33 44 55 10 01 13] | bytes at [3 4]",
result: Some(Value::Binary {
val: vec![0x10],
span: Span::test_data(),
}),
},
Example {
description: "Alternatively, you can use the form",
example: " 0x[33 44 55 10 01 13] | bytes at '3,4'",
result: Some(Value::Binary {
val: vec![0x10],
span: Span::test_data(),
}),
},
Example {
description: "Drop the last `n` characters from the string",
example: " 0x[33 44 55 10 01 13] | bytes at ',-3'",
result: Some(Value::Binary {
val: vec![0x33, 0x44, 0x55],
span: Span::test_data(),
}),
},
Example {
description: "Get the remaining characters from a starting index",
example: " 0x[33 44 55 10 01 13] | bytes at '3,'",
result: Some(Value::Binary {
val: vec![0x10, 0x01, 0x13],
span: Span::test_data(),
}),
},
Example {
description: "Get the characters from the beginning until ending index",
example: " 0x[33 44 55 10 01 13] | bytes at ',4'",
result: Some(Value::Binary {
val: vec![0x33, 0x44, 0x55, 0x10],
span: Span::test_data(),
}),
},
Example {
description:
"Or the characters from the beginning until ending index inside a table",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at "1," ColB ColC"#,
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
val: vec![0x11, 0x12, 0x13],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x15, 0x16],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn at(input: &[u8], arg: &Arguments, span: Span) -> Value {
let len: isize = input.len() as isize;
let start: isize = if arg.start < 0 {
arg.start + len
} else {
arg.start
};
let end: isize = if arg.end < 0 {
std::cmp::max(len + arg.end, 0)
} else {
arg.end
};
if start < len && end >= 0 {
match start.cmp(&end) {
Ordering::Equal => Value::Binary { val: vec![], span },
Ordering::Greater => Value::Error {
error: ShellError::UnsupportedInput(
"End must be greater than or equal to Start".to_string(),
arg.arg_span,
),
},
Ordering::Less => Value::Binary {
val: {
let input_iter = input.iter().copied().skip(start as usize);
if end == isize::max_value() {
input_iter.collect()
} else {
input_iter.take((end - start) as usize).collect()
}
},
span,
},
}
} else {
Value::Binary { val: vec![], span }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesAt {})
}
}

View File

@ -0,0 +1,81 @@
use nu_engine::eval_expression;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
};
#[derive(Clone)]
pub struct BytesBuild;
impl Command for BytesBuild {
fn name(&self) -> &str {
"bytes build"
}
fn usage(&self) -> &str {
"Create bytes from the arguments."
}
fn search_terms(&self) -> Vec<&str> {
vec!["concatenate", "join"]
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("bytes build")
.rest("rest", SyntaxShape::Any, "list of bytes")
.category(Category::Bytes)
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "bytes build 0x[01 02] 0x[03] 0x[04]",
description: "Builds binary data from 0x[01 02], 0x[03], 0x[04]",
result: Some(Value::Binary {
val: vec![0x01, 0x02, 0x03, 0x04],
span: Span::test_data(),
}),
}]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let mut output = vec![];
for expr in call.positional_iter() {
let val = eval_expression(engine_state, stack, expr)?;
match val {
Value::Binary { mut val, .. } => output.append(&mut val),
other => {
return Err(ShellError::UnsupportedInput(
"only support expression which yields to binary data".to_string(),
other.span().unwrap_or(call.head),
))
}
}
}
Ok(Value::Binary {
val: output,
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesBuild {})
}
}

View File

@ -0,0 +1,49 @@
use nu_engine::get_full_help;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, IntoPipelineData, PipelineData, Signature, Value,
};
#[derive(Clone)]
pub struct Bytes;
impl Command for Bytes {
fn name(&self) -> &str {
"bytes"
}
fn signature(&self) -> Signature {
Signature::build("bytes").category(Category::Bytes)
}
fn usage(&self) -> &str {
"Various commands for working with byte data"
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
Ok(Value::String {
val: get_full_help(&Bytes.signature(), &Bytes.examples(), engine_state, stack),
span: call.head,
}
.into_pipeline_data())
}
}
#[cfg(test)]
mod test {
use crate::Bytes;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Bytes {})
}
}

View File

@ -0,0 +1,127 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, SyntaxShape,
Value,
};
#[derive(Clone, Copy)]
pub struct BytesCollect;
impl Command for BytesCollect {
fn name(&self) -> &str {
"bytes collect"
}
fn signature(&self) -> Signature {
Signature::build("bytes collect")
.optional(
"separator",
SyntaxShape::Binary,
"optional separator to use when creating binary",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Concatenate multiple binary into a single binary, with an optional separator between each"
}
fn search_terms(&self) -> Vec<&str> {
vec!["join", "concatenate"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let separator: Option<Vec<u8>> = call.opt(engine_state, stack, 0)?;
// input should be a list of binary data.
let mut output_binary = vec![];
for value in input {
match value {
Value::Binary { mut val, .. } => {
output_binary.append(&mut val);
// manually concat
// TODO: make use of std::slice::Join when it's available in stable.
if let Some(sep) = &separator {
let mut work_sep = sep.clone();
output_binary.append(&mut work_sep)
}
}
other => {
return Err(ShellError::UnsupportedInput(
format!(
"The element type is {}, this command only works with bytes.",
other.get_type()
),
other.span().unwrap_or(call.head),
))
}
}
}
match separator {
None => Ok(Value::Binary {
val: output_binary,
span: call.head,
}
.into_pipeline_data()),
Some(sep) => {
if output_binary.is_empty() {
Ok(Value::Binary {
val: output_binary,
span: call.head,
}
.into_pipeline_data())
} else {
// have push one extra separator in previous step, pop them out.
for _ in sep {
let _ = output_binary.pop();
}
Ok(Value::Binary {
val: output_binary,
span: call.head,
}
.into_pipeline_data())
}
}
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Create a byte array from input",
example: "[0x[11] 0x[13 15]] | bytes collect",
result: Some(Value::Binary {
val: vec![0x11, 0x13, 0x15],
span: Span::test_data(),
}),
},
Example {
description: "Create a byte array from input with a separator",
example: "[0x[11] 0x[33] 0x[44]] | bytes collect 0x[01]",
result: Some(Value::Binary {
val: vec![0x11, 0x01, 0x33, 0x01, 0x44],
span: Span::test_data(),
}),
},
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesCollect {})
}
}

View File

@ -0,0 +1,116 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
struct Arguments {
pattern: Vec<u8>,
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesEndsWith;
impl Command for BytesEndsWith {
fn name(&self) -> &str {
"bytes ends-with"
}
fn signature(&self) -> Signature {
Signature::build("bytes ends-with")
.required("pattern", SyntaxShape::Binary, "the pattern to match")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally matches prefix of text by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Check if bytes ends with a pattern"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let arg = Arguments {
pattern,
column_paths,
};
operate(ends_with, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Checks if binary ends with `0x[AA]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[AA]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary ends with `0x[FF AA AA]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary ends with `0x[11]`",
example: "0x[1F FF AA AA] | bytes ends-with 0x[11]",
result: Some(Value::Bool {
val: false,
span: Span::test_data(),
}),
},
]
}
}
fn ends_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
Value::Bool {
val: input.ends_with(pattern),
span,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesEndsWith {})
}
}

View File

@ -0,0 +1,210 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::{Call, CellPath};
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
struct Arguments {
pattern: Vec<u8>,
end: bool,
all: bool,
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesIndexOf;
impl Command for BytesIndexOf {
fn name(&self) -> &str {
"bytes index-of"
}
fn signature(&self) -> Signature {
Signature::build("bytes index-of")
.required(
"pattern",
SyntaxShape::Binary,
"the pattern to find index of",
)
.rest(
"rest",
SyntaxShape::CellPath,
"optionally returns index of pattern in string by column paths",
)
.switch("all", "returns all matched index", Some('a'))
.switch("end", "search from the end of the binary", Some('e'))
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Returns start index of first occurrence of pattern in bytes, or -1 if no match"
}
fn search_terms(&self) -> Vec<&str> {
vec!["pattern", "match", "find", "search", "index"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let pattern: Vec<u8> = call.req(engine_state, stack, 0)?;
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let arg = Arguments {
pattern,
end: call.has_flag("end"),
all: call.has_flag("all"),
column_paths,
};
operate(index_of, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Returns index of pattern in bytes",
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55]",
result: Some(Value::test_int(1)),
},
Example {
description: "Returns index of pattern, search from end",
example: " 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55]",
result: Some(Value::test_int(6)),
},
Example {
description: "Returns all matched index",
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44]",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(5), Value::test_int(7)],
span: Span::test_data(),
}),
},
Example {
description: "Returns all matched index, searching from end",
example: " 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44]",
result: Some(Value::List {
vals: vec![Value::test_int(7), Value::test_int(5), Value::test_int(0)],
span: Span::test_data(),
}),
},
Example {
description: "Returns index of pattern for specific column",
example: r#" [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC"#,
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::test_int(0),
Value::Binary {
val: vec![0x14, 0x15, 0x16],
span: Span::test_data(),
},
Value::test_int(-1),
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn index_of(input: &[u8], arg: &Arguments, span: Span) -> Value {
if arg.all {
search_all_index(input, &arg.pattern, arg.end, span)
} else {
let mut iter = input.windows(arg.pattern.len());
if arg.end {
Value::Int {
val: iter
.rev()
.position(|sub_bytes| sub_bytes == arg.pattern)
.map(|x| (input.len() - arg.pattern.len() - x) as i64)
.unwrap_or(-1),
span,
}
} else {
Value::Int {
val: iter
.position(|sub_bytes| sub_bytes == arg.pattern)
.map(|x| x as i64)
.unwrap_or(-1),
span,
}
}
}
}
fn search_all_index(input: &[u8], pattern: &[u8], from_end: bool, span: Span) -> Value {
let mut result = vec![];
if from_end {
let (mut left, mut right) = (
input.len() as isize - pattern.len() as isize,
input.len() as isize,
);
while left >= 0 {
if &input[left as usize..right as usize] == pattern {
result.push(Value::Int {
val: left as i64,
span,
});
left -= pattern.len() as isize;
right -= pattern.len() as isize;
} else {
left -= 1;
right -= 1;
}
}
Value::List { vals: result, span }
} else {
// doing find stuff.
let (mut left, mut right) = (0, pattern.len());
let input_len = input.len();
let pattern_len = pattern.len();
while right <= input_len {
if &input[left..right] == pattern {
result.push(Value::Int {
val: left as i64,
span,
});
left += pattern_len;
right += pattern_len;
} else {
left += 1;
right += 1;
}
}
Value::List { vals: result, span }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesIndexOf {})
}
}

View File

@ -1,11 +1,32 @@
mod add;
mod at;
mod build_;
mod bytes_;
mod collect;
mod ends_with;
mod index_of;
mod length;
mod remove;
mod replace;
mod reverse;
mod starts_with;
use nu_protocol::ast::CellPath;
use nu_protocol::{PipelineData, ShellError, Span, Value};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
pub use add::BytesAdd;
pub use at::BytesAt;
pub use build_::BytesBuild;
pub use bytes_::Bytes;
pub use collect::BytesCollect;
pub use ends_with::BytesEndsWith;
pub use index_of::BytesIndexOf;
pub use length::BytesLen;
pub use remove::BytesRemove;
pub use replace::BytesReplace;
pub use reverse::BytesReverse;
pub use starts_with::BytesStartsWith;
trait BytesArgument {

View File

@ -0,0 +1,196 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
struct Arguments {
pattern: Vec<u8>,
end: bool,
column_paths: Option<Vec<CellPath>>,
all: bool,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesRemove;
impl Command for BytesRemove {
fn name(&self) -> &str {
"bytes remove"
}
fn signature(&self) -> Signature {
Signature::build("bytes remove")
.required("pattern", SyntaxShape::Binary, "the pattern to find")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally remove bytes by column paths",
)
.switch("end", "remove from end of binary", Some('e'))
.switch("all", "remove occurrences of finding binary", Some('a'))
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"remove bytes"
}
fn search_terms(&self) -> Vec<&str> {
vec!["search", "shift", "switch"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 1)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let pattern_to_remove = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if pattern_to_remove.item.is_empty() {
return Err(ShellError::UnsupportedInput(
"the pattern to remove cannot be empty".to_string(),
pattern_to_remove.span,
));
}
let pattern_to_remove: Vec<u8> = pattern_to_remove.item;
let arg = Arguments {
pattern: pattern_to_remove,
end: call.has_flag("end"),
column_paths,
all: call.has_flag("all"),
};
operate(remove, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Remove contents",
example: "0x[10 AA FF AA FF] | bytes remove 0x[10 AA]",
result: Some(Value::Binary {
val: vec![0xFF, 0xAA, 0xFF],
span: Span::test_data(),
}),
},
Example {
description: "Remove all occurrences of find binary",
example: "0x[10 AA 10 BB 10] | bytes remove -a 0x[10]",
result: Some(Value::Binary {
val: vec![0xAA, 0xBB],
span: Span::test_data(),
}),
},
Example {
description: "Remove occurrences of find binary from end",
example: "0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10]",
result: Some(Value::Binary {
val: vec![0x10, 0xAA, 0x10, 0xBB, 0xCC, 0xAA],
span: Span::test_data(),
}),
},
Example {
description: "Remove all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
val: vec![0x12, 0x13],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x14, 0x15, 0x16],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x17, 0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn remove(input: &[u8], arg: &Arguments, span: Span) -> Value {
let mut result = vec![];
let remove_all = arg.all;
let input_len = input.len();
let pattern_len = arg.pattern.len();
// Note:
// remove_all from start and end will generate the same result.
// so we'll put `remove_all` relative logic into else clouse.
if arg.end && !remove_all {
let (mut left, mut right) = (
input.len() as isize - arg.pattern.len() as isize,
input.len() as isize,
);
while left >= 0 && input[left as usize..right as usize] != arg.pattern {
result.push(input[right as usize - 1]);
left -= 1;
right -= 1;
}
// append the remaining thing to result, this can be happeneed when
// we have something to remove and remove_all is False.
let mut remain = input[..left as usize].iter().copied().rev().collect();
result.append(&mut remain);
result = result.into_iter().rev().collect();
Value::Binary { val: result, span }
} else {
let (mut left, mut right) = (0, arg.pattern.len());
while right <= input_len {
if input[left..right] == arg.pattern {
left += pattern_len;
right += pattern_len;
if !remove_all {
break;
}
} else {
result.push(input[left]);
left += 1;
right += 1;
}
}
// append the remaing thing to result, this can happened when
// we have something to remove and remove_all is False.
let mut remain = input[left..].to_vec();
result.append(&mut remain);
Value::Binary { val: result, span }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesRemove {})
}
}

View File

@ -0,0 +1,171 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
struct Arguments {
find: Vec<u8>,
replace: Vec<u8>,
column_paths: Option<Vec<CellPath>>,
all: bool,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesReplace;
impl Command for BytesReplace {
fn name(&self) -> &str {
"bytes replace"
}
fn signature(&self) -> Signature {
Signature::build("bytes replace")
.required("find", SyntaxShape::Binary, "the pattern to find")
.required("replace", SyntaxShape::Binary, "the replacement pattern")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally find and replace text by column paths",
)
.switch("all", "replace all occurrences of find binary", Some('a'))
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Find and replace binary"
}
fn search_terms(&self) -> Vec<&str> {
vec!["search", "shift", "switch"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 2)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let find = call.req::<Spanned<Vec<u8>>>(engine_state, stack, 0)?;
if find.item.is_empty() {
return Err(ShellError::UnsupportedInput(
"the pattern to find cannot be empty".to_string(),
find.span,
));
}
let arg = Arguments {
find: find.item,
replace: call.req::<Vec<u8>>(engine_state, stack, 1)?,
column_paths,
all: call.has_flag("all"),
};
operate(replace, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Find and replace contents",
example: "0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF]",
result: Some(Value::Binary {
val: vec![0xFF, 0xFF, 0xAA, 0xFF],
span: Span::test_data(),
}),
},
Example {
description: "Find and replace all occurrences of find binary",
example: "0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0]",
result: Some(Value::Binary {
val: vec![0xA0, 0xAA, 0xA0, 0xBB, 0xA0],
span: Span::test_data(),
}),
},
Example {
description: "Find and replace all occurrences of find binary in table",
example: "[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC",
result: Some(Value::List {
vals: vec![Value::Record {
cols: vec!["ColA".to_string(), "ColB".to_string(), "ColC".to_string()],
vals: vec![
Value::Binary {
val: vec![0x13, 0x12, 0x13],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x14, 0x15, 0x16],
span: Span::test_data(),
},
Value::Binary {
val: vec![0x17, 0x18, 0x19],
span: Span::test_data(),
},
],
span: Span::test_data(),
}],
span: Span::test_data(),
}),
},
]
}
}
fn replace(input: &[u8], arg: &Arguments, span: Span) -> Value {
let mut replaced = vec![];
let replace_all = arg.all;
// doing find-and-replace stuff.
let (mut left, mut right) = (0, arg.find.len());
let input_len = input.len();
let pattern_len = arg.find.len();
while right <= input_len {
if input[left..right] == arg.find {
let mut to_replace = arg.replace.clone();
replaced.append(&mut to_replace);
left += pattern_len;
right += pattern_len;
if !replace_all {
break;
}
} else {
replaced.push(input[left]);
left += 1;
right += 1;
}
}
let mut remain = input[left..].to_vec();
replaced.append(&mut remain);
Value::Binary {
val: replaced,
span,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesReplace {})
}
}

View File

@ -0,0 +1,104 @@
use super::{operate, BytesArgument};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::ast::CellPath;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::Category;
use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value};
struct Arguments {
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
#[derive(Clone)]
pub struct BytesReverse;
impl Command for BytesReverse {
fn name(&self) -> &str {
"bytes reverse"
}
fn signature(&self) -> Signature {
Signature::build("bytes reverse")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally matches prefix of text by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Reverse every bytes in the pipeline"
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "inverse", "flip"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
let column_paths = if column_paths.is_empty() {
None
} else {
Some(column_paths)
};
let arg = Arguments { column_paths };
operate(reverse, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Reverse bytes `0x[1F FF AA AA]`",
example: "0x[1F FF AA AA] | bytes reverse",
result: Some(Value::Binary {
val: vec![0xAA, 0xAA, 0xFF, 0x1F],
span: Span::test_data(),
}),
},
Example {
description: "Reverse bytes `0x[FF AA AA]`",
example: "0x[FF AA AA] | bytes reverse",
result: Some(Value::Binary {
val: vec![0xAA, 0xAA, 0xFF],
span: Span::test_data(),
}),
},
]
}
}
fn reverse(input: &[u8], _args: &Arguments, span: Span) -> Value {
let mut reversed_input = input.to_vec();
reversed_input.reverse();
Value::Binary {
val: reversed_input,
span,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesReverse {})
}
}

View File

@ -28,6 +28,10 @@ impl Command for SubCommand {
"Convert value to duration"
}
fn extra_usage(&self) -> &str {
"into duration does not take leap years into account and every month is calculated with 30 days"
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "time", "period"]
}
@ -136,7 +140,7 @@ fn into_duration(
)
}
fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
fn string_to_duration(s: &str, span: Span, value_span: Span) -> Result<i64, ShellError> {
if let Some(expression) = parse_duration_bytes(s.as_bytes(), span) {
if let Expr::ValueWithUnit(value, unit) = expression.expr {
if let Expr::Int(x) = value.expr {
@ -149,16 +153,21 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
Unit::Hour => return Ok(x * 60 * 60 * 1000 * 1000 * 1000),
Unit::Day => return Ok(x * 24 * 60 * 60 * 1000 * 1000 * 1000),
Unit::Week => return Ok(x * 7 * 24 * 60 * 60 * 1000 * 1000 * 1000),
Unit::Month => return Ok(x * 30 * 24 * 60 * 60 * 1000 * 1000 * 1000), //30 days to a month
Unit::Year => return Ok(x * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
Unit::Decade => return Ok(x * 10 * 365 * 24 * 60 * 60 * 1000 * 1000 * 1000), //365 days to a year
_ => {}
}
}
}
}
Err(ShellError::CantConvert(
Err(ShellError::CantConvertWithValue(
"duration".to_string(),
"string".to_string(),
s.to_string(),
span,
value_span,
Some("supported units are ns, us, ms, sec, min, hr, day, and wk".to_string()),
))
}
@ -166,7 +175,10 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
fn action(input: &Value, span: Span) -> Value {
match input {
Value::Duration { .. } => input.clone(),
Value::String { val, .. } => match string_to_duration(val, span) {
Value::String {
val,
span: value_span,
} => match string_to_duration(val, span, *value_span) {
Ok(val) => Value::Duration { val, span },
Err(error) => Value::Error { error },
},

View File

@ -102,6 +102,21 @@ impl Command for SubCommand {
example: "'FF' | into int -r 16",
result: Some(Value::test_int(255)),
},
Example {
description: "Convert octal string to integer",
example: "'0o10132' | into int",
result: Some(Value::test_int(4186)),
},
Example {
description: "Convert 0 padded string to integer",
example: "'0010132' | into int",
result: Some(Value::test_int(10132)),
},
Example {
description: "Convert 0 padded string to integer with radix",
example: "'0010132' | into int -r 8",
result: Some(Value::test_int(4186)),
},
]
}
}
@ -169,7 +184,32 @@ pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Val
}
Value::Filesize { val, .. } => Value::Int { val: *val, span },
Value::Float { val, .. } => Value::Int {
val: *val as i64,
val: {
if radix == 10 {
*val as i64
} else {
match convert_int(
&Value::Int {
val: *val as i64,
span,
},
span,
radix,
)
.as_i64()
{
Ok(v) => v,
_ => {
return Value::Error {
error: ShellError::UnsupportedInput(
"Could not convert float to integer".to_string(),
span,
),
}
}
}
}
},
span,
},
Value::String { val, .. } => {
@ -233,11 +273,31 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
let i = match input {
Value::Int { val, .. } => val.to_string(),
Value::String { val, .. } => {
if val.starts_with("0x") || val.starts_with("0b") {
let val = val.trim();
if val.starts_with("0x") // hex
|| val.starts_with("0b") // binary
|| val.starts_with("0o")
// octal
{
match int_from_string(val, head) {
Ok(x) => return Value::Int { val: x, span: head },
Err(e) => return Value::Error { error: e },
}
} else if val.starts_with("00") {
// It's a padded string
match i64::from_str_radix(val, radix) {
Ok(n) => return Value::Int { val: n, span: head },
Err(e) => {
return Value::Error {
error: ShellError::CantConvert(
"string".to_string(),
"int".to_string(),
head,
Some(e.to_string()),
),
}
}
}
}
val.to_string()
}
@ -250,10 +310,10 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
}
}
};
match i64::from_str_radix(&i, radix) {
match i64::from_str_radix(i.trim(), radix) {
Ok(n) => Value::Int { val: n, span: head },
Err(_reason) => Value::Error {
error: ShellError::CantConvert("int".to_string(), "string".to_string(), head, None),
error: ShellError::CantConvert("string".to_string(), "int".to_string(), head, None),
},
}
}
@ -291,7 +351,21 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
};
Ok(num)
}
_ => match a_string.parse::<i64>() {
o if o.starts_with("0o") => {
let num = match i64::from_str_radix(o.trim_start_matches("0o"), 8) {
Ok(n) => n,
Err(_reason) => {
return Err(ShellError::CantConvert(
"int".to_string(),
"string".to_string(),
span,
Some(r#"octal digits following "0o" should be in 0-7"#.to_string()),
))
}
};
Ok(num)
}
_ => match trimmed.parse::<i64>() {
Ok(n) => Ok(n),
Err(_) => match a_string.parse::<f64>() {
Ok(f) => Ok(f as i64),
@ -299,7 +373,10 @@ fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {
"int".to_string(),
"string".to_string(),
span,
None,
Some(format!(
r#"string "{}" does not represent a valid integer"#,
trimmed
)),
)),
},
},

View File

@ -53,6 +53,14 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "convert integer to string and append three decimal places",
example: "5 | into string -d 3",
result: Some(Value::String {
val: "5.000".to_string(),
span: Span::test_data(),
}),
},
Example {
description: "convert decimal to string and round to nearest integer",
example: "1.7 | into string -d 0",
@ -210,6 +218,15 @@ pub fn action(
Value::Int { val, .. } => {
let res = if group_digits {
format_int(*val) // int.to_formatted_string(*locale)
} else if let Some(dig) = digits {
let mut val_with_trailing_zeroes = val.to_string();
if dig != 0 {
val_with_trailing_zeroes.push('.');
}
for _ in 0..dig {
val_with_trailing_zeroes.push('0');
}
val_with_trailing_zeroes
} else {
val.to_string()
};

View File

@ -16,6 +16,11 @@ impl Command for ErrorMake {
fn signature(&self) -> Signature {
Signature::build("error make")
.required("error_struct", SyntaxShape::Record, "the error to create")
.switch(
"unspanned",
"remove the origin label from the error",
Some('u'),
)
.category(Category::Core)
}
@ -36,16 +41,29 @@ impl Command for ErrorMake {
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head;
let arg: Value = call.req(engine_state, stack, 0)?;
let unspanned = call.has_flag("unspanned");
Err(make_error(&arg, span).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
if unspanned {
Err(make_error(&arg, None).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
} else {
Err(make_error(&arg, Some(span)).unwrap_or_else(|| {
ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
)
}))
}
}
fn examples(&self) -> Vec<Example> {
@ -69,7 +87,7 @@ impl Command for ErrorMake {
}
}
fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
fn make_error(value: &Value, throw_span: Option<Span>) -> Option<ShellError> {
if let Value::Record { .. } = &value {
let msg = value.get_data_by_key("msg");
let label = value.get_data_by_key("label");
@ -106,7 +124,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
) => Some(ShellError::GenericError(
message,
label_text,
Some(throw_span),
throw_span,
None,
Vec::new(),
)),
@ -116,7 +134,7 @@ fn make_error(value: &Value, throw_span: Span) -> Option<ShellError> {
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
message,
"originates from here".to_string(),
Some(throw_span),
throw_span,
None,
Vec::new(),
)),

View File

@ -1,12 +1,15 @@
use nu_ansi_term::{
Color::{Default, Red, White},
Style,
};
use nu_color_config::get_color_config;
use nu_engine::{get_full_help, CallExt};
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
span, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
ShellError, Signature, Spanned, SyntaxShape, Value,
ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use nu_engine::{get_full_help, CallExt};
use std::borrow::Borrow;
#[derive(Clone)]
@ -81,23 +84,29 @@ fn help(
let head = call.head;
let find: Option<Spanned<String>> = call.get_flag(engine_state, stack, "find")?;
let rest: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
let commands = engine_state.get_decl_ids_sorted(false);
let config = engine_state.get_config();
let color_hm = get_color_config(config);
let default_style = Style::new().fg(Default).on(Default);
let string_style = match color_hm.get("string") {
Some(style) => style,
None => &default_style,
};
if let Some(f) = find {
let org_search_string = f.item.clone();
let search_string = f.item.to_lowercase();
let mut found_cmds_vec = Vec::new();
for decl_id in commands {
let mut cols = vec![];
let mut vals = vec![];
let decl = engine_state.get_decl(decl_id);
let sig = decl.signature().update_from_command(decl.borrow());
let key = sig.name;
let usage = sig.usage;
let search_terms = sig.search_terms;
let matches_term = if !search_terms.is_empty() {
search_terms
.iter()
@ -106,13 +115,16 @@ fn help(
false
};
if key.to_lowercase().contains(&search_string)
|| usage.to_lowercase().contains(&search_string)
|| matches_term
{
let key_match = key.to_lowercase().contains(&search_string);
let usage_match = usage.to_lowercase().contains(&search_string);
if key_match || usage_match || matches_term {
cols.push("name".into());
vals.push(Value::String {
val: key,
val: if key_match {
highlight_search_string(&key, &org_search_string, string_style)?
} else {
key
},
span: head,
});
@ -142,7 +154,11 @@ fn help(
cols.push("usage".into());
vals.push(Value::String {
val: usage,
val: if usage_match {
highlight_search_string(&usage, &org_search_string, string_style)?
} else {
usage
},
span: head,
});
@ -151,7 +167,30 @@ fn help(
Value::nothing(head)
} else {
Value::String {
val: search_terms.join(", "),
val: if matches_term {
search_terms
.iter()
.map(|term| {
if term.to_lowercase().contains(&search_string) {
match highlight_search_string(
term,
&org_search_string,
string_style,
) {
Ok(s) => s,
Err(_) => {
string_style.paint(term.to_string()).to_string()
}
}
} else {
string_style.paint(term.to_string()).to_string()
}
})
.collect::<Vec<_>>()
.join(", ")
} else {
search_terms.join(", ")
},
span: head,
}
});
@ -303,3 +342,48 @@ You can also learn more at https://www.nushell.sh/book/"#;
.into_pipeline_data())
}
}
// Highlight the search string using ANSI escape sequences and regular expressions.
pub fn highlight_search_string(
haystack: &str,
needle: &str,
string_style: &Style,
) -> Result<String, ShellError> {
let regex_string = format!("(?i){}", needle);
let regex = match regex::Regex::new(&regex_string) {
Ok(regex) => regex,
Err(err) => {
return Err(ShellError::GenericError(
"Could not compile regex".into(),
err.to_string(),
Some(Span::test_data()),
None,
Vec::new(),
));
}
};
let mut last_match_end = 0;
let style = Style::new().fg(White).on(Red);
let mut highlighted = String::new();
for cap in regex.captures_iter(haystack) {
let start = match cap.get(0) {
Some(cap) => cap.start(),
None => 0,
};
let end = match cap.get(0) {
Some(cap) => cap.end(),
None => 0,
};
highlighted.push_str(
&string_style
.paint(&haystack[last_match_end..start])
.to_string(),
);
highlighted.push_str(&style.paint(&haystack[start..end]).to_string());
last_match_end = end;
}
highlighted.push_str(&string_style.paint(&haystack[last_match_end..]).to_string());
Ok(highlighted)
}

View File

@ -92,6 +92,7 @@ impl Command for If {
call.redirect_stdout,
call.redirect_stderr,
)
.map(|res| res.0)
}
} else {
eval_expression_with_input(
@ -102,6 +103,7 @@ impl Command for If {
call.redirect_stdout,
call.redirect_stderr,
)
.map(|res| res.0)
}
} else {
Ok(PipelineData::new(call.head))

View File

@ -35,6 +35,10 @@ impl Command for Let {
true
}
fn search_terms(&self) -> Vec<&str> {
vec!["set", "const"]
}
fn run(
&self,
engine_state: &EngineState,
@ -61,7 +65,8 @@ impl Command for Let {
input,
call.redirect_stdout,
call.redirect_stderr,
)?;
)?
.0;
//println!("Adding: {:?} to {}", rhs, var_id);

View File

@ -14,7 +14,7 @@ mod export_env;
mod export_extern;
mod extern_;
mod for_;
mod help;
pub mod help;
mod hide;
mod if_;
mod ignore;

View File

@ -1,9 +1,7 @@
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value,
};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape};
#[derive(Clone)]
pub struct OverlayRemove;
@ -22,9 +20,15 @@ impl Command for OverlayRemove {
.optional("name", SyntaxShape::String, "Overlay to remove")
.switch(
"keep-custom",
"Keep newly added symbols within the next activated overlay",
"Keep all newly added symbols within the next activated overlay",
Some('k'),
)
.named(
"keep-env",
SyntaxShape::List(Box::new(SyntaxShape::String)),
"List of environment variables to keep from the removed overlay",
Some('e'),
)
.category(Category::Core)
}
@ -60,30 +64,44 @@ impl Command for OverlayRemove {
));
}
if call.has_flag("keep-custom") {
let keep_env: Option<Vec<Spanned<String>>> =
call.get_flag(engine_state, stack, "keep-env")?;
let env_vars_to_keep = if call.has_flag("keep-custom") {
if let Some(overlay_id) = engine_state.find_overlay(overlay_name.item.as_bytes()) {
let overlay_frame = engine_state.get_overlay(overlay_id);
let origin_module = engine_state.get_module(overlay_frame.origin);
let env_vars_to_keep: Vec<(String, Value)> = stack
stack
.get_overlay_env_vars(engine_state, &overlay_name.item)
.into_iter()
.filter(|(name, _)| !origin_module.has_env_var(name.as_bytes()))
.collect();
stack.remove_overlay(&overlay_name.item);
for (name, val) in env_vars_to_keep {
stack.add_env_var(name, val);
}
.collect()
} else {
return Err(ShellError::OverlayNotFoundAtRuntime(
overlay_name.item,
overlay_name.span,
));
}
} else if let Some(env_var_names_to_keep) = keep_env {
let mut env_vars_to_keep = vec![];
for name in env_var_names_to_keep.into_iter() {
match stack.get_env_var(engine_state, &name.item) {
Some(val) => env_vars_to_keep.push((name.item, val.clone())),
None => return Err(ShellError::EnvVarNotFoundAtRuntime(name.item, name.span)),
}
}
env_vars_to_keep
} else {
stack.remove_overlay(&overlay_name.item);
vec![]
};
stack.remove_overlay(&overlay_name.item);
for (name, val) in env_vars_to_keep {
stack.add_env_var(name, val);
}
Ok(PipelineData::new(call.head))
@ -112,6 +130,13 @@ impl Command for OverlayRemove {
overlay remove"#,
result: None,
},
Example {
description: "Keep the current working directory when removing an overlay",
example: r#"overlay new spam
cd some-dir
overlay remove --keep-env [ PWD ] spam"#,
result: None,
},
]
}
}

View File

@ -69,11 +69,6 @@ impl Command for Source {
example: r#"source ./foo.nu; say-hi"#,
result: None,
},
Example {
description: "Runs foo.nu in current context and call the `main` command automatically, suppose foo.nu has content: `def main [] { echo 'Hi!' }`",
example: r#"source ./foo.nu"#,
result: None,
},
]
}
}

View File

@ -75,8 +75,9 @@ pub fn test_database(cmds: Vec<Box<dyn Command + 'static>>) {
working_set.render()
};
let cwd = std::env::current_dir().expect("Could not get current working directory.");
let _ = engine_state.merge_delta(delta, None, &cwd);
engine_state
.merge_delta(delta)
.expect("Error merging delta");
for example in examples {
// Skip tests that don't have results to compare to
@ -102,7 +103,9 @@ pub fn test_database(cmds: Vec<Box<dyn Command + 'static>>) {
(output, working_set.render())
};
let _ = engine_state.merge_delta(delta, None, &cwd);
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let mut stack = Stack::new();

View File

@ -0,0 +1,106 @@
use crate::dataframe::values::{Column, NuDataFrame, NuExpression};
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use polars::prelude::concat_str;
#[derive(Clone)]
pub struct ExprConcatStr;
impl Command for ExprConcatStr {
fn name(&self) -> &str {
"concat-str"
}
fn usage(&self) -> &str {
"Creates a concat string expression"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.required(
"separator",
SyntaxShape::String,
"Separator used during the concatenation",
)
.required(
"concat expressions",
SyntaxShape::List(Box::new(SyntaxShape::Any)),
"Expression(s) that define the string concatenation",
)
.input_type(Type::Any)
.output_type(Type::Custom("expression".into()))
.category(Category::Custom("expression".into()))
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Creates a concat string expression",
example: r#"let df = ([[a b c]; [one two 1] [three four 2]] | into df);
$df | with-column ((concat-str "-" [(col a) (col b) ((col c) * 2)]) | as concat)"#,
result: Some(
NuDataFrame::try_from_columns(vec![
Column::new(
"a".to_string(),
vec![Value::test_string("one"), Value::test_string("three")],
),
Column::new(
"b".to_string(),
vec![Value::test_string("two"), Value::test_string("four")],
),
Column::new(
"c".to_string(),
vec![Value::test_int(1), Value::test_int(2)],
),
Column::new(
"concat".to_string(),
vec![
Value::test_string("one-two-2"),
Value::test_string("three-four-4"),
],
),
])
.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> {
let separator: String = call.req(engine_state, stack, 0)?;
let value: Value = call.req(engine_state, stack, 1)?;
let expressions = NuExpression::extract_exprs(value)?;
let expr: NuExpression = concat_str(expressions, &separator).into();
Ok(PipelineData::Value(expr.into_value(call.head), None))
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;
use super::*;
use crate::dataframe::eager::WithColumn;
use crate::dataframe::expressions::alias::ExprAlias;
use crate::dataframe::expressions::col::ExprCol;
#[test]
fn test_examples() {
test_dataframe(vec![
Box::new(ExprConcatStr {}),
Box::new(ExprAlias {}),
Box::new(ExprCol {}),
Box::new(WithColumn {}),
])
}
}

View File

@ -1,6 +1,7 @@
mod alias;
mod as_nu;
mod col;
mod concat_str;
mod expressions_macro;
mod lit;
mod otherwise;
@ -12,6 +13,7 @@ use nu_protocol::engine::StateWorkingSet;
pub(crate) use crate::dataframe::expressions::alias::ExprAlias;
use crate::dataframe::expressions::as_nu::ExprAsNu;
pub(super) use crate::dataframe::expressions::col::ExprCol;
pub(super) use crate::dataframe::expressions::concat_str::ExprConcatStr;
pub(crate) use crate::dataframe::expressions::expressions_macro::*;
pub(super) use crate::dataframe::expressions::lit::ExprLit;
pub(super) use crate::dataframe::expressions::otherwise::ExprOtherwise;
@ -32,6 +34,7 @@ pub fn add_expressions(working_set: &mut StateWorkingSet) {
bind_command!(
ExprAlias,
ExprCol,
ExprConcatStr,
ExprCount,
ExprLit,
ExprAsNu,

View File

@ -6,6 +6,7 @@ use nu_protocol::{
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, Value,
};
use polars::{datatypes::DataType, prelude::Expr};
#[derive(Clone)]
pub struct LazyAggregate;
@ -118,12 +119,29 @@ impl Command for LazyAggregate {
let expressions = NuExpression::extract_exprs(value)?;
let group_by = NuLazyGroupBy::try_from_pipeline(input, call.head)?;
let from_eager = group_by.from_eager;
let group_by = group_by.into_polars();
if let Some(schema) = &group_by.schema {
for expr in &expressions {
if let Some(name) = get_col_name(expr) {
let dtype = schema.get(name.as_str());
if matches!(dtype, Some(DataType::Object(..))) {
return Err(ShellError::GenericError(
"Object type column not supported for aggregation".into(),
format!("Column '{}' is type Object", name),
Some(call.head),
Some("Aggregations cannot be performed on Object type columns. Use dtype command to check column types".into()),
Vec::new(),
));
}
}
}
}
let lazy = NuLazyFrame {
lazy: group_by.agg(&expressions).into(),
from_eager,
from_eager: group_by.from_eager,
lazy: Some(group_by.into_polars().agg(&expressions)),
schema: None,
};
let res = lazy.into_value(call.head)?;
@ -131,6 +149,57 @@ impl Command for LazyAggregate {
}
}
fn get_col_name(expr: &Expr) -> Option<String> {
match expr {
Expr::Column(column) => Some(column.to_string()),
Expr::Agg(agg) => match agg {
polars::prelude::AggExpr::Min(e)
| polars::prelude::AggExpr::Max(e)
| polars::prelude::AggExpr::Median(e)
| polars::prelude::AggExpr::NUnique(e)
| polars::prelude::AggExpr::First(e)
| polars::prelude::AggExpr::Last(e)
| polars::prelude::AggExpr::Mean(e)
| polars::prelude::AggExpr::List(e)
| polars::prelude::AggExpr::Count(e)
| polars::prelude::AggExpr::Sum(e)
| polars::prelude::AggExpr::AggGroups(e)
| polars::prelude::AggExpr::Std(e)
| polars::prelude::AggExpr::Var(e) => get_col_name(e.as_ref()),
polars::prelude::AggExpr::Quantile { expr, .. } => get_col_name(expr.as_ref()),
},
Expr::Reverse(expr)
| Expr::Shift { input: expr, .. }
| Expr::Filter { input: expr, .. }
| Expr::Slice { input: expr, .. }
| Expr::Cast { expr, .. }
| Expr::Sort { expr, .. }
| Expr::Take { expr, .. }
| Expr::SortBy { expr, .. }
| Expr::Exclude(expr, _)
| Expr::Alias(expr, _)
| Expr::KeepName(expr)
| Expr::Not(expr)
| Expr::IsNotNull(expr)
| Expr::IsNull(expr)
| Expr::Duplicated(expr)
| Expr::IsUnique(expr)
| Expr::Explode(expr) => get_col_name(expr.as_ref()),
Expr::Ternary { .. }
| Expr::AnonymousFunction { .. }
| Expr::Function { .. }
| Expr::Columns(_)
| Expr::DtypeColumn(_)
| Expr::Literal(_)
| Expr::BinaryExpr { .. }
| Expr::Window { .. }
| Expr::Wildcard
| Expr::RenameAlias { .. }
| Expr::Count
| Expr::Nth(_) => None,
}
}
#[cfg(test)]
mod test {
use super::super::super::test_dataframe::test_dataframe;

View File

@ -128,13 +128,11 @@ impl Command for ToLazyGroupBy {
));
}
let value = input.into_value(call.head);
let lazy = NuLazyFrame::try_from_value(value)?;
let from_eager = lazy.from_eager;
let lazy = NuLazyFrame::try_from_pipeline(input, call.head)?;
let group_by = NuLazyGroupBy {
schema: lazy.schema.clone(),
from_eager: lazy.from_eager,
group_by: Some(lazy.into_polars().groupby(&expressions)),
from_eager,
};
Ok(PipelineData::Value(group_by.into_value(call.head), None))

View File

@ -37,8 +37,9 @@ pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
working_set.render()
};
let cwd = std::env::current_dir().expect("Could not get current working directory.");
let _ = engine_state.merge_delta(delta, None, &cwd);
engine_state
.merge_delta(delta)
.expect("Error merging delta");
for example in examples {
// Skip tests that don't have results to compare to
@ -64,7 +65,9 @@ pub fn test_dataframe(cmds: Vec<Box<dyn Command + 'static>>) {
(output, working_set.render())
};
let _ = engine_state.merge_delta(delta, None, &cwd);
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let mut stack = Stack::new();

View File

@ -226,6 +226,7 @@ pub(super) fn compute_series_single_value(
Value::Float { val, .. } => {
compute_series_decimal(&lhs, *val, <ChunkedArray<Float64Type>>::add, lhs_span)
}
Value::String { val, .. } => add_string_to_series(&lhs, val, lhs_span),
_ => Err(ShellError::OperatorMismatch {
op_span: operator.span,
lhs_ty: left.get_type(),
@ -758,3 +759,22 @@ fn contains_series_pat(series: &Series, pat: &str, span: Span) -> Result<Value,
)),
}
}
fn add_string_to_series(series: &Series, pat: &str, span: Span) -> Result<Value, ShellError> {
let casted = series.utf8();
match casted {
Ok(casted) => {
let res = casted + pat;
let res = res.into_series();
NuDataFrame::series_to_value(res, span)
}
Err(e) => Err(ShellError::GenericError(
"Unable to cast to string".into(),
e.to_string(),
Some(span),
None,
Vec::new(),
)),
}
}

View File

@ -15,6 +15,7 @@ impl CustomValue for NuLazyFrame {
let cloned = NuLazyFrame {
lazy: self.lazy.clone(),
from_eager: self.from_eager,
schema: self.schema.clone(),
};
Value::CustomValue {

View File

@ -3,7 +3,7 @@ mod custom_value;
use super::{NuDataFrame, NuExpression};
use core::fmt;
use nu_protocol::{PipelineData, ShellError, Span, Value};
use polars::prelude::{Expr, IntoLazy, LazyFrame};
use polars::prelude::{Expr, IntoLazy, LazyFrame, Schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
// Lazyframe wrapper for Nushell operations
@ -12,6 +12,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Default)]
pub struct NuLazyFrame {
pub lazy: Option<LazyFrame>,
pub schema: Option<Schema>,
pub from_eager: bool,
}
@ -63,6 +64,7 @@ impl From<LazyFrame> for NuLazyFrame {
Self {
lazy: Some(lazy_frame),
from_eager: false,
schema: None,
}
}
}
@ -72,6 +74,7 @@ impl NuLazyFrame {
Self {
lazy: Some(lazy),
from_eager,
schema: None,
}
}
@ -80,6 +83,7 @@ impl NuLazyFrame {
Self {
lazy: Some(lazy),
from_eager: true,
schema: Some(df.as_ref().schema()),
}
}
@ -148,6 +152,7 @@ impl NuLazyFrame {
Some(expr) => Ok(Self {
lazy: expr.lazy.clone(),
from_eager: false,
schema: None,
}),
None => Err(ShellError::CantConvert(
"lazy frame".into(),
@ -184,6 +189,7 @@ impl NuLazyFrame {
Self {
from_eager: self.from_eager,
lazy: Some(new_frame),
schema: None,
}
}
}

View File

@ -14,6 +14,7 @@ impl CustomValue for NuLazyGroupBy {
fn clone_value(&self, span: nu_protocol::Span) -> Value {
let cloned = NuLazyGroupBy {
group_by: self.group_by.clone(),
schema: self.schema.clone(),
from_eager: self.from_eager,
};

View File

@ -2,7 +2,7 @@ mod custom_value;
use core::fmt;
use nu_protocol::{PipelineData, ShellError, Span, Value};
use polars::prelude::LazyGroupBy;
use polars::prelude::{LazyGroupBy, Schema};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
// Lazyframe wrapper for Nushell operations
@ -11,6 +11,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Default)]
pub struct NuLazyGroupBy {
pub group_by: Option<LazyGroupBy>,
pub schema: Option<Schema>,
pub from_eager: bool,
}
@ -66,6 +67,7 @@ impl From<LazyGroupBy> for NuLazyGroupBy {
Self {
group_by: Some(group_by),
from_eager: false,
schema: None,
}
}
}
@ -88,6 +90,7 @@ impl NuLazyGroupBy {
match val.as_any().downcast_ref::<NuLazyGroupBy>() {
Some(group) => Ok(Self {
group_by: group.group_by.clone(),
schema: group.schema.clone(),
from_eager: group.from_eager,
}),
None => Err(ShellError::CantConvert(

View File

@ -1,10 +1,8 @@
use nu_protocol::engine::{EngineState, StateWorkingSet};
use std::path::Path;
use crate::*;
pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
pub fn create_default_context() -> EngineState {
let mut engine_state = EngineState::new();
let delta = {
@ -121,6 +119,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
SkipWhile,
Sort,
SortBy,
SplitList,
Transpose,
Uniq,
Upsert,
@ -209,8 +208,18 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
// Bytes
bind_command! {
Bytes,
BytesLen,
BytesStartsWith
BytesStartsWith,
BytesEndsWith,
BytesReverse,
BytesReplace,
BytesAdd,
BytesAt,
BytesIndexOf,
BytesCollect,
BytesRemove,
BytesBuild,
}
// FileSystem
@ -333,6 +342,7 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
WithEnv,
ConfigNu,
ConfigEnv,
ConfigReset,
ConfigMeta,
};
@ -422,7 +432,9 @@ pub fn create_default_context(cwd: impl AsRef<Path>) -> EngineState {
working_set.render()
};
let _ = engine_state.merge_delta(delta, None, &cwd);
if let Err(err) = engine_state.merge_delta(delta) {
eprintln!("Error creating default context: {:?}", err);
}
engine_state
}

View File

@ -0,0 +1,112 @@
use chrono::Local;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature,
};
use std::io::Write;
#[derive(Clone)]
pub struct ConfigReset;
impl Command for ConfigReset {
fn name(&self) -> &str {
"config reset"
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.switch("nu", "reset only nu config, config.nu", Some('n'))
.switch("env", "reset only env config, env.nu", Some('e'))
.switch("without-backup", "do not make a backup", Some('w'))
.category(Category::Env)
}
fn usage(&self) -> &str {
"Reset nushell environment configurations to default, and saves old config files in the config location as oldconfig.nu and oldenv.nu"
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "reset nushell configuration files",
example: "config reset",
result: None,
}]
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let only_nu = call.has_flag("nu");
let only_env = call.has_flag("env");
let no_backup = call.has_flag("without-backup");
let span = call.head;
let mut config_path = match nu_path::config_dir() {
Some(path) => path,
None => {
return Err(ShellError::GenericError(
"Could not find config path".to_string(),
"Could not find config path".to_string(),
None,
None,
Vec::new(),
));
}
};
config_path.push("nushell");
if !only_env {
let mut nu_config = config_path.clone();
nu_config.push("config.nu");
let config_file = include_str!("../../../../../docs/sample_config/default_config.nu");
if !no_backup {
let mut backup_path = config_path.clone();
backup_path.push(format!(
"oldconfig-{}.nu",
Local::now().format("%F-%H-%M-%S"),
));
if std::fs::rename(nu_config.clone(), backup_path).is_err() {
return Err(ShellError::FileNotFoundCustom(
"config.nu could not be backed up".into(),
span,
));
}
}
if let Ok(mut file) = std::fs::File::create(nu_config) {
if writeln!(&mut file, "{}", config_file).is_err() {
return Err(ShellError::FileNotFoundCustom(
"config.nu could not be written to".into(),
span,
));
}
}
}
if !only_nu {
let mut env_config = config_path.clone();
env_config.push("env.nu");
let config_file = include_str!("../../../../../docs/sample_config/default_env.nu");
if !no_backup {
let mut backup_path = config_path.clone();
backup_path.push(format!("oldenv-{}.nu", Local::now().format("%F-%H-%M-%S"),));
if std::fs::rename(env_config.clone(), backup_path).is_err() {
return Err(ShellError::FileNotFoundCustom(
"env.nu could not be backed up".into(),
span,
));
}
}
if let Ok(mut file) = std::fs::File::create(env_config) {
if writeln!(&mut file, "{}", config_file).is_err() {
return Err(ShellError::FileNotFoundCustom(
"env.nu could not be written to".into(),
span,
));
}
}
}
Ok(PipelineData::new(span))
}
}

View File

@ -1,7 +1,9 @@
mod config_;
mod config_env;
mod config_nu;
mod config_reset;
mod utils;
pub use config_::ConfigMeta;
pub use config_env::ConfigEnv;
pub use config_nu::ConfigNu;
pub use config_reset::ConfigReset;

View File

@ -43,6 +43,7 @@ impl Command for LetEnv {
let rhs =
eval_expression_with_input(engine_state, stack, keyword_expr, input, false, true)?
.0
.into_value(call.head);
if env_var == "PWD" {

View File

@ -7,6 +7,7 @@ mod with_env;
pub use config::ConfigEnv;
pub use config::ConfigMeta;
pub use config::ConfigNu;
pub use config::ConfigReset;
pub use env_command::Env;
pub use let_env::LetEnv;
pub use load_env::LoadEnv;

View File

@ -57,7 +57,10 @@ pub fn test_examples(cmd: impl Command + 'static) {
};
let cwd = std::env::current_dir().expect("Could not get current working directory.");
let _ = engine_state.merge_delta(delta, None, &cwd);
engine_state
.merge_delta(delta)
.expect("Error merging delta");
for example in examples {
// Skip tests that don't have results to compare to
@ -76,11 +79,10 @@ pub fn test_examples(cmd: impl Command + 'static) {
span: Span::test_data(),
},
);
let _ = engine_state.merge_delta(
StateWorkingSet::new(&*engine_state).render(),
Some(&mut stack),
&cwd,
);
engine_state
.merge_env(&mut stack, &cwd)
.expect("Error merging environment");
let (block, delta) = {
let mut working_set = StateWorkingSet::new(&*engine_state);
@ -99,7 +101,9 @@ pub fn test_examples(cmd: impl Command + 'static) {
(output, working_set.render())
};
let _ = engine_state.merge_delta(delta, None, &cwd);
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let mut stack = Stack::new();

View File

@ -1,3 +1,4 @@
use itertools::Itertools;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
@ -131,6 +132,15 @@ impl Command for ViewSource {
Vec::new(),
))
}
} else if let Some(alias_id) = engine_state.find_alias(val.as_bytes(), &[]) {
let contents = &mut engine_state.get_alias(alias_id).iter().map(|span| {
String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string()
});
Ok(Value::String {
val: contents.join(" "),
span: call.head,
}
.into_pipeline_data())
} else {
Err(ShellError::GenericError(
"Cannot view value".to_string(),
@ -185,6 +195,14 @@ impl Command for ViewSource {
span: Span::test_data(),
}),
},
Example {
description: "View the source of an alias",
example: r#"alias hello = echo hi; view-source hello"#,
result: Some(Value::String {
val: "echo hi".to_string(),
span: Span::test_data(),
}),
},
]
}
}

View File

@ -1,11 +1,16 @@
use std::collections::HashMap;
use std::fs::read_link;
use std::path::PathBuf;
use itertools::Itertools;
use nu_engine::env::current_dir;
use nu_engine::CallExt;
use nu_glob::GlobResult;
use nu_path::dots::expand_ndots;
use nu_path::{canonicalize_with, expand_path_with};
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::span::span as merge_spans;
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
Spanned, SyntaxShape, Value,
@ -41,7 +46,12 @@ impl Command for Cp {
fn signature(&self) -> Signature {
Signature::build("cp")
.required("source", SyntaxShape::GlobPattern, "the place to copy from")
.rest(
"source(s)",
SyntaxShape::String,
"the place(s) to copy from",
)
// .required("source", SyntaxShape::GlobPattern, "the place to copy from")
.required("destination", SyntaxShape::Filepath, "the place to copy to")
.switch(
"recursive",
@ -71,15 +81,16 @@ impl Command for Cp {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let src: Spanned<String> = call.req(engine_state, stack, 0)?;
let dst: Spanned<String> = call.req(engine_state, stack, 1)?;
let mut src_vec: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
// read dst as final argument
let dst: Spanned<String> = src_vec.pop().expect("Final argument is destination");
let recursive = call.has_flag("recursive");
let verbose = call.has_flag("verbose");
let interactive = call.has_flag("interactive");
let current_dir_path = current_dir(engine_state, stack)?;
let source = current_dir_path.join(src.item.as_str());
let destination = current_dir_path.join(dst.item.as_str());
let destination = expand_ndots(current_dir_path.join(dst.item.as_str()));
let path_last_char = destination.as_os_str().to_string_lossy().chars().last();
let is_directory = path_last_char == Some('/') || path_last_char == Some('\\');
@ -92,24 +103,54 @@ impl Command for Cp {
let ctrlc = engine_state.ctrlc.clone();
let span = call.head;
let sources: Vec<_> = match nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) {
Ok(files) => files.collect(),
Err(e) => {
return Err(ShellError::GenericError(
e.to_string(),
"invalid pattern".to_string(),
Some(src.span),
None,
Vec::new(),
))
let mut sources: Vec<PathBuf> = vec![];
let mut path_to_span: HashMap<PathBuf, Span> = HashMap::new();
for src in &src_vec {
let source = current_dir_path.join(src.item.as_str());
let glob_results: Vec<GlobResult> =
match nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS) {
Ok(files) => files.collect(),
Err(e) => {
return Err(ShellError::GenericError(
e.to_string(),
"invalid pattern".to_string(),
Some(src.span),
None,
Vec::new(),
))
}
};
let mut new_sources: Vec<PathBuf> = vec![];
for glob_result in glob_results {
match glob_result {
Ok(path) => {
path_to_span.insert(path.clone(), src.span);
new_sources.push(path);
}
Err(e) => {
return Err(ShellError::GenericError(
e.to_string(),
"glob iteration error".to_string(),
Some(src.span),
None,
Vec::new(),
))
}
}
}
};
sources.append(&mut new_sources);
}
if sources.is_empty() {
return Err(ShellError::GenericError(
"No matches found".into(),
"no matches found".into(),
Some(src.span),
Some(merge_spans(
&src_vec.into_iter().map(|src| src.span).collect_vec(),
)),
None,
Vec::new(),
));
@ -125,21 +166,25 @@ impl Command for Cp {
));
}
let any_source_is_dir = sources.iter().any(|f| matches!(f, Ok(f) if f.is_dir()));
let any_source_is_dir = sources.iter().find(|f| f.is_dir());
if any_source_is_dir && !recursive {
return Err(ShellError::GenericError(
"Directories must be copied using \"--recursive\"".into(),
"resolves to a directory (not copied)".into(),
Some(src.span),
None,
Vec::new(),
));
if let Some(dir_source) = any_source_is_dir {
if !recursive {
return Err(ShellError::GenericError(
"Directories must be copied using \"--recursive\"".into(),
"resolves to a directory (not copied)".into(),
Some(*path_to_span.get(dir_source).unwrap_or_else(|| {
panic!("Key {:?} should exist", dir_source.as_os_str())
})),
None,
Vec::new(),
));
}
}
let mut result = Vec::new();
for entry in sources.into_iter().flatten() {
for entry in sources.into_iter() {
let mut sources = FileStructure::new();
sources.walk_decorate(&entry, engine_state, stack)?;
@ -163,7 +208,8 @@ impl Command for Cp {
let res = if src == dst {
let message = format!(
"src {:?} and dst {:?} are identical(not copied)",
source, destination
src.as_os_str(),
destination
);
return Err(ShellError::GenericError(

View File

@ -6,7 +6,7 @@ use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Spanned,
SyntaxShape, Value,
};
use wax::Glob as WaxGlob;
use wax::{Glob as WaxGlob, WalkBehavior};
#[derive(Clone)]
pub struct Glob;
@ -120,7 +120,13 @@ impl Command for Glob {
#[allow(clippy::needless_collect)]
let glob_results: Vec<Value> = glob
.walk(path, folder_depth)
.walk_with_behavior(
path,
WalkBehavior {
depth: folder_depth,
..Default::default()
},
)
.flatten()
.map(|entry| Value::String {
val: entry.into_path().to_string_lossy().to_string(),

View File

@ -1,15 +1,17 @@
use crate::DirBuilder;
use crate::DirInfo;
use chrono::{DateTime, Local, LocalResult, TimeZone, Utc};
use itertools::Itertools;
use nu_engine::env::current_dir;
use nu_engine::CallExt;
use nu_glob::MatchOptions;
use nu_path::expand_to_real_path;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::IntoPipelineData;
use nu_protocol::{
Category, DataSource, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData,
PipelineMetadata, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
Category, DataSource, Example, IntoInterruptiblePipelineData, PipelineData, PipelineMetadata,
ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use pathdiff::diff_paths;
@ -38,7 +40,11 @@ impl Command for Ls {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("ls")
// Using a string instead of a glob pattern shape so it won't auto-expand
.optional("pattern", SyntaxShape::String, "the glob pattern to use")
.rest(
"pattern(s)",
SyntaxShape::String,
"the glob pattern(s) to use",
)
.switch("all", "Show hidden files", Some('a'))
.switch(
"long",
@ -56,6 +62,11 @@ impl Command for Ls {
"Display the apparent directory size in place of the directory metadata size",
Some('d'),
)
.switch(
"directory",
"List the specified directory itself instead of its contents",
Some('D'),
)
.category(Category::FileSystem)
}
@ -71,22 +82,28 @@ impl Command for Ls {
let short_names = call.has_flag("short-names");
let full_paths = call.has_flag("full-paths");
let du = call.has_flag("du");
let directory = call.has_flag("directory");
let ctrl_c = engine_state.ctrlc.clone();
let call_span = call.head;
let cwd = current_dir(engine_state, stack)?;
let pattern_arg: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
let mut shell_errors: Vec<ShellError> = vec![];
let pattern_args: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
let glob_results = if !pattern_args.is_empty() {
pattern_args
.into_iter()
.flat_map(|pattern_arg| {
let mut path = expand_to_real_path(pattern_arg.clone().item);
let p_tag = pattern_arg.span;
let cwd = cwd.clone();
let ctrl_c = ctrl_c.clone();
let (path, p_tag, absolute_path) = match pattern_arg {
Some(p) => {
let p_tag = p.span;
let mut p = expand_to_real_path(p.item);
let expanded = nu_path::expand_path_with(&p, &cwd);
if expanded.is_dir() {
if permission_denied(&p) {
#[cfg(unix)]
let error_msg = format!(
let expanded = nu_path::expand_path_with(&path, &cwd);
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
if !directory && expanded.is_dir() {
if permission_denied(&path) {
#[cfg(unix)]
let error_msg = format!(
"The permissions of {:o} do not allow access for this user",
expanded
.metadata()
@ -97,131 +114,288 @@ impl Command for Ls {
.mode()
& 0o0777
);
#[cfg(not(unix))]
let error_msg = String::from("Permission denied");
return Err(ShellError::GenericError(
"Permission denied".to_string(),
error_msg,
Some(p_tag),
#[cfg(not(unix))]
let error_msg = String::from("Permission denied");
shell_errors.push(ShellError::GenericError(
"Permission denied".to_string(),
error_msg,
Some(p_tag),
None,
Vec::new(),
));
}
if is_empty_dir(&expanded) {
return Vec::from([Value::nothing(call_span)]).into_iter();
}
path.push("*");
}
let absolute_path = path.is_absolute();
let hidden_dir_specified = is_hidden_dir(&path);
let glob_path = Spanned {
item: path.display().to_string(),
span: p_tag,
};
let glob_options = if all {
None
} else {
let mut glob_options = MatchOptions::new();
glob_options.recursive_match_hidden_dir = false;
Some(glob_options)
};
let (prefix, paths) =
nu_engine::glob_from(&glob_path, &cwd, call_span, glob_options)
.expect("glob failure");
let mut paths_peek = paths.peekable();
if paths_peek.peek().is_none() {
shell_errors.push(ShellError::GenericError(
format!("No matches found for {}", &path.display().to_string()),
"".to_string(),
None,
Some("no matches found".to_string()),
Vec::new(),
));
}
if is_empty_dir(&expanded) {
return Ok(Value::nothing(call_span).into_pipeline_data());
}
p.push("*");
}
let absolute_path = p.is_absolute();
(p, p_tag, absolute_path)
}
None => {
if is_empty_dir(current_dir(engine_state, stack)?) {
return Ok(Value::nothing(call_span).into_pipeline_data());
} else {
(PathBuf::from("./*"), call_span, false)
}
}
};
let hidden_dir_specified = is_hidden_dir(&path);
let mut hidden_dirs = vec![];
let glob_path = Spanned {
item: path.display().to_string(),
span: p_tag,
};
paths_peek
.into_iter()
.filter_map(move |x| match x {
Ok(path) => {
let metadata = match std::fs::symlink_metadata(&path) {
Ok(metadata) => Some(metadata),
Err(_) => None,
};
if path_contains_hidden_folder(&path, &hidden_dirs) {
return None;
}
let glob_options = if all {
None
if !all && !hidden_dir_specified && is_hidden_dir(&path) {
if path.is_dir() {
hidden_dirs.push(path);
}
return None;
}
let display_name = if short_names {
path.file_name().map(|os| os.to_string_lossy().to_string())
} else if full_paths || absolute_path {
Some(path.to_string_lossy().to_string())
} else if let Some(prefix) = &prefix {
if let Ok(remainder) = path.strip_prefix(&prefix) {
if directory {
// When the path is the same as the cwd, path_diff should be "."
let path_diff = if let Some(path_diff_not_dot) =
diff_paths(&path, &cwd)
{
let path_diff_not_dot =
path_diff_not_dot.to_string_lossy();
if path_diff_not_dot.is_empty() {
".".to_string()
} else {
path_diff_not_dot.to_string()
}
} else {
path.to_string_lossy().to_string()
};
Some(path_diff)
} else {
let new_prefix =
if let Some(pfx) = diff_paths(&prefix, &cwd) {
pfx
} else {
prefix.to_path_buf()
};
Some(
new_prefix
.join(remainder)
.to_string_lossy()
.to_string(),
)
}
} else {
Some(path.to_string_lossy().to_string())
}
} else {
Some(path.to_string_lossy().to_string())
}
.ok_or_else(|| {
ShellError::GenericError(
format!("Invalid file name: {:}", path.to_string_lossy()),
"invalid file name".into(),
Some(call_span),
None,
Vec::new(),
)
});
match display_name {
Ok(name) => {
let entry = dir_entry_dict(
&path,
&name,
metadata.as_ref(),
call_span,
long,
du,
ctrl_c.clone(),
);
match entry {
Ok(value) => Some(value),
Err(err) => Some(Value::Error { error: err }),
}
}
Err(err) => Some(Value::Error { error: err }),
}
}
_ => Some(Value::Nothing { span: call_span }),
})
.collect_vec()
.into_iter()
})
.collect_vec()
} else {
let mut glob_options = MatchOptions::new();
glob_options.recursive_match_hidden_dir = false;
Some(glob_options)
};
let (prefix, paths) = nu_engine::glob_from(&glob_path, &cwd, call_span, glob_options)?;
let (path, p_tag, absolute_path) = if directory {
(PathBuf::from("."), call_span, false)
} else if is_empty_dir(current_dir(engine_state, stack)?) {
return Ok(Value::nothing(call_span).into_pipeline_data());
} else {
(PathBuf::from("./*"), call_span, false)
};
let mut paths_peek = paths.peekable();
if paths_peek.peek().is_none() {
return Err(ShellError::GenericError(
format!("No matches found for {}", &path.display().to_string()),
"".to_string(),
None,
Some("no matches found".to_string()),
Vec::new(),
));
}
let hidden_dir_specified = is_hidden_dir(&path);
let mut hidden_dirs = vec![];
let glob_path = Spanned {
item: path.display().to_string(),
span: p_tag,
};
Ok(paths_peek
.into_iter()
.filter_map(move |x| match x {
Ok(path) => {
let metadata = match std::fs::symlink_metadata(&path) {
Ok(metadata) => Some(metadata),
Err(_) => None,
};
if path_contains_hidden_folder(&path, &hidden_dirs) {
return None;
}
let glob_options = if all {
None
} else {
let mut glob_options = MatchOptions::new();
glob_options.recursive_match_hidden_dir = false;
Some(glob_options)
};
let (prefix, paths) = nu_engine::glob_from(&glob_path, &cwd, call_span, glob_options)?;
if !all && !hidden_dir_specified && is_hidden_dir(&path) {
if path.is_dir() {
hidden_dirs.push(path);
let mut paths_peek = paths.peekable();
if paths_peek.peek().is_none() {
return Err(ShellError::GenericError(
format!("No matches found for {}", &path.display().to_string()),
"".to_string(),
None,
Some("no matches found".to_string()),
Vec::new(),
));
}
let mut hidden_dirs = vec![];
paths_peek
.into_iter()
.filter_map(move |x| match x {
Ok(path) => {
let metadata = match std::fs::symlink_metadata(&path) {
Ok(metadata) => Some(metadata),
Err(_) => None,
};
if path_contains_hidden_folder(&path, &hidden_dirs) {
return None;
}
return None;
}
let display_name = if short_names {
path.file_name().map(|os| os.to_string_lossy().to_string())
} else if full_paths || absolute_path {
Some(path.to_string_lossy().to_string())
} else if let Some(prefix) = &prefix {
if let Ok(remainder) = path.strip_prefix(&prefix) {
let new_prefix = if let Some(pfx) = diff_paths(&prefix, &cwd) {
pfx
if !all && !hidden_dir_specified && is_hidden_dir(&path) {
if path.is_dir() {
hidden_dirs.push(path);
}
return None;
}
let display_name = if short_names {
path.file_name().map(|os| os.to_string_lossy().to_string())
} else if full_paths || absolute_path {
Some(path.to_string_lossy().to_string())
} else if let Some(prefix) = &prefix {
if let Ok(remainder) = path.strip_prefix(&prefix) {
if directory {
// When the path is the same as the cwd, path_diff should be "."
let path_diff = if let Some(path_diff_not_dot) =
diff_paths(&path, &cwd)
{
let path_diff_not_dot = path_diff_not_dot.to_string_lossy();
if path_diff_not_dot.is_empty() {
".".to_string()
} else {
path_diff_not_dot.to_string()
}
} else {
path.to_string_lossy().to_string()
};
Some(path_diff)
} else {
let new_prefix = if let Some(pfx) = diff_paths(&prefix, &cwd) {
pfx
} else {
prefix.to_path_buf()
};
Some(new_prefix.join(remainder).to_string_lossy().to_string())
}
} else {
prefix.to_path_buf()
};
Some(new_prefix.join(remainder).to_string_lossy().to_string())
Some(path.to_string_lossy().to_string())
}
} else {
Some(path.to_string_lossy().to_string())
}
} else {
Some(path.to_string_lossy().to_string())
}
.ok_or_else(|| {
ShellError::GenericError(
format!("Invalid file name: {:}", path.to_string_lossy()),
"invalid file name".into(),
Some(call_span),
None,
Vec::new(),
)
});
.ok_or_else(|| {
ShellError::GenericError(
format!("Invalid file name: {:}", path.to_string_lossy()),
"invalid file name".into(),
Some(call_span),
None,
Vec::new(),
)
});
match display_name {
Ok(name) => {
let entry = dir_entry_dict(
&path,
&name,
metadata.as_ref(),
call_span,
long,
du,
ctrl_c.clone(),
);
match entry {
Ok(value) => Some(value),
Err(err) => Some(Value::Error { error: err }),
match display_name {
Ok(name) => {
let entry = dir_entry_dict(
&path,
&name,
metadata.as_ref(),
call_span,
long,
du,
ctrl_c.clone(),
);
match entry {
Ok(value) => Some(value),
Err(err) => Some(Value::Error { error: err }),
}
}
Err(err) => Some(Value::Error { error: err }),
}
Err(err) => Some(Value::Error { error: err }),
}
}
_ => Some(Value::Nothing { span: call_span }),
})
_ => Some(Value::Nothing { span: call_span }),
})
.collect_vec()
};
if !shell_errors.is_empty() {
return Err(shell_errors.pop().expect("Vec pop error"));
}
Ok(glob_results
.into_iter()
.filter(|result| !matches!(result, Value::Nothing { .. }))
.into_pipeline_data_with_metadata(
PipelineMetadata {
data_source: DataSource::Ls,
@ -252,6 +426,11 @@ impl Command for Ls {
example: "ls *.rs",
result: None,
},
Example {
description: "List all rust files and all toml files",
example: "ls *.rs *.toml",
result: None,
},
Example {
description: "List all files and directories whose name do not contain 'bar'",
example: "ls -s | where name !~ bar",
@ -268,6 +447,12 @@ impl Command for Ls {
example: "ls -s ~ | where type == dir && modified < ((date now) - 7day)",
result: None,
},
Example {
description: "List given paths, show directories themselves",
example:
"['/path/to/directory' '/path/to/file'] | each { |it| ls -D $it } | flatten",
result: None,
},
]
}
}

View File

@ -1,8 +1,11 @@
use std::path::{Path, PathBuf};
use super::util::try_interaction;
use itertools::Itertools;
use nu_engine::env::current_dir;
use nu_engine::CallExt;
use nu_glob::GlobResult;
use nu_path::dots::expand_ndots;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
@ -36,11 +39,16 @@ impl Command for Mv {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("mv")
.required(
"source",
SyntaxShape::GlobPattern,
"the location to move files/directories from",
.rest(
"source(s)",
SyntaxShape::String,
"the location(s) to move files/directories from",
)
// .required(
// "source",
// SyntaxShape::GlobPattern,
// "the location to move files/directories from",
// )
.required(
"destination",
SyntaxShape::Filepath,
@ -64,114 +72,132 @@ impl Command for Mv {
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
// TODO: handle invalid directory or insufficient permissions when moving
let spanned_source: Spanned<String> = call.req(engine_state, stack, 0)?;
let spanned_destination: Spanned<String> = call.req(engine_state, stack, 1)?;
let mut spanned_sources: Vec<Spanned<String>> = call.rest(engine_state, stack, 0)?;
// read destination as final argument
let spanned_destination: Spanned<String> =
call.req(engine_state, stack, spanned_sources.len() - 1)?;
// don't read destination argument
spanned_sources.pop();
let verbose = call.has_flag("verbose");
let interactive = call.has_flag("interactive");
// let force = call.has_flag("force");
let ctrlc = engine_state.ctrlc.clone();
let path = current_dir(engine_state, stack)?;
let source = path.join(spanned_source.item.as_str());
let destination = path.join(spanned_destination.item.as_str());
let mut sources = nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS)
.map_or_else(|_| Vec::new(), Iterator::collect);
if sources.is_empty() {
return Err(ShellError::GenericError(
"Invalid file or pattern".into(),
"invalid file or pattern".into(),
Some(spanned_source.span),
None,
Vec::new(),
));
}
// We have two possibilities.
//
// First, the destination exists.
// - If a directory, move everything into that directory, otherwise
// - if only a single source, overwrite the file, otherwise
// - error.
//
// Second, the destination doesn't exist, so we can only rename a single source. Otherwise
// it's an error.
if (destination.exists() && !destination.is_dir() && sources.len() > 1)
|| (!destination.exists() && sources.len() > 1)
{
return Err(ShellError::GenericError(
"Can only move multiple sources if destination is a directory".into(),
"destination must be a directory when multiple sources".into(),
Some(spanned_destination.span),
None,
Vec::new(),
));
}
let some_if_source_is_destination = sources
.iter()
.find(|f| matches!(f, Ok(f) if destination.starts_with(f)));
if destination.exists() && destination.is_dir() && sources.len() == 1 {
if let Some(Ok(filename)) = some_if_source_is_destination {
return Err(ShellError::GenericError(
format!(
"Not possible to move {:?} to itself",
filename.file_name().expect("Invalid file name")
),
"cannot move to itself".into(),
Some(spanned_destination.span),
None,
Vec::new(),
));
}
}
if let Some(Ok(_filename)) = some_if_source_is_destination {
sources = sources
.into_iter()
.filter(|f| matches!(f, Ok(f) if !destination.starts_with(f)))
.collect();
}
let path = current_dir(engine_state, stack).expect("Failed current_dir");
let span = call.head;
Ok(sources
Ok(spanned_sources
.into_iter()
.flatten()
.filter_map(move |entry| {
let result = move_file(
Spanned {
item: entry.clone(),
span: spanned_source.span,
},
Spanned {
item: destination.clone(),
span: spanned_destination.span,
},
interactive,
);
if let Err(error) = result {
Some(Value::Error { error })
} else if verbose {
let val = if result.expect("Error value when unwrapping mv result") {
format!(
"moved {:} to {:}",
entry.to_string_lossy(),
destination.to_string_lossy()
)
} else {
format!(
"{:} not moved to {:}",
entry.to_string_lossy(),
destination.to_string_lossy()
)
};
Some(Value::String { val, span })
} else {
None
.flat_map(move |spanned_source| {
let path = path.clone();
let source = path.join(spanned_source.item.as_str());
let destination = expand_ndots(path.join(spanned_destination.item.as_str()));
let mut sources: Vec<GlobResult> =
nu_glob::glob_with(&source.to_string_lossy(), GLOB_PARAMS)
.map_or_else(|_| Vec::new(), Iterator::collect);
if sources.is_empty() {
let err = ShellError::GenericError(
"Invalid file or pattern".into(),
"invalid file or pattern".into(),
Some(spanned_source.span),
None,
Vec::new(),
);
return Vec::from([Value::Error { error: err }]).into_iter();
}
// We have two possibilities.
//
// First, the destination exists.
// - If a directory, move everything into that directory, otherwise
// - if only a single source, overwrite the file, otherwise
// - error.
//
// Second, the destination doesn't exist, so we can only rename a single source. Otherwise
// it's an error.
if (destination.exists() && !destination.is_dir() && sources.len() > 1)
|| (!destination.exists() && sources.len() > 1)
{
let err = ShellError::GenericError(
"Can only move multiple sources if destination is a directory".into(),
"destination must be a directory when multiple sources".into(),
Some(spanned_destination.span),
None,
Vec::new(),
);
return Vec::from([Value::Error { error: err }]).into_iter();
}
let some_if_source_is_destination = sources
.iter()
.find(|f| matches!(f, Ok(f) if destination.starts_with(f)));
if destination.exists() && destination.is_dir() && sources.len() == 1 {
if let Some(Ok(filename)) = some_if_source_is_destination {
let err = ShellError::GenericError(
format!(
"Not possible to move {:?} to itself",
filename.file_name().expect("Invalid file name")
),
"cannot move to itself".into(),
Some(spanned_destination.span),
None,
Vec::new(),
);
return Vec::from([Value::Error { error: err }]).into_iter();
}
}
if let Some(Ok(_filename)) = some_if_source_is_destination {
sources = sources
.into_iter()
.filter(|f| matches!(f, Ok(f) if !destination.starts_with(f)))
.collect();
}
sources
.into_iter()
.flatten()
.filter_map(move |entry| {
let entry = expand_ndots(entry);
let result = move_file(
Spanned {
item: entry.clone(),
span: spanned_source.span,
},
Spanned {
item: destination.clone(),
span: spanned_destination.span,
},
interactive,
);
if let Err(error) = result {
Some(Value::Error { error })
} else if verbose {
let val = if result.expect("Error value when unwrapping mv result") {
format!(
"moved {:} to {:}",
entry.to_string_lossy(),
destination.to_string_lossy()
)
} else {
format!(
"{:} not moved to {:}",
entry.to_string_lossy(),
destination.to_string_lossy()
)
};
Some(Value::String { val, span })
} else {
None
}
})
.collect_vec()
.into_iter()
})
.into_pipeline_data(ctrlc))
}

View File

@ -88,14 +88,14 @@ impl Command for Open {
if permission_denied(&path) {
#[cfg(unix)]
let error_msg = format!(
"The permissions of {:o} do not allow access for this user",
path.metadata()
.expect("this shouldn't be called since we already know there is a dir")
.permissions()
.mode()
& 0o0777
);
let error_msg = match path.metadata() {
Ok(md) => format!(
"The permissions of {:o} does not allow access for this user",
md.permissions().mode() & 0o0777
),
Err(e) => e.to_string(),
};
#[cfg(not(unix))]
let error_msg = String::from("Permission denied");
Err(ShellError::GenericError(

View File

@ -66,27 +66,31 @@ impl Command for All {
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
Ok(Value::Bool {
val: input.into_interruptible_iter(ctrlc).all(move |value| {
if let Some(var_id) = var_id {
stack.add_var(var_id, value);
}
for value in input.into_interruptible_iter(ctrlc) {
if let Some(var_id) = var_id {
stack.add_var(var_id, value);
}
eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
call.redirect_stdout,
call.redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})
}),
span,
let eval = eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
call.redirect_stdout,
call.redirect_stderr,
);
match eval {
Err(e) => {
return Err(e);
}
Ok(pipeline_data) => {
if !pipeline_data.into_value(span).is_true() {
return Ok(Value::Bool { val: false, span }.into_pipeline_data());
}
}
}
}
.into_pipeline_data())
Ok(Value::Bool { val: true, span }.into_pipeline_data())
}
}

View File

@ -65,27 +65,31 @@ impl Command for Any {
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
Ok(Value::Bool {
val: input.into_interruptible_iter(ctrlc).any(move |value| {
if let Some(var_id) = var_id {
stack.add_var(var_id, value);
}
for value in input.into_interruptible_iter(ctrlc) {
if let Some(var_id) = var_id {
stack.add_var(var_id, value);
}
eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
call.redirect_stdout,
call.redirect_stderr,
)
.map_or(false, |pipeline_data| {
pipeline_data.into_value(span).is_true()
})
}),
span,
let eval = eval_block(
&engine_state,
&mut stack,
block,
PipelineData::new(span),
call.redirect_stdout,
call.redirect_stderr,
);
match eval {
Err(e) => {
return Err(e);
}
Ok(pipeline_data) => {
if pipeline_data.into_value(span).is_true() {
return Ok(Value::Bool { val: true, span }.into_pipeline_data());
}
}
}
}
.into_pipeline_data())
Ok(Value::Bool { val: false, span }.into_pipeline_data())
}
}

View File

@ -1,10 +1,15 @@
use nu_engine::{eval_block, CallExt};
use crate::help::highlight_search_string;
use lscolors::Style as LsColors_Style;
use nu_ansi_term::{Color::Default, Style};
use nu_color_config::get_color_config;
use nu_engine::{env_to_string, eval_block, CallExt};
use nu_protocol::{
ast::Call,
engine::{CaptureBlock, Command, EngineState, Stack},
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Value,
Category, Example, IntoInterruptiblePipelineData, ListStream, PipelineData, ShellError,
Signature, Span, SyntaxShape, Value,
};
use nu_utils::get_ls_colors;
use regex::Regex;
#[derive(Clone)]
@ -149,7 +154,7 @@ impl Command for Find {
find_with_predicate(predicate, engine_state, stack, call, input)
}
(Some(regex), None) => find_with_regex(regex, engine_state, stack, call, input),
(None, None) => find_with_rest(engine_state, stack, call, input),
(None, None) => find_with_rest_and_highlight(engine_state, stack, call, input),
(Some(_), Some(_)) => Err(ShellError::IncompatibleParametersSingle(
"expected either predicate or regex flag, not both".to_owned(),
call.head,
@ -265,7 +270,7 @@ fn find_with_predicate(
)
}
fn find_with_rest(
fn find_with_rest_and_highlight(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
@ -273,11 +278,10 @@ fn find_with_rest(
) -> Result<PipelineData, ShellError> {
let span = call.head;
let ctrlc = engine_state.ctrlc.clone();
let metadata = input.metadata();
let engine_state = engine_state.clone();
let config = engine_state.get_config().clone();
let filter_config = engine_state.get_config().clone();
let invert = call.has_flag("invert");
let terms = call.rest::<Value>(&engine_state, stack, 0)?;
let lower_terms = terms
.iter()
@ -290,56 +294,236 @@ fn find_with_rest(
})
.collect::<Vec<Value>>();
let pipe = input.filter(
move |value| {
let lower_value = if let Ok(span) = value.span() {
Value::string(value.into_string("", &config).to_lowercase(), span)
} else {
value.clone()
};
let color_hm = get_color_config(&config);
let default_style = Style::new().fg(Default).on(Default);
let string_style = match color_hm.get("string") {
Some(style) => *style,
None => default_style,
};
let ls_colors_env_str = match stack.get_env_var(&engine_state, "LS_COLORS") {
Some(v) => Some(env_to_string("LS_COLORS", &v, &engine_state, stack)?),
None => None,
};
let ls_colors = get_ls_colors(ls_colors_env_str);
lower_terms.iter().any(|term| match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Float { .. }
| Value::Block { .. }
| Value::Nothing { .. }
| Value::Error { .. } => lower_value
.eq(span, term, span)
.map_or(false, |value| value.is_true()),
Value::String { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term
.r#in(span, &lower_value, span)
.map_or(false, |value| value.is_true()),
Value::Record { vals, .. } => vals.iter().any(|val| {
if let Ok(span) = val.span() {
let lower_val = Value::string(
val.into_string("", &config).to_lowercase(),
Span::test_data(),
);
match input {
PipelineData::Value(_, _) => input.filter(
move |value| {
let lower_value = if let Ok(span) = value.span() {
Value::string(value.into_string("", &config).to_lowercase(), span)
} else {
value.clone()
};
term.r#in(span, &lower_val, span)
.map_or(false, |value| value.is_true())
} else {
term.r#in(span, val, span)
.map_or(false, |value| value.is_true())
lower_terms.iter().any(|term| match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Float { .. }
| Value::Block { .. }
| Value::Nothing { .. }
| Value::Error { .. } => lower_value
.eq(span, term, span)
.map_or(false, |val| val.is_true()),
Value::String { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term
.r#in(span, &lower_value, span)
.map_or(false, |val| val.is_true()),
Value::Record { vals, .. } => vals.iter().any(|val| {
if let Ok(span) = val.span() {
let lower_val = Value::string(
val.into_string("", &config).to_lowercase(),
Span::test_data(),
);
term.r#in(span, &lower_val, span)
.map_or(false, |aval| aval.is_true())
} else {
term.r#in(span, val, span)
.map_or(false, |aval| aval.is_true())
}
}),
Value::Binary { .. } => false,
}) != invert
},
ctrlc,
),
PipelineData::ListStream(stream, meta) => {
Ok(ListStream::from_stream(
stream
.map(move |mut x| match &mut x {
Value::Record { cols, vals, span } => {
let mut output = vec![];
for val in vals {
let val_str = val.into_string("", &config);
let lower_val = val.into_string("", &config).to_lowercase();
let mut term_added_to_output = false;
for term in terms.clone() {
let term_str = term.into_string("", &config);
let lower_term = term.into_string("", &config).to_lowercase();
if lower_val.contains(&lower_term) {
if config.use_ls_colors {
// Get the original LS_COLORS color
let style = ls_colors.style_for_path(val_str.clone());
let ansi_style = style
.map(LsColors_Style::to_crossterm_style)
.unwrap_or_default();
let ls_colored_val =
ansi_style.apply(&val_str).to_string();
let hi = match highlight_search_string(
&ls_colored_val,
&term_str,
&string_style,
) {
Ok(hi) => hi,
Err(_) => string_style
.paint(term_str.to_string())
.to_string(),
};
output.push(Value::String {
val: hi,
span: *span,
});
term_added_to_output = true;
} else {
// No LS_COLORS support, so just use the original value
let hi = match highlight_search_string(
&val_str,
&term_str,
&string_style,
) {
Ok(hi) => hi,
Err(_) => string_style
.paint(term_str.to_string())
.to_string(),
};
output.push(Value::String {
val: hi,
span: *span,
});
}
}
}
if !term_added_to_output {
output.push(val.clone());
}
}
Value::Record {
cols: cols.to_vec(),
vals: output,
span: *span,
}
}
_ => x,
})
.filter(move |value| {
let lower_value = if let Ok(span) = value.span() {
Value::string(
value.into_string("", &filter_config).to_lowercase(),
span,
)
} else {
value.clone()
};
lower_terms.iter().any(|term| match value {
Value::Bool { .. }
| Value::Int { .. }
| Value::Filesize { .. }
| Value::Duration { .. }
| Value::Date { .. }
| Value::Range { .. }
| Value::Float { .. }
| Value::Block { .. }
| Value::Nothing { .. }
| Value::Error { .. } => lower_value
.eq(span, term, span)
.map_or(false, |value| value.is_true()),
Value::String { .. }
| Value::List { .. }
| Value::CellPath { .. }
| Value::CustomValue { .. } => term
.r#in(span, &lower_value, span)
.map_or(false, |value| value.is_true()),
Value::Record { vals, .. } => vals.iter().any(|val| {
if let Ok(span) = val.span() {
let lower_val = Value::string(
val.into_string("", &filter_config).to_lowercase(),
Span::test_data(),
);
term.r#in(span, &lower_val, span)
.map_or(false, |value| value.is_true())
} else {
term.r#in(span, val, span)
.map_or(false, |value| value.is_true())
}
}),
Value::Binary { .. } => false,
}) != invert
}),
ctrlc.clone(),
)
.into_pipeline_data(ctrlc)
.set_metadata(meta))
}
PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::new(span)),
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => {
let mut output: Vec<Value> = vec![];
for filter_val in stream {
match filter_val {
Ok(value) => match value {
Value::String { val, span } => {
let split_char = if val.contains("\r\n") { "\r\n" } else { "\n" };
for line in val.split(split_char) {
for term in lower_terms.iter() {
let term_str = term.into_string("", &filter_config);
let lower_val = line.to_lowercase();
if lower_val
.contains(&term.into_string("", &config).to_lowercase())
{
output.push(Value::String {
val: highlight_search_string(
line,
&term_str,
&string_style,
)?,
span,
})
}
}
}
}
_ => {
return Err(ShellError::UnsupportedInput(
format!(
"Unsupport value type '{}' from raw stream",
value.get_type()
),
span,
))
}
},
_ => {
return Err(ShellError::UnsupportedInput(
"Unsupport type from raw stream".to_string(),
span,
))
}
}),
Value::Binary { .. } => false,
}) != invert
},
ctrlc,
)?;
match metadata {
Some(m) => Ok(pipe.into_pipeline_data_with_metadata(m, engine_state.ctrlc.clone())),
None => Ok(pipe),
};
}
Ok(output.into_pipeline_data(ctrlc))
}
}
}

View File

@ -25,7 +25,7 @@ impl Command for Length {
}
fn search_terms(&self) -> Vec<&str> {
vec!["count", "len", "size"]
vec!["count", "len", "size", "wc"]
}
fn run(

View File

@ -21,6 +21,10 @@ impl Command for Reverse {
"Reverses the table."
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert, inverse, flip"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
example: "[0,1,2,3] | reverse",

View File

@ -29,6 +29,10 @@ impl Command for Select {
"Down-select table to only these columns."
}
fn search_terms(&self) -> Vec<&str> {
vec!["pick", "choose", "get"]
}
fn run(
&self,
engine_state: &EngineState,

View File

@ -26,6 +26,10 @@ impl Command for Skip {
"Skip the first n elements of the input."
}
fn search_terms(&self) -> Vec<&str> {
vec!["ignore", "remove"]
}
fn examples(&self) -> Vec<Example> {
vec![
Example {

View File

@ -28,6 +28,10 @@ impl Command for SkipUntil {
"Skip elements of the input until a predicate is true."
}
fn search_terms(&self) -> Vec<&str> {
vec!["ignore"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Skip until the element is positive",

View File

@ -28,6 +28,10 @@ impl Command for SkipWhile {
"Skip elements of the input while a predicate is true."
}
fn search_terms(&self) -> Vec<&str> {
vec!["ignore"]
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Skip while the element is negative",

View File

@ -2,7 +2,8 @@ use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, Signature, SyntaxShape, Value,
Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError,
Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
@ -19,7 +20,13 @@ impl Command for Where {
fn signature(&self) -> nu_protocol::Signature {
Signature::build("where")
.required("cond", SyntaxShape::RowCondition, "condition")
.optional("cond", SyntaxShape::RowCondition, "condition")
.named(
"block",
SyntaxShape::Block(Some(vec![SyntaxShape::Any])),
"use where with a block or variable instead",
Some('b'),
)
.category(Category::Filters)
}
@ -34,51 +41,172 @@ impl Command for Where {
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head;
if let Ok(Some(capture_block)) = call.get_flag::<CaptureBlock>(engine_state, stack, "block")
{
let metadata = input.metadata();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let block = engine_state.get_block(capture_block.block_id).clone();
let mut stack = stack.captures_to_stack(&capture_block.captures);
let orig_env_vars = stack.env_vars.clone();
let orig_env_hidden = stack.env_hidden.clone();
let span = call.head;
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
let metadata = input.metadata();
match input {
PipelineData::Value(Value::Range { .. }, ..)
| PipelineData::Value(Value::List { .. }, ..)
| PipelineData::ListStream { .. } => Ok(input
.into_iter()
.filter_map(move |x| {
stack.with_env(&orig_env_vars, &orig_env_hidden);
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
let mut stack = stack.captures_to_stack(&block.captures);
let block = engine_state.get_block(block.block_id).clone();
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input
.into_iter()
.filter_map(move |value| {
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, value.clone());
}
match eval_block(
&engine_state,
&mut stack,
&block,
x.clone().into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => {
if v.into_value(span).is_true() {
Some(x)
} else {
None
}
}
Err(error) => Some(Value::Error { error }),
}
})
.into_pipeline_data(ctrlc)),
PipelineData::ExternalStream { stdout: None, .. } => {
Ok(PipelineData::new(call.head))
}
let result = eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
);
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => Ok(stream
.into_iter()
.filter_map(move |x| {
stack.with_env(&orig_env_vars, &orig_env_hidden);
match result {
Ok(result) => {
let result = result.into_value(span);
if result.is_true() {
Some(value)
} else {
None
let x = match x {
Ok(x) => x,
Err(err) => return Some(Value::Error { error: err }),
};
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
match eval_block(
&engine_state,
&mut stack,
&block,
x.clone().into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => {
if v.into_value(span).is_true() {
Some(x)
} else {
None
}
}
Err(error) => Some(Value::Error { error }),
}
})
.into_pipeline_data(ctrlc)),
PipelineData::Value(x, ..) => {
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, x.clone());
}
}
Err(err) => Some(Value::Error { error: err }),
Ok(match eval_block(
&engine_state,
&mut stack,
&block,
x.clone().into_pipeline_data(),
redirect_stdout,
redirect_stderr,
) {
Ok(v) => {
if v.into_value(span).is_true() {
Some(x)
} else {
None
}
}
Err(error) => Some(Value::Error { error }),
}
.into_pipeline_data(ctrlc))
}
})
.into_pipeline_data(ctrlc))
.map(|x| x.set_metadata(metadata))
}
.map(|x| x.set_metadata(metadata))
} else {
let capture_block: Option<CaptureBlock> = call.opt(engine_state, stack, 0)?;
if let Some(block) = capture_block {
let span = call.head;
let metadata = input.metadata();
let mut stack = stack.captures_to_stack(&block.captures);
let block = engine_state.get_block(block.block_id).clone();
let ctrlc = engine_state.ctrlc.clone();
let engine_state = engine_state.clone();
let redirect_stdout = call.redirect_stdout;
let redirect_stderr = call.redirect_stderr;
Ok(input
.into_iter()
.filter_map(move |value| {
if let Some(var) = block.signature.get_positional(0) {
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, value.clone());
}
}
let result = eval_block(
&engine_state,
&mut stack,
&block,
PipelineData::new(span),
redirect_stdout,
redirect_stderr,
);
match result {
Ok(result) => {
let result = result.into_value(span);
if result.is_true() {
Some(value)
} else {
None
}
}
Err(err) => Some(Value::Error { error: err }),
}
})
.into_pipeline_data(ctrlc))
.map(|x| x.set_metadata(metadata))
} else {
Err(ShellError::MissingParameter(
"condition".to_string(),
call.head,
))
}
}
}
fn examples(&self) -> Vec<Example> {
@ -103,6 +231,23 @@ impl Command for Where {
example: "ls | where modified >= (date now) - 2wk",
result: None,
},
Example {
description: "Get all numbers above 3 with an existing block condition",
example: "let a = {$in > 3}; [1, 2, 5, 6] | where -b $a",
result: Some(Value::List {
vals: vec![
Value::Int {
val: 5,
span: Span::test_data(),
},
Value::Int {
val: 6,
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
},
]
}
}

View File

@ -442,6 +442,14 @@ fn convert_to_value(
val: size * 1000 * 1000 * 1000 * 1000 * 1000,
span,
}),
Unit::Exabyte => Ok(Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
span,
}),
Unit::Zettabyte => Ok(Value::Filesize {
val: size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
span,
}),
Unit::Kibibyte => Ok(Value::Filesize {
val: size * 1024,
@ -463,6 +471,14 @@ fn convert_to_value(
val: size * 1024 * 1024 * 1024 * 1024 * 1024,
span,
}),
Unit::Exbibyte => Ok(Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
span,
}),
Unit::Zebibyte => Ok(Value::Filesize {
val: size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024,
span,
}),
Unit::Nanosecond => Ok(Value::Duration { val: size, span }),
Unit::Microsecond => Ok(Value::Duration {
@ -489,8 +505,8 @@ fn convert_to_value(
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"duration too large".into(),
"duration too large".into(),
"day duration too large".into(),
"day duration too large".into(),
expr.span,
)),
},
@ -499,11 +515,40 @@ fn convert_to_value(
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"duration too large".into(),
"duration too large".into(),
"week duration too large".into(),
"week duration too large".into(),
expr.span,
)),
},
Unit::Month => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 30) {
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"month duration too large".into(),
"month duration too large".into(),
expr.span,
)),
},
Unit::Year => match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365) {
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"year duration too large".into(),
"year duration too large".into(),
expr.span,
)),
},
Unit::Decade => {
match size.checked_mul(1000 * 1000 * 1000 * 60 * 60 * 24 * 365 * 10) {
Some(val) => Ok(Value::Duration { val, span }),
None => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(),
"decade duration too large".into(),
"decade duration too large".into(),
expr.span,
)),
}
}
}
}
Expr::Var(..) => Err(ShellError::OutsideSpannedLabeledError(

View File

@ -18,6 +18,10 @@ impl Command for SubCommand {
"Applies the floor function to a list of numbers"
}
fn search_terms(&self) -> Vec<&str> {
vec!["floor"]
}
fn run(
&self,
engine_state: &EngineState,

View File

@ -23,7 +23,7 @@ impl Command for SubCommand {
}
fn search_terms(&self) -> Vec<&str> {
vec!["middle", "average"]
vec!["middle", "median"]
}
fn run(

View File

@ -44,7 +44,7 @@ impl Command for SubCommand {
}
fn search_terms(&self) -> Vec<&str> {
vec!["common", "often", "average"]
vec!["common", "often"]
}
fn run(

View File

@ -21,6 +21,10 @@ impl Command for SubCommand {
"Finds the variance of a list of numbers or tables"
}
fn search_terms(&self) -> Vec<&str> {
vec!["deviation", "dispersion", "variance", "variation"]
}
fn run(
&self,
_engine_state: &EngineState,

View File

@ -116,7 +116,7 @@ impl Command for History {
cols: vec![
"item_id".into(),
"start_timestamp".into(),
"command_line".to_string(),
"command".to_string(),
"session_id".into(),
"hostname".into(),
"cwd".into(),

View File

@ -17,7 +17,7 @@ impl Command for SubCommand {
}
fn signature(&self) -> Signature {
Signature::build("post")
Signature::build("port")
.optional(
"start",
SyntaxShape::Int,
@ -48,7 +48,7 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "get free port between 3121 and 4000",
description: "get a free port between 3121 and 4000",
example: "port 3121 4000",
result: Some(Value::Int {
val: 3121,
@ -56,7 +56,7 @@ impl Command for SubCommand {
}),
},
Example {
description: "get free port from system",
description: "get a free port from system",
example: "port",
result: None,
},

View File

@ -40,7 +40,7 @@ impl Command for SubCommand {
fn uuid(call: &Call) -> Result<PipelineData, ShellError> {
let span = call.head;
let uuid_4 = Uuid::new_v4().to_hyphenated().to_string();
let uuid_4 = Uuid::new_v4().hyphenated().to_string();
Ok(PipelineData::Value(
Value::String { val: uuid_4, span },

View File

@ -25,7 +25,7 @@ impl Command for Size {
}
fn search_terms(&self) -> Vec<&str> {
vec!["count", "word", "character", "unicode"]
vec!["count", "word", "character", "unicode", "wc"]
}
fn run(

View File

@ -0,0 +1,130 @@
use nu_engine::CallExt;
use nu_protocol::{
ast::Call,
engine::{Command, EngineState, Stack},
Category, Example, IntoPipelineData, PipelineData, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"split list"
}
fn signature(&self) -> Signature {
Signature::build("split list")
.required(
"separator",
SyntaxShape::Any,
"the value that denotes what separates the list",
)
.category(Category::Filters)
}
fn usage(&self) -> &str {
"Split a list into multiple lists using a separator"
}
fn search_terms(&self) -> Vec<&str> {
vec!["list", "separate", "divide"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
split_list(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Split a list of chars into two lists",
example: "[a, b, c, d, e, f, g] | split list d",
result: Some(Value::List {
vals: vec![
Value::List {
vals: vec![
Value::test_string("a"),
Value::test_string("b"),
Value::test_string("c"),
],
span: Span::test_data(),
},
Value::List {
vals: vec![
Value::test_string("e"),
Value::test_string("f"),
Value::test_string("g"),
],
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
},
Example {
description: "Split a list of lists into two lists of lists",
example: "[[1,2], [2,3], [3,4]] | split list [2,3]",
result: Some(Value::List {
vals: vec![
Value::List {
vals: vec![Value::List {
vals: vec![Value::test_int(1), Value::test_int(2)],
span: Span::test_data(),
}],
span: Span::test_data(),
},
Value::List {
vals: vec![Value::List {
vals: vec![Value::test_int(3), Value::test_int(4)],
span: Span::test_data(),
}],
span: Span::test_data(),
},
],
span: Span::test_data(),
}),
},
]
}
}
fn split_list(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let separator: Value = call.req(engine_state, stack, 0)?;
let mut temp_list = Vec::new();
let mut returned_list = Vec::new();
let iter = input.into_interruptible_iter(engine_state.ctrlc.clone());
for val in iter {
if val == separator && !temp_list.is_empty() {
returned_list.push(Value::List {
vals: temp_list.clone(),
span: call.head,
});
temp_list = Vec::new();
} else {
temp_list.push(val);
}
}
if !temp_list.is_empty() {
returned_list.push(Value::List {
vals: temp_list.clone(),
span: call.head,
});
}
Ok(Value::List {
vals: returned_list,
span: call.head,
}
.into_pipeline_data())
}

View File

@ -1,9 +1,11 @@
pub mod chars;
pub mod column;
pub mod command;
pub mod list;
pub mod row;
pub use chars::SubCommand as SplitChars;
pub use column::SubCommand as SplitColumn;
pub use command::SplitCommand as Split;
pub use list::SubCommand as SplitList;
pub use row::SubCommand as SplitRow;

View File

@ -28,7 +28,7 @@ impl Command for SubCommand {
}
fn search_terms(&self) -> Vec<&str> {
vec!["convert", "inverse"]
vec!["convert", "inverse", "flip"]
}
fn run(

View File

@ -29,6 +29,10 @@ impl Command for Ps {
"View information about system processes."
}
fn search_terms(&self) -> Vec<&str> {
vec!["procedures", "operations", "tasks", "ops"]
}
fn run(
&self,
engine_state: &EngineState,

View File

@ -194,9 +194,6 @@ impl ExternalCommand {
let (stderr_tx, stderr_rx) = mpsc::sync_channel(OUTPUT_BUFFERS_IN_FLIGHT);
let (exit_code_tx, exit_code_rx) = mpsc::channel();
#[cfg(unix)]
let (exit_status_tx, exit_status_rx) = mpsc::channel();
std::thread::spawn(move || {
// If this external is not the last expression, then its output is piped to a channel
// and we create a ListStream that can be consumed
@ -286,9 +283,6 @@ impl ExternalCommand {
span,
)),
Ok(x) => {
#[cfg(unix)]
let _ = exit_status_tx.send(x);
if let Some(code) = x.code() {
let _ = exit_code_tx.send(Value::Int {
val: code as i64,
@ -310,26 +304,6 @@ impl ExternalCommand {
let stderr_receiver = ChannelReceiver::new(stderr_rx);
let exit_code_receiver = ValueReceiver::new(exit_code_rx);
#[cfg(unix)]
{
use signal_hook::low_level::signal_name;
use std::os::unix::process::ExitStatusExt;
use std::time::Duration;
// The receiver will block 100ms if there's no sender
if let Ok(status) = exit_status_rx.recv_timeout(Duration::from_millis(100)) {
if status.core_dumped() {
if let Some(sig) = status.signal().and_then(signal_name) {
return Err(ShellError::ExternalCommand(
format!("{sig} (core dumped)"),
"Child process core dumped".to_string(),
span,
));
}
}
}
}
Ok(PipelineData::ExternalStream {
stdout: if redirect_stdout {
Some(RawStream::new(
@ -428,7 +402,7 @@ impl ExternalCommand {
/// Spawn a command without shelling out to an external shell
pub fn spawn_simple_command(&self, cwd: &str) -> Result<std::process::Command, ShellError> {
let head = trim_enclosing_quotes(&self.name.item);
let (head, _) = trim_enclosing_quotes(&self.name.item);
let head = nu_path::expand_to_real_path(head)
.to_string_lossy()
.to_string();
@ -436,8 +410,9 @@ impl ExternalCommand {
let mut process = std::process::Command::new(&head);
for arg in self.args.iter() {
let (trimmed_args, run_glob_expansion) = trim_enclosing_quotes(&arg.item);
let mut arg = Spanned {
item: remove_quotes(trim_enclosing_quotes(&arg.item)),
item: remove_quotes(trimmed_args),
span: arg.span,
};
@ -447,7 +422,7 @@ impl ExternalCommand {
let cwd = PathBuf::from(cwd);
if arg.item.contains('*') {
if arg.item.contains('*') && run_glob_expansion {
if let Ok((prefix, matches)) =
nu_engine::glob_from(&arg, &cwd, self.name.span, None)
{
@ -542,14 +517,14 @@ fn shell_arg_escape(arg: &str) -> String {
}
}
fn trim_enclosing_quotes(input: &str) -> String {
fn trim_enclosing_quotes(input: &str) -> (String, bool) {
let mut chars = input.chars();
match (chars.next(), chars.next_back()) {
(Some('"'), Some('"')) => chars.collect(),
(Some('\''), Some('\'')) => chars.collect(),
(Some('`'), Some('`')) => chars.collect(),
_ => input.to_string(),
(Some('"'), Some('"')) => (chars.collect(), false),
(Some('\''), Some('\'')) => (chars.collect(), false),
(Some('`'), Some('`')) => (chars.collect(), true),
_ => (input.to_string(), true),
}
}

View File

@ -6,7 +6,7 @@ use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Value,
};
use std::time::{Duration, UNIX_EPOCH};
use sysinfo::{ComponentExt, DiskExt, NetworkExt, ProcessorExt, System, SystemExt, UserExt};
use sysinfo::{ComponentExt, CpuExt, DiskExt, NetworkExt, System, SystemExt, UserExt};
#[derive(Clone)]
pub struct Sys;
@ -199,7 +199,7 @@ pub fn cpu(sys: &mut System, span: Span) -> Option<Value> {
sys.refresh_cpu();
let mut output = vec![];
for cpu in sys.processors() {
for cpu in sys.cpus() {
let mut cols = vec![];
let mut vals = vec![];

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -55,3 +55,15 @@ fn checks_all_columns_of_a_table_is_true() {
assert_eq!(actual.out, "true");
}
#[test]
fn checks_if_all_returns_error_with_invalid_command() {
let actual = nu!(
cwd: ".", pipeline(
r#"
[red orange yellow green blue purple] | all? ($it | st length) > 4
"#
));
assert!(actual.err.contains("can't run executable") || actual.err.contains("type_mismatch"));
}

View File

@ -31,3 +31,15 @@ fn checks_any_column_of_a_table_is_true() {
assert_eq!(actual.out, "true");
}
#[test]
fn checks_if_any_returns_error_with_invalid_command() {
let actual = nu!(
cwd: ".", pipeline(
r#"
[red orange yellow green blue purple] | any? ($it | st length) > 4
"#
));
assert!(actual.err.contains("can't run executable") || actual.err.contains("type_mismatch"));
}

View File

@ -17,6 +17,22 @@ fn copies_a_file() {
});
}
#[test]
fn copies_multiple_files() {
Playground::setup("cp_test_1_1", |dirs, sandbox| {
sandbox
.with_files(vec![EmptyFile("a.txt"), EmptyFile("b.txt")])
.mkdir("dest");
nu!(
cwd: dirs.test(),
"cp a.txt b.txt dest",
);
assert!(dirs.test().join("dest/a.txt").exists());
assert!(dirs.test().join("dest/b.txt").exists());
});
}
#[test]
fn copies_the_file_inside_directory_if_path_to_copy_is_directory() {
Playground::setup("cp_test_2", |dirs, _| {

View File

@ -1,5 +1,5 @@
use nu_test_support::nu;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
use std::fs;
#[test]
@ -18,3 +18,15 @@ def e [arg] {echo $arg}
assert!(actual.out.contains("My echo\\n\\n"));
});
}
#[test]
fn def_errors_with_multiple_short_flags() {
let actual = nu!(
cwd: ".", pipeline(
r#"
def test-command [ --long(-l)(-o) ] {}
"#
));
assert!(actual.err.contains("expected one short flag"));
}

View File

@ -0,0 +1,26 @@
use nu_test_support::{nu, pipeline};
#[test]
fn error_label_works() {
let actual = nu!(
cwd: ".", pipeline(
r#"
error make {msg:foo label:{text:unseen}}
"#
));
assert!(actual.err.contains("unseen"));
assert!(actual.err.contains("╰──"));
}
#[test]
fn no_span_if_unspanned() {
let actual = nu!(
cwd: ".", pipeline(
r#"
error make -u {msg:foo label:{text:unseen}}
"#
));
assert!(!actual.err.contains("unseen"));
}

View File

@ -335,6 +335,28 @@ fn lists_files_including_starting_with_dot() {
})
}
#[test]
fn lists_regular_files_using_multiple_asterisk_wildcards() {
Playground::setup("ls_test_10", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("los.txt"),
EmptyFile("tres.txt"),
EmptyFile("amigos.txt"),
EmptyFile("arepas.clu"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
ls *.txt *.clu
| length
"#
));
assert_eq!(actual.out, "4");
})
}
#[test]
fn list_all_columns() {
Playground::setup("ls_test_all_columns", |dirs, sandbox| {
@ -392,6 +414,74 @@ fn list_all_columns() {
});
}
#[test]
fn lists_with_directory_flag() {
Playground::setup("ls_test_flag_directory_1", |dirs, sandbox| {
sandbox
.within("dir_files")
.with_files(vec![EmptyFile("nushell.json")])
.within("dir_empty");
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
cd dir_empty;
['.' '././.' '..' '../dir_files' '../dir_files/*']
| each { |it| ls --directory $it }
| flatten
| get name
| to text
"#
));
let expected = [".", ".", "..", "../dir_files", "../dir_files/nushell.json"].join("");
#[cfg(windows)]
let expected = expected.replace("/", "\\");
assert_eq!(
actual.out, expected,
"column names are incorrect for ls --directory (-D)"
);
});
}
#[test]
fn lists_with_directory_flag_without_argument() {
Playground::setup("ls_test_flag_directory_2", |dirs, sandbox| {
sandbox
.within("dir_files")
.with_files(vec![EmptyFile("nushell.json")])
.within("dir_empty");
// Test if there are some files in the current directory
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
cd dir_files;
ls --directory
| get name
| to text
"#
));
let expected = ".";
assert_eq!(
actual.out, expected,
"column names are incorrect for ls --directory (-D)"
);
// Test if there is no file in the current directory
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
cd dir_empty;
ls -D
| get name
| to text
"#
));
let expected = ".";
assert_eq!(
actual.out, expected,
"column names are incorrect for ls --directory (-D)"
);
});
}
/// Rust's fs::metadata function is unable to read info for certain system files on Windows,
/// like the `C:\Windows\System32\Configuration` folder. https://github.com/rust-lang/rust/issues/96980
/// This test confirms that Nu can work around this successfully.

View File

@ -291,7 +291,7 @@ fn duration_math() {
"#
));
assert_eq!(actual.out, "8day");
assert_eq!(actual.out, "1wk 1day");
}
#[test]
@ -315,7 +315,7 @@ fn duration_math_with_nanoseconds() {
"#
));
assert_eq!(actual.out, "7day 10ns");
assert_eq!(actual.out, "1wk 10ns");
}
#[test]
@ -327,7 +327,22 @@ fn duration_decimal_math_with_nanoseconds() {
"#
));
assert_eq!(actual.out, "10day 10ns");
assert_eq!(actual.out, "1wk 3day 10ns");
}
#[test]
fn duration_decimal_math_with_all_units() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
5dec + 3yr + 2month + 1wk + 3day + 8hr + 10min + 16sec + 121ms + 11us + 12ns
"#
));
assert_eq!(
actual.out,
"53yr 2month 1wk 3day 8hr 10min 16sec 121ms 11µs 12ns"
);
}
#[test]

View File

@ -15,6 +15,7 @@ mod each;
mod echo;
mod empty;
mod enter;
mod error_make;
mod every;
mod find;
mod first;

View File

@ -22,6 +22,36 @@ fn moves_a_file() {
})
}
#[test]
fn moves_multiple_files() {
Playground::setup("mv_test_1_1", |dirs, sandbox| {
sandbox
.mkdir("expected")
.with_files(vec![EmptyFile("andres.txt"), EmptyFile("yehuda.txt")])
.within("foo")
.with_files(vec![EmptyFile("bar.txt")]);
let original_1 = dirs.test().join("andres.txt");
let original_2 = dirs.test().join("yehuda.txt");
let original_3 = dirs.test().join("foo/bar.txt");
let expected_1 = dirs.test().join("expected/andres.txt");
let expected_2 = dirs.test().join("expected/yehuda.txt");
let expected_3 = dirs.test().join("expected/bar.txt");
nu!(
cwd: dirs.test(),
"mv andres.txt yehuda.txt foo/bar.txt expected"
);
assert!(!original_1.exists());
assert!(!original_2.exists());
assert!(!original_3.exists());
assert!(expected_1.exists());
assert!(expected_2.exists());
assert!(expected_3.exists());
})
}
#[test]
fn overwrites_if_moving_to_existing_file() {
Playground::setup("mv_test_2", |dirs, sandbox| {

View File

@ -16,26 +16,35 @@ fn port_with_invalid_range() {
#[test]
fn port_with_already_usage() {
let (tx, rx) = mpsc::sync_channel(0);
let retry_times = 10;
for _ in 0..retry_times {
let (tx, rx) = mpsc::sync_channel(0);
// let system pick a free port for us.
let free_port = {
let listener = TcpListener::bind("127.0.0.1:0").expect("failed to pick a port");
listener.local_addr().unwrap().port()
};
let handler = std::thread::spawn(move || {
let _listener = TcpListener::bind(format!("127.0.0.1:{free_port}"));
let _ = rx.recv();
});
let actual = nu!(
cwd: ".", pipeline(&format!("port {free_port} {free_port}"))
// let system pick a free port for us.
let free_port = {
let listener = TcpListener::bind("127.0.0.1:0").expect("failed to pick a port");
listener.local_addr().unwrap().port()
};
let handler = std::thread::spawn(move || {
let _listener = TcpListener::bind(format!("127.0.0.1:{free_port}"));
let _ = rx.recv();
});
let actual = nu!(
cwd: ".", pipeline(&format!("port {free_port} {free_port}"))
);
let _ = tx.send(true);
// make sure that the thread is closed and we release the port.
handler.join().unwrap();
// check for error kind str.
if actual.err.contains("AddrInUse") {
return;
}
}
assert!(
false,
"already check port report AddrInUse for seveval times, but still failed."
);
let _ = tx.send(true);
// make sure that the thread is closed and we release the port.
handler.join().unwrap();
// check for error kind str.
assert!(actual.err.contains("AddrInUse"))
}
#[test]

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