Compare commits

...

387 Commits

Author SHA1 Message Date
370639d7d7 Fix ls panics when a file or directory not exists (#6148)
* Fix ls panics when a file or directory not exists

Fixes #6146

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

* add test

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-28 09:16:52 +12:00
d326f6def6 winget wants this to match (#6152)
See the link below for more information
https://github.com/microsoft/winget-pkgs/pull/67598#issuecomment-1196952191
2022-07-28 09:16:14 +12:00
JT
d42cfab6ef bump to 0.66.1 dev version (#6140) 2022-07-27 13:15:04 +12:00
JT
2b7c811402 fix 0.66 nu-command crate (#6138) 2022-07-27 11:20:12 +12:00
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
390d06d4e7 add bytes starts-with command (#5950)
* refactor operate, make it generic

* refactor operate, add starts with command

* add comment

* remove useless file
2022-07-05 06:42:01 -05:00
89acbda877 Pin reedline to new 0.8.0 release (#5954)
For the nushell 0.65.0 release

https://github.com/nushell/reedline/releases/tag/v0.8.0
2022-07-05 21:25:35 +12:00
JT
0d40d0438f bump to 0.65 (#5952) 2022-07-05 17:54:16 +12:00
1e8212a938 add bytes len (#5945) 2022-07-04 05:51:07 -05:00
JT
2da8310b11 Fix 'skip' support for binary streams (#5943) 2022-07-04 19:53:54 +12:00
JT
c16d8f0d5f Make take work like first (#5942) 2022-07-04 08:03:35 +12:00
JT
2ac5b0480a Binary into int (#5941)
* Add support for binary to into int

* Add test
2022-07-04 06:31:50 +12:00
4e90b478b7 Add bit operator: bit-xor (#5940) 2022-07-03 06:45:20 -05:00
3a38fb94f0 add search terms for is-admin (#5939) 2022-07-03 06:44:26 -05:00
c6f6dcb57c Change C-u and C-k to be readline compatible, move old C-u to C-s (#5938) 2022-07-03 06:43:56 -05:00
b80299eba7 change default keybinding in default config (#5925)
* change default keybinding in default config

* change from alt-o to ctrl-o

* change back to alt-o

* really changed it back to alt-o this time

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-02 18:12:03 -05:00
JT
a48616697a Rename bitwise operators for readability (#5937) 2022-07-02 17:05:02 -05:00
b82dccf0bd Add band and bor operator for bit operations (#5936)
* Add `band` and `bor` Operator

* Add tests
2022-07-02 13:03:36 -05:00
84caf8859f add -e flag to print, to print the value to stderr (#5935)
* Refactor: make stdout write all and flush as generic function

* support print to stderr
2022-07-02 09:54:49 -05:00
be7f35246e Fix to md --pretty when rendering a list (#5932)
Fixes #5931

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-02 15:36:16 +03:00
3917fda7ed Update #4202: Add shift operator bshl and bshr for integers (#5928)
* Update #4202: Add shift operator bshl and bshr for integers

* Add more tests
2022-07-02 06:48:43 -05:00
3b357e5402 fix parse_failure_due_conflicted_flags test (#5926) 2022-07-01 21:59:51 -05:00
79da470239 simplify error make (#5883) 2022-07-01 21:06:36 -05:00
37949e70e0 Add all flag to nu-check command (#5911)
* Add all flag

* Make all and moduel flags as mutually exclusive

* Fix new test

* format code...

* tweak words

* another tweak

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-07-01 15:49:24 -05:00
5d00ecef56 Return error when external command core dumped (#5908)
* Return error when external command core dumped

Fixes #5903

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

* Use signal-hook to get signal name

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

* Fix comment

Signed-off-by: nibon7 <nibon7@163.com>
2022-07-01 08:58:21 -05:00
6dde231dde make module imports in scripts used for relative path. (#5913)
* always load env

* add interactive argument for read_config_file
2022-07-01 06:35:09 -05:00
58fa2e51a2 update crate thiserror to version 1.0.31 in crates nu-cli, nu-command, nu-parser, nu-protocol (#5919) 2022-06-30 13:55:01 -07:00
cf0877bf72 ensure required positionals don't show up as optional when help (#5916)
* ensure `required` positionals show up as `required` when `help`

* moves it to the older format

* standardises across optional and required parameters
2022-07-01 05:51:41 +12:00
a0db4ce747 Better error handling using do (#5890)
* adds `capture-errors` flag for `do`

* adds `get-type` core command to get type

* fmt

* add tests in example

* fmt

* fix tests

* manually revert previous changes related to `get-type`

* adds method to check for error name using `into string`

* fix clippy
2022-06-29 20:01:34 -05:00
6ee13126f7 Update Dockerfile (#5910)
Container now uses unpriviledged user with UID 1000 by default
Container now uses Alpine as base
Final image size dropped to just 67MB
2022-06-29 18:36:24 -05:00
1c15a4ed3a docs: clarify print and echo commands (#5909)
I thought this comment was relevant:
https://github.com/nushell/nushell/issues/5724#issuecomment-1148164153
2022-06-29 18:43:46 -04:00
7aabc381a3 fix bug where thin theme wasn't getting applied correctly (#5905) 2022-06-28 14:14:20 -05:00
8c9dced71b fix excessive ansi escape sequences (#5901) 2022-06-27 18:51:14 -05:00
06d5a31301 Make sort logic available outside sort-by (#5893) 2022-06-27 13:36:59 -04:00
ffbc0b0180 Header filtering out of for loop (#5896)
* remove extra print

* dataframe with real index

* corrected dataframe tests

* clippy error

* clippy error

* moved header filter out of loop
2022-06-27 06:33:45 -05:00
c0901ef707 Dataframe with real index (#5892)
* remove extra print

* dataframe with real index

* corrected dataframe tests

* clippy error

* clippy error
2022-06-26 17:32:18 -05:00
d3e84daa49 remove extra print (#5891) 2022-06-26 11:48:30 -05:00
228ede18cf build: update miette dependency (#5889) 2022-06-26 07:03:38 -05:00
c5a69271a2 make path exists work on expanded path (#5886)
* make path exists works with home

* fix test name
2022-06-26 06:55:55 -05:00
dc9d939c83 Introduce new command - nu check (#5864)
* nu check command - 1

* Support stream

* Polish code and fix corner case
2022-06-26 06:53:06 -05:00
32f0f94b46 feat: add --binary(-b) option to hash commands (#5885)
For instance,

```
echo 'abcdefghijklmnopqrstuvwxyz' | hash sha256 --binary
```

Will returns the hash as a binary value instead of a hexadecimaly encoded string.
2022-06-26 06:50:56 -05:00
a142d1a192 update encode decode with new signature (#5881) 2022-06-25 19:06:39 -05:00
173d60d59d Deprecate hash base64, extend decode and add encode commands (#5863)
* feat: deprecate `hash base64` command

* feat: extend `decode` and `encode` command families

This commit
- Adds `encode` command family
- Backports `hash base64` features to `encode base64` and `decode base64` subcommands.
- Refactors code a bit and extends tests for encodings
- `decode base64` returns a binary `Value` (that may be decoded into a string using `decode` command)

* feat: add `--binary(-b)` flag to `decode base64`

Default output type is now string, but binary can be requested using this new flag.
2022-06-26 00:35:23 +03:00
JT
f2989bf704 Move input/output type from Command to Signature (#5880) 2022-06-26 09:23:56 +12:00
JT
575ddbd4ef Clippy and remove unused is_binary (#5879) 2022-06-26 08:20:28 +12:00
ef9b72d360 add ability to convert timestamp_millis() (#5876)
* add ability to convert timestamp_millis()

* add example test

* add nanos too
2022-06-25 09:51:41 -05:00
25349a1eac Add an example for default command to get an env var with fallback (#5874)
* Add an example for `default` command to get an env var with fallback

* update test

* update test
2022-06-25 17:27:54 +08:00
99e4c44862 Fix less.exe downloading for windows release pkgs, close #5868 (#5873)
* Fix less.exe downloading for windows release pkgs

* Fix less.exe downloading for windows release pkgs
2022-06-25 09:09:48 +08:00
1345f97202 Errors when let in, let env and similar commands are passed. (#5866)
* throw `let nu/env/nothing/in` error in parsing

* add tests and fmt

* fix clippy

* suggestions

* fmt

* `lvalue.span` instead of `spans[1]`

* clippy

* fmt
2022-06-25 00:55:25 +03:00
f02076daa8 fix plugin path with whitespace (#5871) 2022-06-24 12:44:22 -05:00
JT
533e04a60a Bump to 0.64.1 dev version (#5865) 2022-06-24 16:47:00 +12:00
13c152b00f finish git fetch custom completions (#5859) 2022-06-23 05:19:11 -05:00
f231a6df4a Remove quotes from external args (#5846)
* remove quotes from external args

* remove internal quotes

* correct escaped quotes in string
2022-06-22 22:01:44 -05:00
3c0bccb900 Exclude ./... from expansion (#5839)
* exclude ./... from expansion

* use all instead of any

* no path expansion for external arguments

* clippy error

* expand only tilde
2022-06-22 22:00:30 -05:00
f43a65d7a7 Prevents duplicate fields in transpose -r (#5840) 2022-06-22 19:19:06 -05:00
0827ed143d cleanup $config as a built-in (#5852) 2022-06-22 13:13:03 -05:00
4b84825dbf Remove externa nu from nu config (#5847) 2022-06-22 09:42:18 +03:00
82ae06865c Port command (#5849)
* implement port command

* better comment

* fmt code

* fix example description

* fix usage

* fix tests
2022-06-21 23:27:58 -04:00
128ce6f9b7 update reedline config based on recent reedline changes (#5845) 2022-06-21 12:22:11 -05:00
44cbd88b55 allow comparison for similar types (#5844) 2022-06-21 12:15:31 -05:00
7164929c61 Db commands without DB (#5838)
* database commands without db

* database command tests
2022-06-21 12:14:29 -05:00
848ff8453b feat: Update dockerfile for latest nu release (#5843) 2022-06-21 18:28:31 +08:00
f94ca6cfde root/admin prompt is red now (#5836)
I really miss bash's visual way of signalising root, i.e. blue: user, red: root

So I brought it to nushell (since you've added `is-admin` the code is fully portable and easily-readable) and hope you'll like it
2022-06-20 15:23:55 -05:00
fab3f8fd40 fix exit code (#5835)
* fix exit code

* fix usage

* add comment
2022-06-20 09:05:11 -05:00
dbcfcdae89 calculates history duration properly (#5827) 2022-06-19 00:44:46 -04:00
08aa248c42 Add more tests for completion (#5826)
* Add more tests for completion

* Fix windows

* Cleanup
2022-06-18 19:42:00 -05:00
9f07bcc66f first stab at minimizing ansi escapes (#5822) 2022-06-17 22:07:46 -05:00
2caa44cea8 Fix parser panic (#5820) 2022-06-17 11:11:48 -07:00
28c21121cf fixes to nuon for inf, -inf, and NaN (#5818) 2022-06-17 21:01:37 +03:00
a17d46f200 add more columns to the history command when using sqlite history (#5817) 2022-06-17 09:35:34 -05:00
6cc8402127 Standardise to commands (#5800)
* standarize to commands

* move from to to into
2022-06-17 07:51:50 -05:00
5f0ad1d6ad Fix alias completion crash (#5814)
* Solve crash - commit 1

* commit 2 with issue

* Fix corner case

* Unit tests

* Fix windows tests
2022-06-17 07:50:10 -05:00
8d7bb9147e Attempts to fix file completions for open, rm and ls (and other filesystem commands) (#5805)
* fixes the issue for 'open' and other commands that explicitly use `SyntaxShape::Filepath`

* fixes for `rm` and similar commands

* fixes for `ls`: potentially breaking?

* fmt

* a curious fix to the test

* a curious fix to the test, except for Windows this time

* fixes Windows tests failing

* resolves it by putting an explicit check for `ls`

* reverts unnecessary test changes; fmt

* changes the order of completion operations
2022-06-17 07:47:43 -05:00
bc48b4553c Move the history and tutor commands out of core_commands (#5813)
* move history and tutor commands from core to misc

* add in the Misc Category for the history and tutor commands
2022-06-16 09:58:38 -07:00
28c07a5072 Add Windows Terminal profile and icon in Windows control panel (#5812)
* Show icon in Windows 'Add/Remove Programs' control panel

* Add install option for Windows Terminal profile

* Re-create icon because the icon was not shwon in Windows Terminal

Procedure: opened the original file with GIMP and simply overwrited it
2022-06-16 09:46:33 -07:00
30c8dabeb4 Add test requirements to PR template (#5809)
* Add test requirements to PR template

* tweak words

* another tweak

* add /tests folder as a suggestion

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-06-16 08:14:59 -05:00
8b368b6a4e Fix drop nth with open end range on 32-bit platforms (#5808)
Fixes #5793

Signed-off-by: nibon7 <nibon7@163.com>
2022-06-16 06:39:48 -05:00
8c0d60d0fb add notes for def_env (#5807)
* add notes for def_env

* Update crates/nu-command/src/core_commands/export_def_env.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* Update crates/nu-command/src/core_commands/def_env.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2022-06-16 06:37:44 -05:00
8b0a4ccf4c add light theme to default_config (#5804) 2022-06-16 06:19:49 -05:00
cfe4eff566 update default_context.rs to put the Du command in platform instead core (#5795) 2022-06-15 11:11:26 -07:00
38f3957edf update polars (#5791) 2022-06-15 11:45:03 -05:00
cb66d2bcad Try to fix winget package submit (#5790) 2022-06-15 07:35:28 -05:00
ff73623873 shows location of sqlite3 history file (#5784)
* shows location of sqlite3 file

* fmt
2022-06-15 10:06:49 +02:00
JT
d1c719a8cc bump to 0.64 (#5777)
Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2022-06-15 14:39:17 +12:00
4d854f36af add --values flag to sort record by values, by default, sort record by keys (#5782) 2022-06-14 20:42:22 -05:00
8d5848c955 bool type for binary operations (#5779)
* bool type for binary operations

* fixed type in commands
2022-06-14 20:31:14 -05:00
fe88d58b1e Pin reedline v0.7.0 for the nushell v0.64.0 release (#5781)
Includes the new History API and sqlite history backend

Release notes: https://github.com/nushell/reedline/releases/tag/v0.7.0
2022-06-14 23:21:14 +02:00
42dbfd1fa0 SQLite History MVP with timestamp, duration, working directory, exit status metadata (#5721)
This PR adds support for an SQLite history via nushell/reedline#401

The SQLite history is enabled by setting history_file_format: "sqlite" in config.nu.

* somewhat working sqlite history
* Hook up history command
* Fix error in SQlitebacked with empty lines

When entering an empty line there previously was the "No command run"
error with `SqliteBackedHistory` during addition of the metadata

May be considered a temporary fix

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2022-06-14 22:53:33 +02:00
534e1fc3ce Add NU config to allow user be able to turn off external completion (#5773)
* 06-07-wsl

* 06-07-linux-issue-with-delete-input

* 06-08-2023

* 06-08-Linux

* commit for merge

* Fix unit test

* format

* clean code

* Add flag to turn off external completion

* change env var to config

* Fix comment

Co-authored-by: Frank Zhang <v-frankz@microsoft.com>
2022-06-14 14:28:11 -05:00
ff946a2f21 each while command (#5771)
* each while command

* test value adjustment
2022-06-14 16:16:31 +02:00
3c0cbec993 sort not change shape (#5778) 2022-06-14 06:41:45 -05:00
48e29e9ed6 path join support multi path (#5775) 2022-06-14 06:34:00 -05:00
ff53352afe Add option to sort-by naturally (#5774)
* add `natural` option to sort-by

* clippy

* Add tests
2022-06-14 09:03:13 +03:00
4fd4136d50 Should we keep old semantics of uniq command? (#5761)
* Update uniq tests with less surprising output

* Remove original nushell surprising semantics
2022-06-14 16:04:29 +12:00
dc1248a454 Fix drop nth bug (#5312)
* Fix drop nth bug on ranges. Should fix & close #5260

* Fix drop nth bug on ranges. Should fix & close #5260

* Add support for ranges

* Working version of drop nth, but the issue is that we unwrap the value which is problematic for Streams. Should convert to the way @stormasm was doing it before and implement the range check

* Fix fmt issue

* Drop nth now works for Lists, Records, and Ranges. We need support for ListStreams and for ExternalStreams

* Keep consistent naming

* Fix fmt issue

* Support ListStreams for drop nth

* Use DropNthIterator instead

* Found a more elegant way to deal with the check for no upper bound input

* Add extra checks for negative inputs or to < from for ranges

Co-authored-by: Stefan Stanciulescu <test@test.com>
2022-06-13 20:49:59 -07:00
de554f8e5f filesize conversion (#5770) 2022-06-13 14:44:32 -05:00
44979f3051 expression to literal (#5769) 2022-06-13 13:22:46 -05:00
JT
7ae7394c85 Force floats to output a decimal in nuon (#5768)
* Force floats to output a decimal in nuon

* Add test
2022-06-14 05:45:07 +12:00
9dbf7556b8 more verbose error handling (#5765) 2022-06-13 07:01:00 -05:00
caafd26deb Attempts to add // math operator (#5759)
* attempts to add `div` math operator

* allows `//` to be used too

* fmt:

* clippy issue

* returns appropriate type

* returns appropriate type 2

* fmt

* ensure consistency; rename to `fdiv`

* Update parser.rs
2022-06-13 13:54:47 +03:00
43a218240c Add setup-nu link in README.md (#5763) 2022-06-13 17:40:38 +08:00
11d7d8ea1e Remove dfr from dataframe commands (#5760)
* input and output tests

* input and output types for dfr

* expression converter

* remove deprecated command

* correct expressions

* cargo clippy

* identifier for ls

* cargo clippy

* type for head and tail expression

* modify full cell path if block
2022-06-12 14:18:00 -05:00
2dea9e6f1f fix arg parse (#5754)
* fix arg parse

* add ut, fix clippy

* simplify code

* fmt code
2022-06-11 20:52:31 +12:00
c5cb369d8d While starting nu, force PWD to be current working directory (#5751)
* fix current working directory during start

* fix tests

* always set PWD to current_dir
2022-06-10 13:01:08 -05:00
b6959197bf Support completion for alias and sub-command (#5749)
* 06-07-wsl

* 06-07-linux-issue-with-delete-input

* 06-08-2023

* 06-08-Linux

* commit for merge

* Fix unit test

* format

* clean code

Co-authored-by: Frank Zhang <v-frankz@microsoft.com>
2022-06-10 12:59:15 -05:00
d5b99ae316 input and output types (#5750)
* input and output types

* added description

* type from stored variable

* string in custom value

* more tests with non custom
2022-06-10 10:59:35 -05:00
9d10007085 Temporarily disable rust-cache in tests (#5747) 2022-06-09 12:03:56 -04:00
2e0b964d5b handle SIGQUIT (#5744)
* handle sigquit

* fix clippy
2022-06-09 07:08:15 -05:00
5bae7e56ef Add $nu.scope.engine_state (#5739)
* Add number of items present in engine state

* Rename num_decls column to num_commands
2022-06-08 13:31:36 -05:00
b42ef45c7c add as record tag to transfer result to record (#5736)
* add as record tag to transfer result to record

* tweak text

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-06-08 07:00:19 -05:00
3423cd54a1 add search terms to alias (#5737) 2022-06-08 06:22:53 -05:00
837f0463eb updated Dir to dir 2022-06-07 14:58:23 -05:00
56f6f683fc Clean up README (#5718)
* Clean up README

* Update CONTRIBUTING.md

* Another pass over the README. Table of contents, more install info

* add a little extra features definition

* fix Winget instructions

* Change winget instructions to nushell (easier to remember)

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-06-07 12:47:08 -07:00
c57f41e5f2 make to text work more intuitively (#5733) 2022-06-07 14:43:24 -05:00
8c74b1e437 print warning message if meet non utf-8 path (#5731) 2022-06-07 08:22:52 -05:00
8318d59ef1 improve str substring (#5730) 2022-06-07 06:09:16 -05:00
64efa30f3e fix: normalize some parameter names (#5725) 2022-06-06 09:42:13 -05:00
820a6bfb08 feat: add search terms to category of strings (#5723) 2022-06-06 08:47:09 -05:00
b8d253cbd7 Attempts to add a command that checks if nushell is running with admin priveleges (#5712)
* attempts to add is-admin command

* fmt and clippy

* fmt

* Update is_admin.rs

* typos

* typo in example
2022-06-06 06:55:23 -05:00
3c421c5726 Added loginshell config file #4620 (#5714)
* Added loginshell config file #4620

* added sample login.nu

* added environment variable loginshell-path
2022-06-06 06:52:37 -05:00
75b2d26187 fix argument type (#5695)
* fix argument type

* while run external, convert list argument to str

* fix argument converting logic

* using parse_list_expression instead of parse_full_cell_path

* make parsing logic more explicit

* revert changes

* add tests
2022-06-06 13:19:06 +03:00
17a5aa3052 Statically link the CRT on Windows (#5717) 2022-06-05 17:01:01 -07:00
e4a22799d5 nu-engine: better display for shape when showing help params (#5715) 2022-06-05 08:13:04 -05:00
fda456e469 make range require the rows (#5710) 2022-06-04 18:48:01 +12:00
e5d38dcff6 Address lints from clippy for beta/nightly (#5709)
* Fix clippy lints in tests

* Replace `format!` in `.push_str()` with `write!`

Stylistically that might be a bit rough but elides an allocation.

Fallibility of allocation is more explicit, but ignored with `let _ =`
like in the clippy example:

https://rust-lang.github.io/rust-clippy/master/index.html#format_push_string

* Remove unused lifetime

* Fix macro crate relative import

* Derive `Eq` for `PartialEq` with `Eq` members

https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq

* Remove unnnecessary `.to_string()` for Cow<str>

* Remove `.to_string()` for `tendril::Tendril`

Implements `Deref<Target = str>`
2022-06-04 18:47:36 +12:00
a82fa75c31 Update nu-ansi-term to remove Deref impl (#5706)
Resolves an unexpected issue due to `Deref` and `ToString` interacting

Details: https://github.com/nushell/nu-ansi-term/pull/5 and https://github.com/nushell/reedline/pull/435#issuecomment-1141348209

Also updates reedline: Includes a fix for a panic when the directory containing the history is deleted during a running reedline session. (nushell/reedline#436)
2022-06-03 21:38:54 +02:00
0c16464320 Use search terms in the help menu search (#5708)
Currently only `help --find` was using the search terms.  With this change they will also be used by the `F1` help menu
2022-06-03 20:30:36 +02:00
888758b813 Fix ls for Windows system files (#5703)
* Fix `ls` for Windows system files

* Fix non-Windows builds

* Make Clippy happy on non-Windows platforms

* Fix new test on GitHub runners

* Move ls Windows code into its own module
2022-06-03 12:37:27 -04:00
cb909f810e fix[table]: Panic when passthru small number of table -w. (#5705) 2022-06-03 07:46:36 -05:00
a75318d7e8 Improve internal documentation of save command (#5704)
- Example for `--append` mode.
- Search terms for redirection
2022-06-03 11:35:31 +02:00
7a9bf06005 Minor fixes to shell integation in repl. (#5701)
Added CMD_FINISHED_MARKER to be emitted when command finishes.
Also switched the names PRE_EXECUTE_MARKER and PRE_PROMPT_MARKER
as the old names were confusing/wrong.
2022-06-02 17:57:19 -05:00
a06299c77a Improve <table> output of 'to html', (#5699)
* Fix <table> output of 'to html',

Specifically, add <thead> and <tbody> elements.
That allows for better styling and (future) some neat JavaScript.

* Update tests for previous <table> changes.
2022-06-02 17:34:31 -05:00
e4bcd1934d Add completions for nu (#5700) 2022-06-02 17:12:59 -05:00
4673adecc5 Fix wrong path help message (#5698) 2022-06-02 23:00:29 +03:00
1b8051ece5 Fix doc building for vuepress-next, avoid using angle brackets (#5696)
* Fix doc building for vuepress-next, avoid using angle brackets

* [ci skip]
2022-06-02 17:38:42 +08:00
d44059c36b feat: Add sensitive flag to get, fix #4295 (#5685)
* feat: Add insensitive flag to get, fix #4295

* add get insensitive example

* Fix get flags

* Update get examples
2022-06-01 08:34:42 -05:00
b79abdb2a5 small typo fix (#5693) 2022-05-31 21:24:16 -05:00
ee8a0c9477 Fix cp bug (#5642) 2022-05-31 18:24:33 -05:00
41853b9f18 expand env for path (#5692) 2022-05-31 12:51:42 +03:00
997d56a288 Lazy dataframes (#5687)
* change between lazy and eager

* when expressions

* examples for aggregations

* more examples for agg

* examples for dataframes

* checked examples

* cargo fmt
2022-05-31 07:29:55 +01:00
0769e9b750 make ls works better with glob (#5691)
* fix glob behavior

* fix doc
2022-05-30 19:13:27 -05:00
f5519e2a09 base64 command more friendly (#5680)
* base64 command more friendly

* using match instead of so much else if..
2022-05-30 09:30:16 +02:00
8259d463aa Update reedline (#5678)
More fixes/changes to default keybindings
2022-05-30 09:26:57 +02:00
e2c015f725 Clarify error message for let in pipeline (#5677)
Refer to the suggestion as an assignment
2022-05-30 09:26:33 +02:00
eb12fffbc6 prevent panic with let alone in pipeline (#5676)
* prevent panic with `let` alone in pipeline

* Update parser.rs
2022-05-29 22:16:41 +02:00
c42096c34e Add '-o'/--output flag to fetch to download to file (#5673)
* attemps to add '-o' flag to `fetch`

* fmt

* changed from 'output' to 'file'.

* Revert "changed from 'output' to 'file'."

As @hustcer mentioned, all typical command line tools for downloading
use `-o` or `-O` and a variation on `--output` for the file

This reverts commit 6baf718f91.

Co-authored-by: sholderbach <sholderbach@users.noreply.github.com>
2022-05-29 18:32:30 +02:00
46eb34b35d Differentiate internal signature from external signature w.r.t. help (#5667)
* Differentiate internal signature from external signature w.r.t. help

* Add in the --help flag to default externs in default config

* Remove unusued build_extern

Co-authored-by: mjclements <clements.michael.james@gmail.com>
2022-05-29 15:14:15 +02:00
23a73cd31f feat: Add search terms to find, where, exit, which and fetch, update #5093 (#5671)
* feat: Add search terms to find, where, exit, which and fetch, update #5093

* Update crates/nu-command/src/filters/where_.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* Update crates/nu-command/src/filters/find.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

* Update crates/nu-command/src/shells/exit.rs

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2022-05-28 13:25:11 +02:00
6c07bc10e2 feat: Refactor and optimize the github release workflow: deliver binary package for more targets (#5649) 2022-05-28 10:41:47 +08:00
6365ba0286 Add search terms for all?, any?, length, and keybindings (#5665)
* Add search terms for `all?`

JavaScript has `Array.every` similar to `all?`

* Add search terms for `any?`

JavaScript has `Array.some` similar to `any?`

* Add search terms for `length`

Count, `len()`, and `size`/`sizeof` in widely-known programming languages are equivalent to `length`

* Add search terms for `keybindings`

Shortcut and hotkey are common synonyms (especially in web and GUI land) for keybindings.
2022-05-27 16:38:54 +02:00
545b1dcd94 Add search terms to error make (#5657)
* add search terms to error make

* add throw

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-05-27 06:04:33 -05:00
fb89f2f48c Update reedline: Support more bindings in vi mode (#5654)
Now more bindings are shared between vi-mode and emacs mode.
E.g. Ctrl-D, Ctrl-C, Ctrl-L, Ctrl-O will work in all modes.

Also arrow navigation extra functions will behave consistent.
2022-05-26 23:46:18 +02:00
f6ee21f76b nu-cli/completions: add filtering tests for variables completions (#5653) 2022-05-26 23:38:03 +02:00
d69a4db2e7 Unpin reedline for regular development (#5634)
Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
2022-05-26 23:21:16 +02:00
d4bfbb5eaf feat: add search terms to random & typo fix (#5652)
Co-authored-by: chinsaw <chinsaw@example.com>
2022-05-26 13:09:22 -07:00
507f24d029 Improve test coverage of command examples (#5650)
* Ignore `cargo tarpaulin` output files

* Add expected result for `columns` example

Examples without provided expected output will never be tested.
The subset of commands available in `test_examples()` is limited thus
excluding the tests depending on other commands

* Add example test harness to `reject`

* Test and fix `wrap` example

* Test and fix `drop column` example

* Update `from ods` examples

* Update `from xlsx` examples

* Run `to nuon` examples

* Run `hash base64` examples

* Add example output to `path parse`

* Test and fix the `grid` examples
2022-05-26 13:51:31 -05:00
230c36f2fb Don't build OpenSSL on Windows (#5651) 2022-05-26 14:28:59 -04:00
219c719e98 make cp can copy folders contains dangling symbolic link (#5645)
* cp with no dangling link

* add -p to not follow symbolic link

* change comment

* add one more test case to check symblink body after copied

* better help message
2022-05-26 10:42:52 -05:00
50146bdef3 Shorten the links of parser keywords help msgs (#5648) 2022-05-26 18:15:36 +03:00
2042f7f769 Add 'overlay new' command (#5647)
* Add 'overlay new' command

* Add missing file
2022-05-26 17:47:04 +03:00
0594f9e7aa add case_sensitive_completions config option (#5646) 2022-05-26 09:22:20 -05:00
3b8deb9ec7 Add search terms for describe (#5644) 2022-05-26 08:11:45 -05:00
727ff5f2d4 feat[table]: Allow specific table width with -w, like command grid. (#5643) 2022-05-26 06:53:05 -05:00
3d62528d8c Makes a more helpful error for let in pipeline (#5632)
* a more helpful error for let in pipeline

* a more helpful error for let in pipeline fmt

* changed help message

* type-o

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-05-25 19:13:14 -05:00
a42d419b66 nu-cli/completions: fix filter for variable completions (#5641) 2022-05-25 19:10:46 -05:00
9602e82029 make sure no duplicate records exists during eval and merge (#5633) 2022-05-25 19:10:31 -05:00
JT
8e98df8b28 bump to dev version (#5635) 2022-05-25 19:09:44 -05:00
2daf8ec72d cargo update (#5639) 2022-05-25 13:13:14 -04:00
afcacda35f Change embed-resource dep to slimmer winres (#5630) 2022-05-24 23:28:10 -04:00
JT
06cf3fa5ad Bump to 0.63 (#5627) 2022-05-25 11:33:28 +12:00
9a482ce284 Overlay keep (#5629)
* Allow env vars to be kept from removed overlay

* Rename --keep to --keep-custom; Add new test

* Rename some symbols

* (WIP) Start working on --keep for defs and aliases

* Fix decls/aliases not melting properly

* Use id instead of the whole cloned overlay

* Rewrite overlay remove for no reason

Doesn't fix the bug but at least looks better.

* Rename variable

* Fix adding overlay env vars

* Add more tests; Fmt + Clippy
2022-05-25 09:22:17 +12:00
8018ae3286 Pin reedline v0.6.0 for the nushell v0.63.0 release (#5620)
Release notes: https://github.com/nushell/reedline/releases/tag/v0.6.0

This release contains several bug fixes and improvements to the vi-emulation and documentation.

- Improvements to the vi-style keybindings (@sadmac7000):
  - `w` now correctly moves to the beginning of the word.
  - `e` to move to the end of the word.
- Bugfixes:
  - Support terminal emulators that erroneously report a size of 0x0 by assuming a default size to avoid panics and draw nevertheless (@DhruvDh)
  - Fix `ListMenu` layout calculations. Avoids scrolling bug when wrapping occurs due to the line numbering (@ahkrr)
  - Avoid allocating to the total history capacity which can cause the application to go out of memory (@sholderbach)
- Documentation improvements including addition of documentation intended for reedline developers (@petrisch, @sholderbach)
2022-05-24 00:39:55 +02:00
ef322a24c5 fix date format (#5619) 2022-05-23 09:59:34 -07:00
a8db4f0b0e load config when requried (#5618) 2022-05-23 15:47:08 +03:00
98a4280c41 Add octal binary literals (#5604)
Schema `0o[77]` with the same padding behavior as the other binary literals

- this updates #5551
- test for parsing binary from octal
- test for string parsing
2022-05-23 11:01:15 +02:00
0e1bfae13d Fallback for config.buffer_editor from EDITOR (#5614)
For the reedline `buffer_editor` use the `EDITOR` and `VISUAL`
environment variables as fallback.

Same resolution order as #5607

Closes #5430
2022-05-23 05:32:52 +12:00
6ff717c0ba Add meta command for the config subcommands (#5616)
When using `config` without the `config nu` or `config env` subcommands
introduced by #5607 display basic usage like `str`.
2022-05-23 05:31:57 +12:00
d534a89867 Make flatten works better and predictable (#5611)
* only want to flatten at most one column which contains a list

* make flatten works better

* more readable
2022-05-22 06:22:38 -05:00
5bc9246f0f Allow for test_iteration_errors to work when run as root (#5609)
* allow for test_iteration_errors to work when run as root

* Add comment to skip condition

Co-authored-by: Stefan Holderbach <sholderbach@users.noreply.github.com>
2022-05-22 11:47:03 +02:00
1e89cc3578 fix typo for version command (#5610) 2022-05-22 16:48:39 +08:00
06f5199570 Add config command (#5607)
* Add config command

* Format code

Co-authored-by: Frank Zhang <v-frankz@microsoft.com>
2022-05-22 15:13:58 +12:00
9e5e9819d6 adjust flatten default behavior (#5606) 2022-05-21 08:32:51 -05:00
1f8ccd8e5e Add search term to str substring command. (#5603) 2022-05-21 11:40:37 +03:00
e9d8b19d4d feat: add search terms to network (#5602)
Co-authored-by: Leyoh Li <leyohli@LeyohdeMacBook-Air.local>
2022-05-20 23:19:17 -04:00
7c63ce15d8 attempts to allow the test to work when run as root (#5601) 2022-05-20 21:48:36 -05:00
JT
a3a9571dac Add environment change hook (#5600)
* add environment change hook

* clippy
2022-05-21 09:49:42 +12:00
2cc5952c37 Fix cp bug (#5462)
* Cleanup - remove old commented code

* Force a / or \ to distinguish between folders and files for cp

* Force a / or \ to distinguish between folders and files for cp

* Remove unneeded code

* Add cp test for checking copy to non existing directory

* Fix warning in test
2022-05-21 09:49:29 +12:00
aa88449f29 Refer to the span of error make if not given (#5599)
* Refer to the span of `error make` if not given

Implements #5591

Currently the span of the "throwing" `error make`

Also allow to set `msg` and `label` without an additional span.

* Message plus "originates from here" label
2022-05-21 09:48:36 +12:00
06199d731b Use bleeding edge reedline, with fix for #5593 (#5598)
Fixes #5593 (OOM introduced with #5587 when no config was present and an attempt was
made to allocate all memory in advance)

Includes also other changes to reedline:

- Vi word definition fixed and `w` and `e` work as expected
2022-05-20 17:35:25 +02:00
0ba86d7eb8 Fix #5578, assume pipe file be zero-sized (#5594)
* Fix #5578, assume pipe file be zero-sized

* rust fmt
2022-05-20 09:27:21 -05:00
6efd1bcb3f Don't report error when cwd is not exists. (#5590)
* only set cwd for child process if cwd exists, and avoid showing error when pwd is not exists

* better comment text

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-05-20 07:03:03 -05:00
0d06b6259f Change miette theme based on ANSI config (#5588)
* Change miette theme based on ANSI config

Use the base ansi colors to simplify the use of the terminal emulator
theming.
Turn of most eye-candy (including unicode) when using
`$config.use_ansi_coloring: false`

Addresses #5582

* Fix error test affected by changed styling
2022-05-19 13:59:14 -05:00
8fdc272bcc Use effectively unlimited history size if not set (#5587)
Fixes #5586
2022-05-19 12:42:41 -05:00
0ea7a38c21 Move help menu to canonical F1 binding (#5510)
Currently the fully fledged help menu is bound to `Ctrl-Q`.
Help is widely associated with `F1`.

Before merging check that it is passed through on all platforms and
terminal emulators
2022-05-19 08:24:04 -05:00
1999e0dcf3 Fix flatten behavior (#5584)
* one step closer to flatten

* integration code is passing, but still need to do one more level flatten for table

* fix flatten

* using match instead of several if let

* make better comment

* fmt code

* better comment
2022-05-19 06:46:48 -05:00
ac30b3d108 Fix menu panic for empty examples. (#5581) 2022-05-19 10:04:56 +02:00
2b1e05aad0 add quantile column (#5583) 2022-05-18 20:47:26 -05:00
6c56829976 Allowing for flags with '=' in them to register as flags. (#5579)
* hacky fix for registering flags with '='

* fmt
2022-05-18 11:26:58 -05:00
2c58beec13 cp, mv, and rm commands need to support -i flag (#5523)
* restored interactive mode to rm command

* removed unnecessary whitespace in rm file

* removed unnecessary whitespace in rm file

* fixed python-vertualenv build issue

* moved interactive logic to utils file

* restored interactive mode to cp command

* interactive mode for mv wip

* finished mv implementation

* removed unnecessary whitespace

* changed unwrap to expect
2022-05-18 09:53:46 -05:00
9c779b071b feat: apply the --numbered option to acc in reduce command. (#5575)
* feat: apply the `-n` option to acc

* feat: update tests and examples
2022-05-18 09:49:34 -05:00
1e94793df5 Add str title-case (#5573)
Co-authored-by: kyle <kyle@archtop.local>
2022-05-18 08:57:20 -05:00
7d9a77f179 fix select tests (#5577) 2022-05-18 06:20:26 -05:00
bb079608dd fix move test (#5576)
* fix move test

* remove ignore
2022-05-18 06:18:21 -05:00
5fa42eeb8c Make format support nested column and use variable (#5570)
* fix format for nested structure

* make little revert

* add tests

* fix format

* better comment

* make better comment
2022-05-18 06:08:43 -05:00
3e09158afc Move capitalize, downcase, upcase to /cases; fix some example descriptions; clarify usage text (#5572)
Co-authored-by: kyle <kyle@archtop.local>
2022-05-18 00:55:43 -04:00
7a78171b34 move items to showcase (#5569) 2022-05-17 18:21:14 -05:00
633ebc7e43 Revert "Enable backtraces by default (#5562)" (#5568)
This reverts commit 8004e8e2a0.
2022-05-17 15:02:45 -07:00
f0cb2f38df refactor all write_alls to ensure flushing (#5567) 2022-05-17 13:28:18 -05:00
f26d3bf8d7 make print flush (#5566) 2022-05-17 09:27:12 -05:00
498672f5e5 feat(errors): more explicit module_or_overlay_not_found_error help message (#5564) 2022-05-17 06:22:31 -05:00
038391519b Upgrade trash crate for faster non-Windows builds (#5563) 2022-05-16 17:48:41 -07:00
8004e8e2a0 Enable backtraces by default (#5562) 2022-05-16 17:04:41 -07:00
JT
e192684612 Revert "Try to do less work during capture discovery (#5560)" (#5561)
This reverts commit 5d40fc2726.
2022-05-17 10:49:59 +12:00
JT
5d40fc2726 Try to do less work during capture discovery (#5560) 2022-05-17 09:05:26 +12:00
a22d70718f Add search terms to build-string command. (#5557) 2022-05-16 12:21:01 -07:00
24a49f1b0a Remove doctests action (#5556)
We're no longer using `cargo nextest` for our main test job. The separate action for doctests was only necessary because `cargo nextest` does not support doctests, it can be removed.

Hoping this will result in less data cached but we'll see.
2022-05-16 09:10:00 -07:00
04473a5593 Update pull request template for faster clippy+tests
Updating the Clippy and `cargo test` instructions to be more similar to what we do in CI. Will speed things up a bit for contributors.
2022-05-16 08:42:38 -07:00
d1e7884d19 table refactor for readability (#5555) 2022-05-16 10:35:57 -05:00
2b96c93b8d Sync resources version (#5554)
Fix line ending
2022-05-16 09:15:10 -05:00
fc41a0f96b use reverse iter on value search (#5553) 2022-05-16 06:29:40 -05:00
8bd68416e3 Lazy dataframes (#5546)
* lazyframe definition

* expressions and lazy frames

* new alias expression

* more expression commands

* updated to polars main

* more expressions and groupby

* more expressions, fetch and sort-by

* csv reader

* removed open csv

* unique function

* joining functions

* join lazy frames commands with eager commands

* corrected tests

* Update .gitignore

* Update .gitignore

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
2022-05-16 08:27:43 +01:00
2062e33c37 CI: bust caches (#5550)
* bust test cache to see if that fixes issue

* bust all caches
2022-05-15 22:24:51 -07:00
JT
c6383874e9 Try removing debuginfo for ci builds (#5549)
* Try removing debuginfo for ci builds

* oops, wrong inherits

* extra flag

* nextest doesn't support --profile in the same way

* try to allow for a ci-specific target

* Oops, run more tests
2022-05-16 16:02:11 +12:00
d90b25c633 Look up git commit hash ourselves, drop libgit2 dependency (#5548) 2022-05-16 13:57:25 +12:00
44bcfb3403 fix zip test (#5536) 2022-05-15 16:44:32 -05:00
c047fd4778 nu-cli/completions: add custom completion test (#5543) 2022-05-14 15:09:41 -05:00
16bd7b6d0d Fix Value::Record compare logic, and pass uniq tests. (#5541)
* fix record compare logic

* add more comment
2022-05-14 06:04:09 -05:00
3cef94ba39 nu-glob: add fs::symlink_metadata to detect broken symlinks (#5537)
* nu-glob: add fs::symlink_metadata to detect broken symlinks

* fix join result
2022-05-13 17:56:26 -07:00
f818193b53 Change history menu keybinding from ctrl+x to ctrl+r (#5507)
* Change history menu keybinding to ctrl+r from ctrl+x

* Remove menupage actions from default config

* remove trailing whitespace

* re-add next+previous page keybindings

* Remove hardcoded menu keybindings

* Hardcode new keybindings
2022-05-13 09:26:14 -05:00
1aec4a343a Made a change to completion resolution order (#5440)
* Made a change to completion resolution order

* Potential fix for completion (remove file paths from command completer)

* Updating formatting

* Removed commented out code for readability

* Fixed compile error on merge
2022-05-13 08:15:24 -05:00
852de79212 Implement histogram command (#5518)
* finish histogram

* adjust comment

* add test for histogram

* add Date to test

* move hashable value back inside chart package
2022-05-13 06:48:47 -05:00
06f40405fe add rename (#5534) 2022-05-13 06:47:11 -05:00
65bac77e8a More CI work (#5527)
* Add cache+docs to plugin CI job

* CI perf: don't statically link OpenSSL

* Run Clippy in plugin job

* comment

* bust cache

* trigger build

* remove nextest, split plugins better

* trigger CI

* try disabling embed-resource

* try disabling libgit2 in shadow-rs

* use lld linker on Windows

* Skip embedding Windows resource (slow) during tests

* disable shadow-rs git integration during tests

* go back to simpler shadow-rs and embed-resources setup

* some renaming

* forgot nextest

* trigger ci

* Remove Clippy and unnecessary build

* trigger CI

* disable lld

* reenable lld

* cleanup

* revert embed_resource change
2022-05-13 06:40:46 -05:00
32d1939a95 nu-command/filesystem: fix rm .sock file (#5524) 2022-05-12 19:25:21 -05:00
53e35670ea add the ability to change table mode when running script (#5520) 2022-05-12 07:27:44 -05:00
a92567489f nu-cli/completions: verify case for matching dir, .nu, file and command (#5506)
* nu-cli/completions: verify case for matching dir, .nu, file and command

* avoid copy

* fix clippy
2022-05-11 16:16:52 -05:00
2145feff5d feat: add tutor list support, remove tutor engine-q, fix: #4950 (#5511)
* feat: add `tutor list` support, remove tutor `engine-q`, fix: #4950

* cs

* fmt
2022-05-11 16:16:01 -05:00
0b95465ea1 add --table_mode -m parameter (#5513)
* add `--table_mode` `-m` parameter

* underscores to dashes
2022-05-11 16:15:31 -05:00
ec804f4568 nu-command ls - bump umask crate to 2.0.0 (#5514) 2022-05-11 16:13:45 -05:00
4717ac70fd Add verbose (#5512)
Co-authored-by: Frank Zhang <v-frankz@microsoft.com>
2022-05-11 11:46:13 -05:00
9969fbfbb1 Add feedback to cp (#5482)
Co-authored-by: Frank Zhang <v-frankz@microsoft.com>
2022-05-11 20:06:30 +08:00
5f39267a80 Make $nothing | into string == "" (#5490)
* Make $nothing | into string == ""

* Fix up existing into string tests

* Add $nothing | into string test

* Formatting

* Windows line endings test fix
2022-05-11 12:26:43 +03:00
94a9380e8b adjust where prompt markers go (#5491)
* adjust where prompt markers go

* marks are working, yipee!
2022-05-10 16:33:18 -05:00
1d64863585 nu-cli/completions: add variable completions test + refactor tests (#5504)
* refactor tests

* removed old test file
2022-05-10 15:17:07 -05:00
8218f72eea nu-cli/completions: added tests for dotnu completions (#5460) 2022-05-10 13:18:18 -05:00
c0b99b7131 Enable converting dates to ints (#5489) 2022-05-10 13:15:28 -05:00
75c033e4d1 refactor for legibility (#5503)
* refactor for legibility

* clippy
2022-05-10 12:49:34 -05:00
d88d057bf6 keep metadata while format filesize (#5502) 2022-05-10 11:24:06 -05:00
b00098ccc6 opt: improve ls by call get_file_type only one time (#5500)
* opt: improve ls by call get_file_type only one time

* fmt

* cs
2022-05-10 08:01:06 -05:00
7e5e9c28dd Fix #3899, make mv and rm to be quiet by default (#5501) 2022-05-10 08:00:27 -05:00
8ffffe9bcc Improve #4975 of filtering ls output by size issue (#5494)
* Improve #4975 of filtering `ls` output by size issue

* cargo fmt
2022-05-10 06:39:37 -05:00
8030f7e9f0 add format filesize (#5498)
* add format filesize

* add comment

* add comment

* remove comment
2022-05-10 06:35:14 -05:00
e4959d2f9f Update comment in default_config.nu [skip ci] (#5496) 2022-05-10 06:21:01 -05:00
f311da9623 Adds fix for when multiple flags are in one line. (#5493) 2022-05-10 06:13:19 -05:00
14d80d54fe Parse timestamps as UTC by default (#5488)
* Parse timestamps as UTC by default

* Fix up flags and examples
2022-05-09 13:57:28 -05:00
23b467061b Display range values better (#5487) 2022-05-09 12:18:37 -05:00
8d8f25b210 Fixing the flag issue (#5447)
* Fixing the flag issue

* whoops, forgot the original point of the function

* Update deparse.rs

* Update deparse.rs

* Update deparse.rs

* maybe this might work

* fmt

* quotation marks works now due to a rigorous check for args.

* fmt and clippy

* kept the original escape_quote_string(), escaped " and \

* removed script.nu

* Added appropriate comments.
2022-05-09 07:01:58 -05:00
7ee22603ac Fix #5469, making $nothing or null convert to filesize of 0B (#5485) 2022-05-09 06:19:28 -05:00
4052a99ff5 Handle int input in into datetime (#5484) 2022-05-09 06:16:01 -05:00
ccfa35289b Fix to csv and to tsv for simple list, close: #4780 (#5483)
* Fix `to csv` and `to tsv` for simple list, close: #4780

* ci skip
2022-05-09 06:14:42 -05:00
JT
54fc164e1c Allow hooks to be lists of blocks (#5480) 2022-05-09 13:56:48 +12:00
JT
3a35bf7d4e Add hooks to cli/repl (#5479)
* Add hooks to cli/repl

* Clippy

* Clippy
2022-05-09 07:28:39 +12:00
a61d09222f document out positional argument type (#5461) 2022-05-08 08:11:28 -05:00
07ac3c3aab Add Nushell REPL simulator; Fix bug in overlay add (#5478)
* Add Nushell REPL simulator; Fix bug in overlay add

The `nu_repl` function takes an array of strings and processes them as
if they were REPL lines entered one by one. This helps to discover bugs
due to the state changes between the parse and eval stages.

* Fix REPL tests on Windows
2022-05-08 16:09:39 +03:00
061e9294b3 join and from derived tables (#5477) 2022-05-08 11:12:03 +01:00
JT
374757f286 Bump to the 0.62.1 dev version (#5473) 2022-05-08 08:38:12 +12:00
ca75cd7c0a nu-cli/completions: add tests for flag completions (#5468) 2022-05-07 15:19:48 -05:00
d08c072f19 feat: add disable field type inferencing for from csv and from tsv, fix: #3485 and #4217 (#5467) 2022-05-07 15:04:31 -05:00
9b99b2f6ac Overlays (#5375)
* WIP: Start laying overlays

* Rename Overlay->Module; Start adding overlay

* Revamp adding overlay

* Add overlay add tests; Disable debug print

* Fix overlay add; Add overlay remove

* Add overlay remove tests

* Add missing overlay remove file

* Add overlay list command

* (WIP?) Enable overlays for env vars

* Move OverlayFrames to ScopeFrames

* (WIP) Move everything to overlays only

ScopeFrame contains nothing but overlays now

* Fix predecls

* Fix wrong overlay id translation and aliases

* Fix broken env lookup logic

* Remove TODOs

* Add overlay add + remove for environment

* Add a few overlay tests; Fix overlay add name

* Some cleanup; Fix overlay add/remove names

* Clippy

* Fmt

* Remove walls of comments

* List overlays from stack; Add debugging flag

Currently, the engine state ordering is somehow broken.

* Fix (?) overlay list test

* Fix tests on Windows

* Fix activated overlay ordering

* Check for active overlays equality in overlay list

This removes the -p flag: Either both parser and engine will have the
same overlays, or the command will fail.

* Add merging on overlay remove

* Change help message and comment

* Add some remove-merge/discard tests

* (WIP) Track removed overlays properly

* Clippy; Fmt

* Fix getting last overlay; Fix predecls in overlays

* Remove merging; Fix re-add overwriting stuff

Also some error message tweaks.

* Fix overlay error in the engine

* Update variable_completions.rs

* Adds flags and optional arguments to view-source (#5446)

* added flags and optional arguments to view-source

* removed redundant code

* removed redundant code

* fmt

* fix bug in shell_integration (#5450)

* fix bug in shell_integration

* add some comments

* enable cd to work with directory abbreviations (#5452)

* enable cd to work with abbreviations

* add abbreviation example

* fix tests

* make it configurable

* make cd recornize symblic link (#5454)

* implement seq char command to generate single character sequence (#5453)

* add tmp code

* add seq char command

* Add split number flag in `split row` (#5434)

Signed-off-by: Yuheng Su <gipsyh.icu@gmail.com>

* Add two more overlay tests

* Add ModuleId to OverlayFrame

* Fix env conversion accidentally activating overlay

It activated overlay from permanent state prematurely which would
cause `overlay add` to misbehave.

* Remove unused parameter; Add overlay list test

* Remove added traces

* Add overlay commands examples

* Modify TODO

* Fix $nu.scope iteration

* Disallow removing default overlay

* Refactor some parser errors

* Remove last overlay if no argument

* Diversify overlay examples

* Make it possible to update overlay's module

In case the origin module updates, the overlay add loads the new module,
makes it overlay's origin and applies the changes. Before, it was
impossible to update the overlay if the module changed.

Co-authored-by: JT <547158+jntrnr@users.noreply.github.com>
Co-authored-by: pwygab <88221256+merelymyself@users.noreply.github.com>
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: WindSoilder <WindSoilder@outlook.com>
Co-authored-by: Yuheng Su <gipsyh.icu@gmail.com>
2022-05-08 07:39:22 +12:00
1cb449b2d1 Database commands (#5466)
* change query to statement

* internal functions and over definitions

* cargo fmt
2022-05-07 13:33:33 +01:00
6cc66c8afd complete some commands tests (#5464)
* complete hash test

* unignore source relative tests
2022-05-07 06:23:49 -05:00
08e495ea67 Enable string interpolation for environment shorthand (#5463) 2022-05-07 06:21:29 -05:00
b0647f780d nu-cli/completions: send original line to custom completer (#5459) 2022-05-06 16:58:42 -05:00
2dfd975940 add -n flag to print to print without a newline (#5458)
* add -n flag to print to print without a newline

* clippy
2022-05-06 15:33:00 -05:00
fbdb125141 Add split number flag in split row (#5434)
Signed-off-by: Yuheng Su <gipsyh.icu@gmail.com>
2022-05-06 10:53:02 -05:00
c2ea993f7e implement seq char command to generate single character sequence (#5453)
* add tmp code

* add seq char command
2022-05-06 10:40:02 -05:00
e14e60dd2c make cd recornize symblic link (#5454) 2022-05-06 10:39:48 -05:00
768ff47d28 enable cd to work with directory abbreviations (#5452)
* enable cd to work with abbreviations

* add abbreviation example

* fix tests

* make it configurable
2022-05-06 07:58:32 -05:00
78a1879e36 fix bug in shell_integration (#5450)
* fix bug in shell_integration

* add some comments
2022-05-05 10:10:03 -05:00
0b9c0fea9d Adds flags and optional arguments to view-source (#5446)
* added flags and optional arguments to view-source

* removed redundant code

* removed redundant code

* fmt
2022-05-05 06:37:56 -05:00
Tom
02a3430ef0 Use correct ParseError (#5431) 2022-05-05 07:41:32 +12:00
6623ed9061 sometimes you want a text output (#5441) 2022-05-04 14:12:23 -05:00
48cf103439 Allowed for view-source to include entire custom command definition (#5435)
* allowed for view-source to include entire custom command definition

* fmt

* clippy
2022-05-04 06:35:09 -05:00
1bcb87c48d Update rust version (#5432) 2022-05-04 13:56:31 +12:00
608 changed files with 34930 additions and 10228 deletions

View File

@ -1,6 +1,7 @@
# increase the default windows stack size
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "link-args=-stack:10000000"]
# increase the default windows stack size
# statically link the CRT so users don't have to install it
rustflags = ["-C", "link-args=-stack:10000000", "-C", "target-feature=+crt-static"]
# keeping this but commentting out in case we need them in the future

View File

@ -4,8 +4,14 @@
# Tests
Make sure you've done the following:
- [ ] Add tests that cover your changes, either in the command examples, the crate/tests folder, or in the /tests folder.
- [ ] Try to think about corner cases and various ways how your changes could break. Cover them with tests.
- [ ] If adding tests is not possible, please document in the PR body a minimal example with steps on how to reproduce so one can verify your change works.
Make sure you've run and fixed any issues with these commands:
- [ ] `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes)
- [ ] `cargo clippy --all --all-features -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- [ ] `cargo build; cargo test --all --all-features` to check that all the tests pass
- [ ] `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style
- [ ] `cargo test --workspace --features=extra` to check that all the tests pass

View File

@ -7,26 +7,17 @@ on:
name: continuous-integration
jobs:
build-clippy:
nu-fmt-clippy:
strategy:
fail-fast: false
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-latest]
style: [all, default]
rust:
- stable
include:
- style: all
flags: "--all-features"
- style: default
flags: ""
exclude:
- platform: windows-latest
style: default
- platform: macos-latest
style: default
runs-on: ${{ matrix.platform }}
env:
NUSHELL_CARGO_TARGET: ci
steps:
- uses: actions/checkout@v2
@ -41,7 +32,7 @@ jobs:
- uses: Swatinem/rust-cache@v1
with:
key: ${{ matrix.style }}v1 # increment this to bust the cache if needed
key: "v2" # increment this to bust the cache if needed
- name: Rustfmt
uses: actions-rs/cargo@v1
@ -49,29 +40,26 @@ jobs:
command: fmt
args: --all -- --check
- name: Build Nushell
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace ${{ matrix.flags }}
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
args: --features=extra --workspace --exclude nu_plugin_* -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
nu-tests:
env:
NUSHELL_CARGO_TARGET: ci
test:
strategy:
fail-fast: false
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-latest]
style: [all, default]
style: [extra, default]
rust:
- stable
include:
- style: all
flags: "--all-features"
- style: extra
flags: "--features=extra"
- style: default
flags: ""
exclude:
@ -91,29 +79,25 @@ jobs:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v1
with:
key: ${{ matrix.style }}v1 # increment this to bust the cache if needed
- uses: taiki-e/install-action@nextest
# Temporarily disabled; the cache was getting huge (2.6GB compressed) on Windows and causing issues.
# TODO: investigate why the cache was so big
# - uses: Swatinem/rust-cache@v1
# with:
# key: ${{ matrix.style }}v3 # increment this to bust the cache if needed
- name: Tests
uses: actions-rs/cargo@v1
with:
command: nextest
args: run --all ${{ matrix.flags }}
- name: Doctests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --doc ${{ matrix.flags }}
args: --workspace --profile ci --exclude nu_plugin_* ${{ matrix.flags }}
python-virtualenv:
env:
NUSHELL_CARGO_TARGET: ci
strategy:
fail-fast: false
fail-fast: true
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
rust:
@ -135,13 +119,13 @@ jobs:
- uses: Swatinem/rust-cache@v1
with:
key: "1" # increment this to bust the cache if needed
key: "2" # increment this to bust the cache if needed
- name: Install Nushell
uses: actions-rs/cargo@v1
with:
command: install
args: --path=. --no-default-features --debug
args: --path=. --profile ci --no-default-features
- name: Setup Python
uses: actions/setup-python@v2
@ -151,19 +135,21 @@ 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
run: cd virtualenv && tox -e ${{ matrix.py }} -- -k nushell
shell: bash
# Build+test plugins on their own, without the rest of Nu. This helps with CI parallelization and
# also helps test that the plugins build without any feature unification shenanigans
plugins:
env:
NUSHELL_CARGO_TARGET: ci
strategy:
fail-fast: false
fail-fast: true
matrix:
platform: [windows-latest, macos-latest, ubuntu-latest]
rust:
@ -181,29 +167,14 @@ jobs:
toolchain: ${{ matrix.rust }}
override: true
# This job does not use rust-cache because 1) we have limited cache space, 2) even
# without caching, it's not the slowest job. Revisit if those facts change.
- name: Build nu_plugin_example
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: build
args: --package nu_plugin_example
command: clippy
args: --package nu_plugin_* ${{ matrix.flags }} -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
- name: Build nu_plugin_gstat
- name: Tests
uses: actions-rs/cargo@v1
with:
command: build
args: --package nu_plugin_gstat
- name: Build nu_plugin_inc
uses: actions-rs/cargo@v1
with:
command: build
args: --package nu_plugin_inc
- name: Build nu_plugin_query
uses: actions-rs/cargo@v1
with:
command: build
args: --package nu_plugin_query
command: test
args: --profile ci --package nu_plugin_*

155
.github/workflows/release-pkg.nu vendored Executable file
View File

@ -0,0 +1,155 @@
#!/usr/bin/env nu
# Created: 2022/05/26 19:05:20
# Description:
# A script to do the github release task, need nushell to be installed.
# REF:
# 1. https://github.com/volks73/cargo-wix
# The main binary file to be released
let bin = 'nu'
let os = $env.OS
let target = $env.TARGET
# Repo source dir like `/home/runner/work/nushell/nushell`
let src = $env.GITHUB_WORKSPACE
let flags = $env.TARGET_RUSTFLAGS
let dist = $'($env.GITHUB_WORKSPACE)/output'
let version = (open Cargo.toml | get package.version)
# $env
$'(char nl)Packaging ($bin) v($version) for ($target) in ($src)...'; hr-line -b
if not ('Cargo.lock' | path exists) { cargo generate-lockfile }
$'Start building ($bin)...'; hr-line
# ----------------------------------------------------------------------------
# Build for Ubuntu and macOS
# ----------------------------------------------------------------------------
if $os in ['ubuntu-latest', 'macos-latest'] {
if $os == 'ubuntu-latest' {
sudo apt-get install libxcb-composite0-dev -y
}
if $target == 'aarch64-unknown-linux-gnu' {
sudo apt-get install gcc-aarch64-linux-gnu -y
let-env CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER = 'aarch64-linux-gnu-gcc'
cargo-build-nu $flags
} else if $target == 'armv7-unknown-linux-gnueabihf' {
sudo apt-get install pkg-config gcc-arm-linux-gnueabihf -y
let-env CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER = 'arm-linux-gnueabihf-gcc'
cargo-build-nu $flags
} else {
# musl-tools to fix 'Failed to find tool. Is `musl-gcc` installed?'
# Actually just for x86_64-unknown-linux-musl target
sudo apt install musl-tools -y
cargo-build-nu $flags
}
}
# ----------------------------------------------------------------------------
# Build for Windows without static-link-openssl feature
# ----------------------------------------------------------------------------
if $os in ['windows-latest'] {
if ($flags | str trim | empty?) {
cargo build --release --all --target $target --features=extra
} else {
cargo build --release --all --target $target --features=extra $flags
}
}
# ----------------------------------------------------------------------------
# Prepare for the release archive
# ----------------------------------------------------------------------------
let suffix = if $os == 'windows-latest' { '.exe' }
# nu, nu_plugin_* were all included
let executable = $'target/($target)/release/($bin)*($suffix)'
$'Current executable file: ($executable)'
cd $src; mkdir $dist;
rm -rf $'target/($target)/release/*.d' $'target/($target)/release/nu_pretty_hex*'
$'(char nl)All executable files:'; hr-line
ls -f $executable
$'(char nl)Copying release files...'; hr-line
cp -v README.release.txt $'($dist)/README.txt'
[LICENSE $executable] | each {|it| cp -rv $it $dist } | flatten
$'(char nl)Check binary release version detail:'; hr-line
let ver = if $os == 'windows-latest' {
(do -i { ./output/nu.exe -c 'version' }) | str collect
} else {
(do -i { ./output/nu -c 'version' }) | str collect
}
if ($ver | str trim | empty?) {
$'(ansi r)Incompatible nu binary...(ansi reset)'
} else { $ver }
# ----------------------------------------------------------------------------
# Create a release archive and send it to output for the following steps
# ----------------------------------------------------------------------------
cd $dist; $'(char nl)Creating release archive...'; hr-line
if $os in ['ubuntu-latest', 'macos-latest'] {
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
let archive = $'($dist)/($bin)-($version)-($target).tar.gz'
tar czf $archive *
print $'archive: ---> ($archive)'; ls $archive
echo $'::set-output name=archive::($archive)'
} else if $os == 'windows-latest' {
let releaseStem = $'($bin)-($version)-($target)'
$'(char nl)Download less related stuffs...'; hr-line
aria2c https://github.com/jftuga/less-Windows/releases/download/less-v590/less.exe -o less.exe
aria2c https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE -o LICENSE-for-less.txt
# Create Windows msi release package
if (get-env _EXTRA_) == 'msi' {
let wixRelease = $'($src)/target/wix/($releaseStem).msi'
$'(char nl)Start creating Windows msi package...'
cd $src; hr-line
# Wix need the binaries be stored in target/release/
cp -r $'($dist)/*' target/release/
cargo install cargo-wix --version 0.3.2
cargo wix --no-build --nocapture --package nu --output $wixRelease
echo $'::set-output name=archive::($wixRelease)'
} else {
$'(char nl)(ansi g)Archive contents:(ansi reset)'; hr-line; ls
let archive = $'($dist)/($releaseStem).zip'
7z a $archive *
print $'archive: ---> ($archive)';
let pkg = (ls -f $archive | get name)
if not ($pkg | empty?) {
echo $'::set-output name=archive::($pkg | get 0)'
}
}
}
def 'cargo-build-nu' [ options: string ] {
if ($options | str trim | empty?) {
cargo build --release --all --target $target --features=extra,static-link-openssl
} else {
cargo build --release --all --target $target --features=extra,static-link-openssl $options
}
}
# Print a horizontal line marker
def 'hr-line' [
--blank-line(-b): bool
] {
print $'(ansi g)---------------------------------------------------------------------------->(ansi reset)'
if $blank-line { char nl }
}
# Get the specified env key's value or ''
def 'get-env' [
key: string # The key to get it's env value
default: string = '' # The default value for an empty env
] {
$env | get -i $key | default $default
}

View File

@ -1,3 +1,7 @@
#
# REF:
# 1. https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrixinclude
#
name: Create Release Draft
on:
@ -5,434 +9,89 @@ on:
push:
tags: ["[0-9]+.[0-9]+.[0-9]+*"]
defaults:
run:
shell: bash
jobs:
linux:
name: Build Linux
runs-on: ubuntu-latest
all:
name: All
strategy:
matrix:
target:
- aarch64-apple-darwin
- x86_64-apple-darwin
- x86_64-pc-windows-msvc
- x86_64-unknown-linux-gnu
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
- armv7-unknown-linux-gnueabihf
extra: ['bin']
include:
- target: aarch64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-apple-darwin
os: macos-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: 'bin'
os: windows-latest
target_rustflags: ''
- target: x86_64-pc-windows-msvc
extra: msi
os: windows-latest
target_rustflags: ''
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
target_rustflags: ''
- target: x86_64-unknown-linux-musl
os: ubuntu-latest
target_rustflags: ''
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
target_rustflags: ''
- target: armv7-unknown-linux-gnueabihf
os: ubuntu-latest
target_rustflags: ''
runs-on: ${{matrix.os}}
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Install libxcb
run: sudo apt-get install libxcb-composite0-dev
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=extra,static-link-openssl
# - name: Strip binaries (nu)
# run: strip target/release/nu
# - name: Strip binaries (nu_plugin_inc)
# run: strip target/release/nu_plugin_inc
# - name: Strip binaries (nu_plugin_match)
# run: strip target/release/nu_plugin_match
# - name: Strip binaries (nu_plugin_textview)
# run: strip target/release/nu_plugin_textview
# - name: Strip binaries (nu_plugin_binaryview)
# run: strip target/release/nu_plugin_binaryview
# - name: Strip binaries (nu_plugin_chart_bar)
# run: strip target/release/nu_plugin_chart_bar
# - name: Strip binaries (nu_plugin_chart_line)
# run: strip target/release/nu_plugin_chart_line
# - name: Strip binaries (nu_plugin_from_bson)
# run: strip target/release/nu_plugin_from_bson
# - name: Strip binaries (nu_plugin_from_sqlite)
# run: strip target/release/nu_plugin_from_sqlite
# - name: Strip binaries (nu_plugin_from_mp4)
# run: strip target/release/nu_plugin_from_mp4
# - name: Strip binaries (nu_plugin_query_json)
# run: strip target/release/nu_plugin_query_json
# - name: Strip binaries (nu_plugin_s3)
# run: strip target/release/nu_plugin_s3
# - name: Strip binaries (nu_plugin_selector)
# run: strip target/release/nu_plugin_selector
# - name: Strip binaries (nu_plugin_start)
# run: strip target/release/nu_plugin_start
# - name: Strip binaries (nu_plugin_to_bson)
# run: strip target/release/nu_plugin_to_bson
# - name: Strip binaries (nu_plugin_to_sqlite)
# run: strip target/release/nu_plugin_to_sqlite
# - name: Strip binaries (nu_plugin_tree)
# run: strip target/release/nu_plugin_tree
# - name: Strip binaries (nu_plugin_xpath)
# run: strip target/release/nu_plugin_xpath
- name: Create output directory
run: mkdir output
- name: Copy files to output
run: |
cp target/release/nu target/release/nu_plugin_* output/
cp README.release.txt output/README.txt
cp LICENSE output/LICENSE
rm output/*.d
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: linux
path: output/*
macos:
name: Build macOS
runs-on: macos-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=extra,static-link-openssl
# - name: Strip binaries (nu)
# run: strip target/release/nu
# - name: Strip binaries (nu_plugin_inc)
# run: strip target/release/nu_plugin_inc
# - name: Strip binaries (nu_plugin_match)
# run: strip target/release/nu_plugin_match
# - name: Strip binaries (nu_plugin_textview)
# run: strip target/release/nu_plugin_textview
# - name: Strip binaries (nu_plugin_binaryview)
# run: strip target/release/nu_plugin_binaryview
# - name: Strip binaries (nu_plugin_chart_bar)
# run: strip target/release/nu_plugin_chart_bar
# - name: Strip binaries (nu_plugin_chart_line)
# run: strip target/release/nu_plugin_chart_line
# - name: Strip binaries (nu_plugin_from_bson)
# run: strip target/release/nu_plugin_from_bson
# - name: Strip binaries (nu_plugin_from_sqlite)
# run: strip target/release/nu_plugin_from_sqlite
# - name: Strip binaries (nu_plugin_from_mp4)
# run: strip target/release/nu_plugin_from_mp4
# - name: Strip binaries (nu_plugin_query_json)
# run: strip target/release/nu_plugin_query_json
# - name: Strip binaries (nu_plugin_s3)
# run: strip target/release/nu_plugin_s3
# - name: Strip binaries (nu_plugin_selector)
# run: strip target/release/nu_plugin_selector
# - name: Strip binaries (nu_plugin_start)
# run: strip target/release/nu_plugin_start
# - name: Strip binaries (nu_plugin_to_bson)
# run: strip target/release/nu_plugin_to_bson
# - name: Strip binaries (nu_plugin_to_sqlite)
# run: strip target/release/nu_plugin_to_sqlite
# - name: Strip binaries (nu_plugin_tree)
# run: strip target/release/nu_plugin_tree
# - name: Strip binaries (nu_plugin_xpath)
# run: strip target/release/nu_plugin_xpath
- name: Create output directory
run: mkdir output
- name: Copy files to output
run: |
cp target/release/nu target/release/nu_plugin_* output/
cp README.release.txt output/README.txt
cp LICENSE output/LICENSE
rm output/*.d
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: macos
path: output/*
windows:
name: Build Windows
runs-on: windows-latest
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Set up cargo
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Add cargo-wix subcommand
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-wix --version 0.3.1
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all --features=extra,static-link-openssl
# - name: Strip binaries (nu.exe)
# run: strip target/release/nu.exe
# - name: Strip binaries (nu_plugin_inc.exe)
# run: strip target/release/nu_plugin_inc.exe
# - name: Strip binaries (nu_plugin_match.exe)
# run: strip target/release/nu_plugin_match.exe
# - name: Strip binaries (nu_plugin_textview.exe)
# run: strip target/release/nu_plugin_textview.exe
# - name: Strip binaries (nu_plugin_binaryview.exe)
# run: strip target/release/nu_plugin_binaryview.exe
# - name: Strip binaries (nu_plugin_chart_bar.exe)
# run: strip target/release/nu_plugin_chart_bar.exe
# - name: Strip binaries (nu_plugin_chart_line.exe)
# run: strip target/release/nu_plugin_chart_line.exe
# - name: Strip binaries (nu_plugin_from_bson.exe)
# run: strip target/release/nu_plugin_from_bson.exe
# - name: Strip binaries (nu_plugin_from_sqlite.exe)
# run: strip target/release/nu_plugin_from_sqlite.exe
# - name: Strip binaries (nu_plugin_from_mp4.exe)
# run: strip target/release/nu_plugin_from_mp4.exe
# - name: Strip binaries (nu_plugin_query_json.exe)
# run: strip target/release/nu_plugin_query_json.exe
# - name: Strip binaries (nu_plugin_s3.exe)
# run: strip target/release/nu_plugin_s3.exe
# - name: Strip binaries (nu_plugin_selector.exe)
# run: strip target/release/nu_plugin_selector.exe
# - name: Strip binaries (nu_plugin_start.exe)
# run: strip target/release/nu_plugin_start.exe
# - name: Strip binaries (nu_plugin_to_bson.exe)
# run: strip target/release/nu_plugin_to_bson.exe
# - name: Strip binaries (nu_plugin_to_sqlite.exe)
# run: strip target/release/nu_plugin_to_sqlite.exe
# - name: Strip binaries (nu_plugin_tree.exe)
# run: strip target/release/nu_plugin_tree.exe
# - name: Strip binaries (nu_plugin_xpath.exe)
# run: strip target/release/nu_plugin_xpath.exe
- name: Create output directory
run: mkdir output
- name: Download Less Binary
run: Invoke-WebRequest -Uri "https://github.com/jftuga/less-Windows/releases/download/less-v562.0/less.exe" -OutFile "target\release\less.exe"
- name: Download Less License
run: Invoke-WebRequest -Uri "https://raw.githubusercontent.com/jftuga/less-Windows/master/LICENSE" -OutFile "target\release\LICENSE-for-less.txt"
- name: Copy files to output
run: |
cp target\release\nu.exe output\
cp LICENSE output\
cp target\release\LICENSE-for-less.txt output\
cp target\release\nu_plugin_*.exe output\
cp README.release.txt output\README.txt
cp target\release\less.exe output\
# Note: If the version of `less.exe` needs to be changed, update this URL
# Similarly, if `less.exe` is checked into the repo, copy from the local path here
# moved this stuff down to create wix after we download less
- name: Create msi with wix
uses: actions-rs/cargo@v1
with:
command: wix
args: --no-build --nocapture --output target\wix\nushell-windows.msi
- name: Upload installer
uses: actions/upload-artifact@v2
with:
name: windows-installer
path: target\wix\nushell-windows.msi
- name: Upload zip
uses: actions/upload-artifact@v2
with:
name: windows-zip
path: output\*
release:
name: Publish Release
runs-on: ubuntu-latest
needs:
- linux
- macos
- windows
steps:
- name: Check out code
uses: actions/checkout@v2
- name: Determine Release Info
id: info
env:
GITHUB_REF: ${{ github.ref }}
run: |
VERSION=${GITHUB_REF##*/}
MAJOR=${VERSION%%.*}
MINOR=${VERSION%.*}
MINOR=${MINOR#*.}
PATCH=${VERSION##*.}
echo "::set-output name=version::${VERSION}"
echo "::set-output name=linuxdir::nu_${MAJOR}_${MINOR}_${PATCH}_linux"
echo "::set-output name=macosdir::nu_${MAJOR}_${MINOR}_${PATCH}_macOS"
echo "::set-output name=windowsdir::nu_${MAJOR}_${MINOR}_${PATCH}_windows"
echo "::set-output name=innerdir::nushell-${VERSION}"
- name: Create Release Draft
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ steps.info.outputs.version }} Release
draft: true
- name: Create Linux Directory
run: mkdir -p ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
- name: Download Linux Artifacts
uses: actions/download-artifact@v2
with:
name: linux
path: ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}
- name: Restore Linux File Modes
run: |
chmod 755 ${{ steps.info.outputs.linuxdir }}/${{ steps.info.outputs.innerdir }}/nu*
- name: Create Linux tarball
run: tar -zcvf ${{ steps.info.outputs.linuxdir }}.tar.gz ${{ steps.info.outputs.linuxdir }}
- name: Upload Linux Artifact
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.info.outputs.linuxdir }}.tar.gz
asset_name: ${{ steps.info.outputs.linuxdir }}.tar.gz
asset_content_type: application/gzip
- name: Create macOS Directory
run: mkdir -p ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
- name: Download macOS Artifacts
uses: actions/download-artifact@v2
with:
name: macos
path: ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}
- name: Restore macOS File Modes
run: chmod 755 ${{ steps.info.outputs.macosdir }}/${{ steps.info.outputs.innerdir }}/nu*
- name: Create macOS Archive
run: zip -r ${{ steps.info.outputs.macosdir }}.zip ${{ steps.info.outputs.macosdir }}
- name: Upload macOS Artifact
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.info.outputs.macosdir }}.zip
asset_name: ${{ steps.info.outputs.macosdir }}.zip
asset_content_type: application/zip
- name: Create Windows Directory
run: mkdir -p ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Download Windows zip
uses: actions/download-artifact@v2
with:
name: windows-zip
path: ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Show Windows Artifacts
run: ls -la ${{ steps.info.outputs.windowsdir }}/${{ steps.info.outputs.innerdir }}
- name: Create macOS Archive
run: zip -r ${{ steps.info.outputs.windowsdir }}.zip ${{ steps.info.outputs.windowsdir }}
- name: Upload Windows zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.info.outputs.windowsdir }}.zip
asset_name: ${{ steps.info.outputs.windowsdir }}.zip
asset_content_type: application/zip
- name: Download Windows installer
uses: actions/download-artifact@v2
with:
name: windows-installer
path: ./
- name: Upload Windows installer
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./nushell-windows.msi
asset_name: ${{ steps.info.outputs.windowsdir }}.msi
asset_content_type: application/x-msi
- uses: actions/checkout@v3.0.2
- name: Install Rust Toolchain Components
uses: actions-rs/toolchain@v1.0.6
with:
override: true
profile: minimal
toolchain: stable
target: ${{ matrix.target }}
- name: Setup Nushell
uses: hustcer/setup-nu@v1
with:
version: 0.63.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Release Nu Binary
id: nu
run: nu .github/workflows/release-pkg.nu
env:
OS: ${{ matrix.os }}
REF: ${{ github.ref }}
TARGET: ${{ matrix.target }}
_EXTRA_: ${{ matrix.extra }}
TARGET_RUSTFLAGS: ${{ matrix.target_rustflags }}
# REF: https://github.com/marketplace/actions/gh-release
- name: Publish Archive
uses: softprops/action-gh-release@v1
if: ${{ startsWith(github.ref, 'refs/tags/') }}
with:
draft: true
files: ${{ steps.nu.outputs.archive }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,4 +1,4 @@
name: Submit Nushell package to Windows Package Manager Community Repository
name: Submit Nushell package to Windows Package Manager Community Repository
on:
workflow_dispatch:
@ -15,5 +15,5 @@ jobs:
run: |
iwr https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe
$github = Get-Content '${{ github.event_path }}' | ConvertFrom-Json
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows.msi' | Select -ExpandProperty browser_download_url -First 1
$installerUrl = $github.release.assets | Where-Object -Property name -match 'windows-msvc.msi' | Select -ExpandProperty browser_download_url -First 1
.\wingetcreate.exe update Nushell.Nushell -s -v $github.release.tag_name -u $installerUrl -t ${{ secrets.NUSHELL_PAT }}

5
.gitignore vendored
View File

@ -23,4 +23,9 @@ debian/nu/
.vscode/*
# Helix configuration folder
.helix/*
.helix
# Coverage tools
lcov.info
tarpaulin-report.html

View File

@ -1,21 +1,14 @@
# Contributing
Welcome to nushell!
*Note: for a more complete guide see [The nu contributor book](https://www.nushell.sh/contributor-book/)*
For speedy contributions open it in Gitpod, nu will be pre-installed with the latest build in a VSCode like editor all from your browser.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/nushell/nushell)
Welcome to Nushell!
To get live support from the community see our [Discord](https://discordapp.com/invite/NtAbbGn), [Twitter](https://twitter.com/nu_shell) or file an issue or feature request here on [GitHub](https://github.com/nushell/nushell/issues/new/choose)!
<!--WIP-->
## Developing
### Set up
### Setup
This is no different than other Rust projects.
Nushell requires a recent Rust toolchain and some dependencies; [refer to the Nu Book for up-to-date requirements](https://www.nushell.sh/book/installation.html#build-from-source). After installing dependencies, you should be able to clone+build Nu like any other Rust project:
```bash
git clone https://github.com/nushell/nushell
@ -28,24 +21,24 @@ cargo build
- Build and run Nushell:
```shell
cargo build --release && cargo run --release
cargo run
```
- Build and run with extra features:
- Build and run with extra features. Currently extra features include dataframes and sqlite database support.
```shell
cargo build --release --features=extra && cargo run --release --features=extra
cargo run --features=extra
```
- Run Clippy on Nushell:
```shell
cargo clippy --all --features=stable
cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect
```
- Run all tests:
```shell
cargo test --all --features=stable
cargo test --workspace --features=extra
```
- Run all tests for a specific command
@ -71,5 +64,5 @@ cargo build
- To view verbose logs when developing, enable the `trace` log level.
```shell
cargo build --release --features=extra && cargo run --release --features=extra -- --log-level trace
cargo run --release --features=extra -- --log-level trace
```

1770
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,8 @@ license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
rust-version = "1.59"
version = "0.62.0"
rust-version = "1.60"
version = "0.66.1"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -28,47 +28,53 @@ members = [
"crates/nu_plugin_gstat",
"crates/nu_plugin_example",
"crates/nu_plugin_query",
"crates/nu_plugin_custom_values",
"crates/nu-utils",
]
[dependencies]
chrono = "0.4.19"
chrono = { version = "0.4.19", features = ["serde"] }
crossterm = "0.23.0"
ctrlc = "3.2.1"
log = "0.4"
miette = "4.5.0"
nu-ansi-term = "0.45.1"
nu-cli = { path="./crates/nu-cli", version = "0.62.0" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.62.0" }
nu-command = { path="./crates/nu-command", version = "0.62.0" }
nu-engine = { path="./crates/nu-engine", version = "0.62.0" }
nu-json = { path="./crates/nu-json", version = "0.62.0" }
nu-parser = { path="./crates/nu-parser", version = "0.62.0" }
nu-path = { path="./crates/nu-path", version = "0.62.0" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.62.0" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.62.0" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.62.0" }
nu-system = { path = "./crates/nu-system", version = "0.62.0" }
nu-table = { path = "./crates/nu-table", version = "0.62.0" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.62.0" }
openssl = { version = "0.10.38", features = ["vendored"], optional = true }
miette = "5.1.0"
nu-ansi-term = "0.46.0"
nu-cli = { path="./crates/nu-cli", version = "0.66.1" }
nu-color-config = { path = "./crates/nu-color-config", version = "0.66.1" }
nu-command = { path="./crates/nu-command", version = "0.66.1" }
nu-engine = { path="./crates/nu-engine", version = "0.66.1" }
nu-json = { path="./crates/nu-json", version = "0.66.1" }
nu-parser = { path="./crates/nu-parser", version = "0.66.1" }
nu-path = { path="./crates/nu-path", version = "0.66.1" }
nu-plugin = { path = "./crates/nu-plugin", optional = true, version = "0.66.1" }
nu-pretty-hex = { path = "./crates/nu-pretty-hex", version = "0.66.1" }
nu-protocol = { path = "./crates/nu-protocol", version = "0.66.1" }
nu-system = { path = "./crates/nu-system", version = "0.66.1" }
nu-table = { path = "./crates/nu-table", version = "0.66.1" }
nu-term-grid = { path = "./crates/nu-term-grid", version = "0.66.1" }
nu-utils = { path = "./crates/nu-utils", version = "0.66.1" }
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
pretty_env_logger = "0.4.0"
rayon = "1.5.1"
reedline = { version = "0.5.0", features = ["bashisms"]}
is_executable = "1.0.1"
[target.'cfg(not(target_os = "windows"))'.dependencies]
# Our dependencies don't use OpenSSL on Windows
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.62.0" }
nu-test-support = { path="./crates/nu-test-support", version = "0.66.1" }
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]
embed-resource = "1"
winres = "0.1"
[features]
plugin = ["nu-plugin", "nu-cli/plugin", "nu-parser/plugin", "nu-command/plugin", "nu-protocol/plugin", "nu-engine/plugin"]
@ -103,6 +109,13 @@ inherits = "release"
strip = false
debug = true
# build with `cargo build --profile ci`
# to analyze performance with tooling like linux perf
[profile.ci]
inherits = "dev"
strip = false
debug = false
# Main nu binary
[[bin]]
name = "nu"

249
README.md
View File

@ -1,5 +1,4 @@
# README
# Nushell <!-- omit in toc -->
[![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu)
![Build Status](https://img.shields.io/github/workflow/status/nushell/nushell/continuous-integration)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn)
@ -8,128 +7,100 @@
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)
![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)
## Nushell
A new type of shell.
![Example of nushell](images/nushell-autocomplete6.gif "Example of nushell")
## Table of Contents <!-- omit in toc -->
- [Status](#status)
- [Learning About Nu](#learning-about-nu)
- [Installation](#installation)
- [Philosophy](#philosophy)
- [Pipelines](#pipelines)
- [Opening files](#opening-files)
- [Plugins](#plugins)
- [Goals](#goals)
- [Progress](#progress)
- [Officially Supported By](#officially-supported-by)
- [Contributing](#contributing)
- [License](#license)
## Status
This project has reached a minimum-viable product level of quality.
While contributors dogfood it as their daily driver, it may be unstable for some commands.
Future releases will work to fill out missing features and improve stability.
Its design is also subject to change as it matures.
This project has reached a minimum-viable-product level of quality. Many people use it as their daily driver, but it may be unstable for some commands. Nu's design is subject to change as it matures.
Nu comes with a set of built-in commands (listed below).
If a command is unknown, the command will shell-out and execute it (using cmd on Windows or bash on Linux and macOS), correctly passing through stdin, stdout, and stderr, so things like your daily git workflows and even `vim` will work just fine.
## Learning About Nu
## Learning more
The [Nushell book](https://www.nushell.sh/book/) is the primary source of Nushell documentation. You can find [a full list of Nu commands in the book](https://www.nushell.sh/book/command_reference.html), and we have many examples of using Nu in our [cookbook](https://www.nushell.sh/cookbook/).
There are a few good resources to learn about Nu.
There is a [book](https://www.nushell.sh/book/) about Nu that is currently in progress.
The book focuses on using Nu and its core concepts.
If you're a developer who would like to contribute to Nu, we're also working on a [book for developers](https://www.nushell.sh/contributor-book/) to help you get started.
There are also [good first issues](https://github.com/nushell/nushell/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) to help you dive in.
We also have an active [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell) if you'd like to come and chat with us.
You can also find information on more specific topics in our [cookbook](https://www.nushell.sh/cookbook/).
We're also active on [Discord](https://discord.gg/NtAbbGn) and [Twitter](https://twitter.com/nu_shell); come and chat with us!
## Installation
### Local
To quickly install Nu:
Up-to-date installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). **Windows users**: please note that Nu works on Windows 10 and does not currently have Windows 7/8.1 support.
To build Nu, you will need to use the **latest stable (1.59 or later)** version of the compiler.
Required dependencies:
- pkg-config and libssl (only needed on Linux)
- On Debian/Ubuntu: `apt install pkg-config libssl-dev`
Optional dependencies:
- To use Nu with all possible optional features enabled, you'll also need the following:
- On Linux (on Debian/Ubuntu): `apt install libxcb-composite0-dev libx11-dev`
To install Nu via cargo (make sure you have installed [rustup](https://rustup.rs/) and the latest stable compiler via `rustup install stable`):
For Windows users, you may also need to install the [Microsoft Visual C++ 2015 Redistributables](https://docs.microsoft.com/cpp/windows/latest-supported-vc-redist).
```shell
cargo install nu
```
To install Nu via the [Windows Package Manager](https://aka.ms/winget-cli):
```shell
```bash
# Linux and macOS
brew install nushell
# Windows
winget install nushell
```
To install Nu via the [Chocolatey](https://chocolatey.org) package manager:
To use `Nu` in Github Action, check [setup-nu](https://github.com/marketplace/actions/setup-nu) for more detail.
```shell
choco install nushell
```
You can also build Nu yourself with all the bells and whistles (be sure to have installed the [dependencies](https://www.nushell.sh/book/installation.html#dependencies) for your platform), once you have checked out this repo with git:
```shell
cargo build --workspace --features=extra
```
### Packaging status
Detailed installation instructions can be found in the [installation chapter of the book](https://www.nushell.sh/book/installation.html). Nu is available via many package managers:
[![Packaging status](https://repology.org/badge/vertical-allrepos/nushell.svg)](https://repology.org/project/nushell/versions)
#### Fedora
[COPR repo](https://copr.fedorainfracloud.org/coprs/atim/nushell/): `sudo dnf copr enable atim/nushell -y && sudo dnf install nushell -y`
## Philosophy
Nu draws inspiration from projects like PowerShell, functional programming languages, and modern CLI tools.
Rather than thinking of files and services as raw streams of text, Nu looks at each input as something with structure.
For example, when you list the contents of a directory, what you get back is a table of rows, where each row represents an item in that directory.
Rather than thinking of files and data as raw streams of text, Nu looks at each input as something with structure.
For example, when you list the contents of a directory what you get back is a table of rows, where each row represents an item in that directory.
These values can be piped through a series of steps, in a series of commands called a 'pipeline'.
### Pipelines
In Unix, it's common to pipe between commands to split up a sophisticated command over multiple steps.
Nu takes this a step further and builds heavily on the idea of _pipelines_.
Just as the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
As in the Unix philosophy, Nu allows commands to output to stdout and read from stdin.
Additionally, commands can output structured data (you can think of this as a third kind of stream).
Commands that work in the pipeline fit into one of three categories:
- Commands that produce a stream (e.g., `ls`)
- Commands that filter a stream (eg, `where type == "Dir"`)
- Commands that consume the output of the pipeline (e.g., `autoview`)
- Commands that filter a stream (eg, `where type == "dir"`)
- Commands that consume the output of the pipeline (e.g., `table`)
Commands are separated by the pipe symbol (`|`) to denote a pipeline flowing left to right.
```shell
> ls | where type == "Dir" | autoview
───┬────────┬──────┬───────┬──────────────
# │ name │ type │ size │ modified
───┼────────┼──────┼───────┼──────────────
0assetsDir │ 128 B │ 5 months ago
1cratesDir │ 704 B │ 50 mins ago
2debianDir │ 352 B │ 5 months ago
3 │ docsDir │ 192 B │ 50 mins ago
4imagesDir │ 160 B │ 5 months ago
5src Dir │ 128 B │ 1 day ago
6targetDir │ 160 B │ 5 days ago
7tests │ Dir │ 192 B │ 3 months ago
───┴────────┴──────┴───────┴──────────────
> ls | where type == "dir" | table
╭────┬──────────┬──────┬─────────┬───────────────╮
# name │ type │ size modified
├────┼──────────┼──────┼─────────┼───────────────┤
0.cargo dir │ 0 B │ 9 minutes ago
1assets dir │ 0 B │ 2 weeks ago
2crates dir │ 4.0 KiB │ 2 weeks ago
3 │ dockerdir │ 0 B │ 2 weeks ago
4docs dir │ 0 B │ 2 weeks ago
5imagesdir │ 0 B │ 2 weeks ago │
6pkg_mgrsdir │ 0 B │ 2 weeks ago
7samples │ dir │ 0 B │ 2 weeks ago
8 │ src │ dir │ 4.0 KiB │ 2 weeks ago │
9 │ target │ dir │ 0 B │ a day ago │
10 │ tests │ dir │ 4.0 KiB │ 2 weeks ago │
11 │ wix │ dir │ 0 B │ 2 weeks ago │
╰────┴──────────┴──────┴─────────┴───────────────╯
```
Because most of the time you'll want to see the output of a pipeline, `autoview` is assumed.
Because most of the time you'll want to see the output of a pipeline, `table` is assumed.
We could have also written the above:
```shell
> ls | where type == Dir
> ls | where type == "dir"
```
Being able to use the same commands and compose them differently is an important philosophy in Nu.
@ -137,15 +108,13 @@ For example, we could use the built-in `ps` command to get a list of the running
```shell
> ps | where cpu > 0
───┬───────┬───────────────────┬─────────┬─────────┬──────────┬──────────
# │ pid name │ status │ cpu mem │ virtual
───┼───────┼───────────────────┼─────────┼─────────┼──────────┼──────────
0 435 │ irq/142-SYNA327 │ Sleeping │ 7.5699 │ 0 B │ 0 B
1 1609pulseaudio │ Sleeping 6.5605 │ 10.6 MB │ 2.3 GB
2 1625 │ gnome-shell Sleeping │ 6.5684 │ 639.6 MB │ 7.3 GB
32202 │ Web Content │ Sleeping │ 6.8157 │ 320.8 MB │ 3.0 GB
4328788 │ nu_plugin_core_ps │ Sleeping │ 92.5750 │ 5.9 MB │ 633.2 MB
───┴────────┴───────────────────┴──────────┴─────────┴──────────┴──────────
───┬───────┬──────────────────┬───────────┬───────────╮
# │ pid name │ cpu mem │ virtual
───┼───────┼──────────────────┼───────────┼───────────┤
02240 │ Slack.exe │ 16.40 │ 178.3 MiB │ 232.6 MiB │
116948Slack.exe16.32 │ 205.0 MiB │ 197.9 MiB │
217700 │ nu.exe 3.77 │ 26.1 MiB │ 8.8 MiB │
╰───┴───────┴───────────┴───────┴───────────┴───────────╯
```
### Opening files
@ -155,72 +124,49 @@ For example, you can load a .toml file as structured data and explore it:
```shell
> open Cargo.toml
────────────────────┬───────────────────────────
bin [table 18 rows]
build-dependencies │ [row serde toml]
dependencies [row 29 columns]
dev-dependencies[row nu-test-support]
features [row 19 columns]
package [row 12 columns]
workspace[row members]
────────────────────┴───────────────────────────
──────────────────┬────────────────────
bin │ [table 1 row]
dependencies {record 24 fields}
│ dev-dependencies │ {record 8 fields}
│ features {record 10 fields}
│ package{record 13 fields}
profile{record 3 fields}
│ target {record 2 fields}
│ workspace │ {record 1 field}
╰──────────────────┴────────────────────╯
```
We can pipeline this into a command that gets the contents of one of the columns:
We can pipe this into a command that gets the contents of one of the columns:
```shell
> open Cargo.toml | get package
───────────────┬────────────────────────────────────
authors │ [table 1 rows]
default-run │ nu
description │ A new type of shell
documentation │ https://www.nushell.sh/book/
edition │ 2018
exclude │ [table 1 rows]
homepage │ https://www.nushell.sh
license │ MIT
name │ nu
readme │ README.md
repository │ https://github.com/nushell/nushell
version │ 0.32.0
───────────────┴────────────────────────────────────
───────────────┬────────────────────────────────────
authors │ [list 1 item]
default-run │ nu
description │ A new type of shell
documentation │ https://www.nushell.sh/book/
edition │ 2018
exclude │ [list 1 item]
homepage │ https://www.nushell.sh
license │ MIT
name │ nu
readme │ README.md
repository │ https://github.com/nushell/nushell
│ rust-version │ 1.60 │
│ version │ 0.63.1 │
╰───────────────┴────────────────────────────────────╯
```
Finally, we can use commands outside of Nu once we have the data we want:
And if needed we can drill down further:
```shell
> open Cargo.toml | get package.version
0.32.0
0.63.1
```
### Configuration
Nu has early support for configuring the shell. You can refer to the book for a list of [all supported variables](https://www.nushell.sh/book/configuration.html).
To set one of these variables, you can use `config set`. For example:
```shell
> config set line_editor.edit_mode "vi"
> config set path $nu.path
```
### Shells
Nu will work inside of a single directory and allow you to navigate around your filesystem by default.
Nu also offers a way of adding additional working directories that you can jump between, allowing you to work in multiple directories simultaneously.
To do so, use the `enter` command, which will allow you to create a new "shell" and enter it at the specified path.
You can toggle between this new shell and the original shell with the `p` (for previous) and `n` (for next), allowing you to navigate around a ring buffer of shells.
Once you're done with a shell, you can `exit` it and remove it from the ring buffer.
Finally, to get a list of all the current shells, you can use the `shells` command.
### Plugins
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use.
This allows you to extend nu for your needs.
There are a few examples in the `plugins` directory.
Nu supports plugins that offer additional functionality to the shell and follow the same structured data model that built-in commands use. There are a few examples in the `crates/nu_plugins_*` directories.
Plugins are binaries that are available in your path and follow a `nu_plugin_*` naming convention.
These binaries interact with nu via a simple JSON-RPC protocol where the command identifies itself and passes along its configuration, making it available for use.
@ -231,23 +177,19 @@ If the plugin is a sink, it is given the full vector of final data and is given
Nu adheres closely to a set of goals that make up its design philosophy. As features are added, they are checked against these goals.
- First and foremost, Nu is cross-platform. Commands and techniques should carry between platforms and offer consistent first-class support for Windows, macOS, and Linux.
- First and foremost, Nu is cross-platform. Commands and techniques should work across platforms and Nu has first-class support for Windows, macOS, and Linux.
- Nu ensures direct compatibility with existing platform-specific executables that make up people's workflows.
- Nu ensures compatibility with existing platform-specific executables.
- Nu's workflow and tools should have the usability in day-to-day experience of using a shell in 2019 (and beyond).
- Nu's workflow and tools should have the usability expected of modern software in 2022 (and beyond).
- Nu views data as both structured and unstructured. It is a structured shell like PowerShell.
- Nu views data as either structured or unstructured. It is a structured shell like PowerShell.
- Finally, Nu views data functionally. Rather than using mutation, pipelines act as a means to load, change, and save data without mutable state.
## Commands
You can find a list of Nu commands, complete with documentation, in [quick command references](https://www.nushell.sh/book/command_reference.html).
## Progress
Nu is in heavy development and will naturally change as it matures and people use it. The chart below isn't meant to be exhaustive, but rather helps give an idea for some of the areas of development and their relative completion:
Nu is under heavy development and will naturally change as it matures. The chart below isn't meant to be exhaustive, but it helps give an idea for some of the areas of development and their relative maturity:
| Features | Not started | Prototype | MVP | Preview | Mature | Notes |
| ------------- | :---------: | :-------: | :-: | :-----: | :----: | -------------------------------------------------------------------- |
@ -270,20 +212,15 @@ Nu is in heavy development and will naturally change as it matures and people us
Please submit an issue or PR to be added to this list.
### Integrations
- [zoxide](https://github.com/ajeetdsouza/zoxide)
- [starship](https://github.com/starship/starship)
- [oh-my-posh](https://ohmyposh.dev)
- [Couchbase Shell](https://couchbase.sh)
- [virtualenv](https://github.com/pypa/virtualenv)
### Mentions
- [The Python Launcher for Unix](https://github.com/brettcannon/python-launcher#how-do-i-get-a-table-of-python-executables-in-nushell)
## Contributing
See [Contributing](CONTRIBUTING.md) for details.
Thanks to all the people who already contributed!
See [Contributing](CONTRIBUTING.md) for details. Thanks to all the people who already contributed!
<a href="https://github.com/nushell/nushell/graphs/contributors">
<img src="https://contributors-img.web.app/image?repo=nushell/nushell&max=500" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -1,49 +0,0 @@
#include <winver.h>
#define VER_FILEVERSION 0,59,1,0
#define VER_FILEVERSION_STR "0.59.1"
#define VER_PRODUCTVERSION 0,59,1,0
#define VER_PRODUCTVERSION_STR "0.59.1"
#ifdef RC_INVOKED
#ifdef DEBUG // TODO: Actually define DEBUG
#define VER_DEBUG VS_FF_DEBUG
#else
#define VER_DEBUG 0
#endif
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION
PRODUCTVERSION VER_PRODUCTVERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
FILEFLAGS VER_DEBUG
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "nushell"
VALUE "FileDescription", "Nushell"
VALUE "FileVersion", VER_FILEVERSION_STR
VALUE "InternalName", "nu.exe"
VALUE "LegalCopyright", "Copyright (C) 2022"
VALUE "OriginalFilename", "nu.exe"
VALUE "ProductName", "Nushell"
VALUE "ProductVersion", VER_PRODUCTVERSION_STR
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#define IDI_ICON 0x101
IDI_ICON ICON "assets/nu_logo.ico"
#endif

View File

@ -1,6 +1,12 @@
#[cfg(windows)]
fn main() {
embed_resource::compile_for("assets/nushell.rc", &["nu"])
let mut res = winres::WindowsResource::new();
res.set("ProductName", "Nushell");
res.set("FileDescription", "Nushell");
res.set("LegalCopyright", "Copyright (C) 2022");
res.set_icon("assets/nu_logo.ico");
res.compile()
.expect("Failed to run the Windows resource compiler (rc.exe)");
}
#[cfg(not(windows))]

View File

@ -4,28 +4,33 @@ description = "CLI-related functionality for Nushell"
edition = "2021"
license = "MIT"
name = "nu-cli"
version = "0.62.0"
version = "0.66.1"
[dev-dependencies]
nu-test-support = { path="../nu-test-support", version = "0.62.0" }
nu-command = { path = "../nu-command", version = "0.62.0" }
nu-test-support = { path="../nu-test-support", version = "0.66.1" }
nu-command = { path = "../nu-command", version = "0.66.1" }
rstest = "0.15.0"
[dependencies]
nu-engine = { path = "../nu-engine", version = "0.62.0" }
nu-path = { path = "../nu-path", version = "0.62.0" }
nu-parser = { path = "../nu-parser", version = "0.62.0" }
nu-protocol = { path = "../nu-protocol", version = "0.62.0" }
nu-utils = { path = "../nu-utils", version = "0.62.0" }
nu-ansi-term = "0.45.1"
nu-color-config = { path = "../nu-color-config", version = "0.62.0" }
reedline = { version = "0.5.0", features = ["bashisms"]}
nu-engine = { path = "../nu-engine", version = "0.66.1" }
nu-path = { path = "../nu-path", version = "0.66.1" }
nu-parser = { path = "../nu-parser", version = "0.66.1" }
nu-protocol = { path = "../nu-protocol", version = "0.66.1" }
nu-utils = { path = "../nu-utils", version = "0.66.1" }
nu-ansi-term = "0.46.0"
nu-color-config = { path = "../nu-color-config", version = "0.66.1" }
reedline = { version = "0.9.0", features = ["bashisms", "sqlite"]}
crossterm = "0.23.0"
miette = { version = "4.5.0", features = ["fancy"] }
thiserror = "1.0.29"
miette = { version = "5.1.0", features = ["fancy"] }
thiserror = "1.0.31"
fuzzy-matcher = "0.3.7"
log = "0.4"
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]
plugin = []

View File

@ -5,21 +5,34 @@ use nu_engine::{convert_env_values, eval_block};
use nu_parser::parse;
use nu_protocol::engine::Stack;
use nu_protocol::{
engine::{EngineState, StateDelta, StateWorkingSet},
PipelineData, Spanned,
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,
) -> Result<()> {
// Run a command (or commands) given to us by the user
table_mode: Option<Value>,
) -> Result<Option<i64>> {
// 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();
config.table_mode = t_mode.as_string()?;
engine_state.set_config(&config);
}
let mut working_set = StateWorkingSet::new(engine_state);
let (output, err) = parse(&mut working_set, None, commands.item.as_bytes(), false, &[]);
@ -32,39 +45,20 @@ 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 config = engine_state.get_config().clone();
// 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(), 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);
}
match eval_block(engine_state, stack, &block, input, false, false) {
// Run the block
let exit_code = match eval_block(engine_state, stack, &block, input, false, false) {
Ok(pipeline_data) => {
crate::eval_file::print_table_or_error(engine_state, stack, pipeline_data, &config)
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) => {
let working_set = StateWorkingSet::new(engine_state);
@ -72,11 +66,11 @@ pub fn evaluate_commands(
report_error(&working_set, &err);
std::process::exit(1);
}
}
};
if is_perf_true {
info!("evaluate {}:{}:{}", file!(), line!(), column!());
}
Ok(())
Ok(exit_code)
}

View File

@ -1,7 +1,5 @@
use crate::completions::{
file_completions::file_path_completion, Completer, CompletionOptions, MatchAlgorithm, SortBy,
};
use nu_parser::{unescape_unquote_string, FlatShape};
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm, SortBy};
use nu_parser::FlatShape;
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
Span,
@ -12,7 +10,6 @@ use std::sync::Arc;
pub struct CommandCompletion {
engine_state: Arc<EngineState>,
flattened: Vec<(Span, FlatShape)>,
flat_idx: usize,
flat_shape: FlatShape,
}
@ -21,13 +18,11 @@ impl CommandCompletion {
engine_state: Arc<EngineState>,
_: &StateWorkingSet,
flattened: Vec<(Span, FlatShape)>,
flat_idx: usize,
flat_shape: FlatShape,
) -> Self {
Self {
engine_state,
flattened,
flat_idx,
flat_shape,
}
}
@ -39,7 +34,7 @@ impl CommandCompletion {
) -> Vec<String> {
let mut executables = vec![];
let paths = self.engine_state.env_vars.get("PATH");
let paths = self.engine_state.get_env_var("PATH");
if let Some(paths) = paths {
if let Ok(paths) = paths.as_list() {
@ -48,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);
@ -161,7 +158,7 @@ impl Completer for CommandCompletion {
fn fetch(
&mut self,
working_set: &StateWorkingSet,
prefix: Vec<u8>,
_prefix: Vec<u8>,
span: Span,
offset: usize,
pos: usize,
@ -204,76 +201,25 @@ impl Completer for CommandCompletion {
return subcommands;
}
let config = working_set.get_config();
let commands = if matches!(self.flat_shape, nu_parser::FlatShape::External)
|| matches!(self.flat_shape, nu_parser::FlatShape::InternalCall)
|| ((span.end - span.start) == 0)
{
// we're in a gap or at a command
self.complete_commands(working_set, span, offset, true, options.match_algorithm)
self.complete_commands(
working_set,
span,
offset,
config.enable_external_completion,
options.match_algorithm,
)
} else {
vec![]
};
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
}
} else {
"".to_string()
};
let preceding_byte = if span.start > offset {
working_set
.get_span_contents(Span {
start: span.start - 1,
end: span.start,
})
.to_vec()
} else {
vec![]
};
// let prefix = working_set.get_span_contents(flat.0);
let prefix = String::from_utf8_lossy(&prefix).to_string();
file_path_completion(span, &prefix, &cwd, options.match_algorithm)
subcommands
.into_iter()
.map(move |x| {
if self.flat_idx == 0 {
// We're in the command position
if (x.1.starts_with('"') || x.1.starts_with('\'') || x.1.starts_with('`'))
&& !matches!(preceding_byte.get(0), Some(b'^'))
{
let (trimmed, _) = unescape_unquote_string(x.1.as_bytes(), span);
let expanded = nu_path::canonicalize_with(trimmed, &cwd);
if let Ok(expanded) = expanded {
if is_executable::is_executable(expanded) {
(x.0, format!("^{}", x.1))
} else {
(x.0, x.1)
}
} else {
(x.0, x.1)
}
} else {
(x.0, x.1)
}
} else {
(x.0, x.1)
}
})
.map(move |x| Suggestion {
value: x.1,
description: None,
extra: None,
span: reedline::Span {
start: x.0.start - offset,
end: x.0.end - offset,
},
append_whitespace: false,
})
.chain(subcommands.into_iter())
.chain(commands.into_iter())
.collect::<Vec<_>>()
}

View File

@ -37,7 +37,10 @@ impl NuCompleter {
) -> Vec<Suggestion> {
let config = self.engine_state.get_config();
let mut options = CompletionOptions::default();
let mut options = CompletionOptions {
case_sensitive: config.case_sensitive_completions,
..Default::default()
};
if config.completion_algorithm == "fuzzy" {
options.match_algorithm = MatchAlgorithm::Fuzzy;
@ -56,60 +59,39 @@ impl NuCompleter {
fn completion_helper(&mut self, line: &str, pos: usize) -> Vec<Suggestion> {
let mut working_set = StateWorkingSet::new(&self.engine_state);
let offset = working_set.next_span_start();
let mut line = line.to_string();
line.insert(pos, 'a');
let (mut new_line, alias_offset) = try_find_alias(line.as_bytes(), &working_set);
let initial_line = line.to_string();
new_line.push(b'a');
let pos = offset + pos;
let (output, _err) = parse(
&mut working_set,
Some("completer"),
line.as_bytes(),
false,
&[],
);
let (output, _err) = parse(&mut working_set, Some("completer"), &new_line, false, &[]);
for pipeline in output.pipelines.into_iter() {
for expr in pipeline.expressions {
let flattened: Vec<_> = flatten_expression(&working_set, &expr);
let span_offset: usize = alias_offset.iter().sum();
for (flat_idx, flat) in flattened.iter().enumerate() {
if pos >= flat.0.start && pos < flat.0.end {
if pos + span_offset >= flat.0.start && pos + span_offset < flat.0.end {
// Context variables
let most_left_var =
most_left_variable(flat_idx, &working_set, flattened.clone());
// Create a new span
let new_span = Span {
start: flat.0.start,
end: flat.0.end - 1,
let new_span = if flat_idx == 0 {
Span {
start: flat.0.start,
end: flat.0.end - 1 - span_offset,
}
} else {
Span {
start: flat.0.start - span_offset,
end: flat.0.end - 1 - span_offset,
}
};
// Parses the prefix
let mut prefix = working_set.get_span_contents(flat.0).to_vec();
prefix.remove(pos - flat.0.start);
// Completions that depends on the previous expression (e.g: use, source)
if flat_idx > 0 {
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
// Read the content for the previous expression
let prev_expr_str =
working_set.get_span_contents(previous_expr.0).to_vec();
// Completion for .nu files
if prev_expr_str == b"use" || prev_expr_str == b"source" {
let mut completer =
DotNuCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
}
}
}
prefix.remove(pos - (flat.0.start - span_offset));
// Variables completion
if prefix.starts_with(b"$") || most_left_var.is_some() {
@ -143,6 +125,42 @@ impl NuCompleter {
);
}
// Completions that depends on the previous expression (e.g: use, source)
if flat_idx > 0 {
if let Some(previous_expr) = flattened.get(flat_idx - 1) {
// Read the content for the previous expression
let prev_expr_str =
working_set.get_span_contents(previous_expr.0).to_vec();
// Completion for .nu files
if prev_expr_str == b"use" || prev_expr_str == b"source" {
let mut completer =
DotNuCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
} else if prev_expr_str == b"ls" {
let mut completer =
FileCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
}
}
}
// Match other types
match &flat.1 {
FlatShape::Custom(decl_id) => {
@ -150,7 +168,7 @@ impl NuCompleter {
self.engine_state.clone(),
self.stack.clone(),
*decl_id,
line,
initial_line,
);
return self.process_completion(
@ -175,9 +193,7 @@ impl NuCompleter {
pos,
);
}
FlatShape::Filepath
| FlatShape::GlobPattern
| FlatShape::ExternalArg => {
FlatShape::Filepath | FlatShape::GlobPattern => {
let mut completer = FileCompletion::new(self.engine_state.clone());
return self.process_completion(
@ -194,18 +210,34 @@ impl NuCompleter {
self.engine_state.clone(),
&working_set,
flattened.clone(),
flat_idx,
// flat_idx,
flat_shape.clone(),
);
return self.process_completion(
let out: Vec<_> = self.process_completion(
&mut completer,
&working_set,
prefix,
prefix.clone(),
new_span,
offset,
pos,
);
if out.is_empty() {
let mut completer =
FileCompletion::new(self.engine_state.clone());
return self.process_completion(
&mut completer,
&working_set,
prefix,
new_span,
offset,
pos,
);
}
return out;
}
};
}
@ -223,6 +255,85 @@ impl ReedlineCompleter for NuCompleter {
}
}
type MatchedAlias = Vec<(Vec<u8>, Vec<u8>)>;
// Handler the completion when giving lines contains at least one alias. (e.g: `g checkout`)
// that `g` is an alias of `git`
fn try_find_alias(line: &[u8], working_set: &StateWorkingSet) -> (Vec<u8>, Vec<usize>) {
// An vector represents the offsets of alias
// e.g: the offset is 2 for the alias `g` of `git`
let mut alias_offset = vec![];
let mut output = vec![];
if let Some(matched_alias) = search_alias(line, working_set) {
let mut lens = matched_alias.len();
for (input_vec, line_vec) in matched_alias {
alias_offset.push(line_vec.len() - input_vec.len());
output.extend(line_vec);
if lens > 1 {
output.push(b' ');
lens -= 1;
}
}
if !line.is_empty() {
let last = line.last().expect("input is empty");
if last == &b' ' {
output.push(b' ');
}
}
} else {
output = line.to_vec();
}
(output, alias_offset)
}
fn search_alias(input: &[u8], working_set: &StateWorkingSet) -> Option<MatchedAlias> {
let mut vec_names = vec![];
let mut vec_alias = vec![];
let mut pos = 0;
let mut is_alias = false;
for (index, character) in input.iter().enumerate() {
if *character == b' ' {
let range = &input[pos..index];
vec_names.push(range.to_owned());
pos = index + 1;
}
}
// Push the rest to names vector.
if pos < input.len() {
vec_names.push((&input[pos..]).to_owned());
}
for name in &vec_names {
if let Some(alias_id) = working_set.find_alias(&name[..]) {
let alias_span = working_set.get_alias(alias_id);
let mut span_vec = vec![];
is_alias = true;
for alias in alias_span {
let name = working_set.get_span_contents(*alias);
if !name.is_empty() {
span_vec.push(name);
}
}
// Join span of vector together for complex alias, e.g: `f` is an alias for `git remote -v`
let full_aliases = span_vec.join(&[b' '][..]);
vec_alias.push(full_aliases);
} else {
vec_alias.push(name.to_owned());
}
}
if is_alias {
// Zip names and alias vectors, the original inputs and its aliases mapping.
// e.g:(['g'], ['g','i','t'])
let output = vec_names.into_iter().zip(vec_alias).collect();
Some(output)
} else {
None
}
}
// reads the most left variable returning it's name (e.g: $myvar)
// and the depth (a.b.c)
fn most_left_variable(

View File

@ -4,10 +4,11 @@ use nu_protocol::{
levenshtein_distance, Span,
};
use reedline::Suggestion;
use std::fs;
use std::path::Path;
use std::sync::Arc;
use super::{partial_from, prepend_base_dir, MatchAlgorithm};
use super::{partial_from, prepend_base_dir};
const SEP: char = std::path::MAIN_SEPARATOR;
@ -32,7 +33,7 @@ impl Completer for DirectoryCompletion {
_: usize,
options: &CompletionOptions,
) -> Vec<Suggestion> {
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
@ -43,7 +44,7 @@ impl Completer for DirectoryCompletion {
let partial = String::from_utf8_lossy(&prefix).to_string();
// Filter only the folders
let output: Vec<_> = directory_completion(span, &partial, &cwd, options.match_algorithm)
let output: Vec<_> = directory_completion(span, &partial, &cwd, options)
.into_iter()
.map(move |x| Suggestion {
value: x.1,
@ -102,7 +103,7 @@ pub fn directory_completion(
span: nu_protocol::Span,
partial: &str,
cwd: &str,
match_algorithm: MatchAlgorithm,
options: &CompletionOptions,
) -> Vec<(nu_protocol::Span, String)> {
let original_input = partial;
@ -120,10 +121,10 @@ pub fn directory_completion(
return result
.filter_map(|entry| {
entry.ok().and_then(|entry| {
if let Ok(metadata) = entry.metadata() {
if let Ok(metadata) = fs::metadata(entry.path()) {
if metadata.is_dir() {
let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matches(&partial, &file_name, match_algorithm) {
if matches(&partial, &file_name, options) {
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
format!("{}{}", base_dir_name, file_name)
} else {

View File

@ -37,7 +37,7 @@ impl Completer for DotNuCompletion {
// Fetch the lib dirs
let lib_dirs: Vec<String> =
if let Some(lib_dirs) = self.engine_state.env_vars.get("NU_LIB_DIRS") {
if let Some(lib_dirs) = self.engine_state.get_env_var("NU_LIB_DIRS") {
lib_dirs
.as_list()
.into_iter()
@ -58,7 +58,7 @@ impl Completer for DotNuCompletion {
};
// Check if the base_dir is a folder
if base_dir != "./" {
if base_dir != format!(".{}", SEP) {
// Add the base dir into the directories to be searched
search_dirs.push(base_dir.clone());
@ -70,7 +70,7 @@ impl Completer for DotNuCompletion {
partial = base_dir_partial;
} else {
// Fetch the current folder
let current_folder = if let Some(d) = self.engine_state.env_vars.get("PWD") {
let current_folder = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
@ -91,7 +91,7 @@ impl Completer for DotNuCompletion {
let output: Vec<Suggestion> = search_dirs
.into_iter()
.flat_map(|it| {
file_path_completion(span, &partial, &it, options.match_algorithm)
file_path_completion(span, &partial, &it, options)
.into_iter()
.filter(|it| {
// Different base dir, so we list the .nu files or folders

View File

@ -1,4 +1,4 @@
use crate::completions::{Completer, CompletionOptions, MatchAlgorithm};
use crate::completions::{Completer, CompletionOptions};
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
levenshtein_distance, Span,
@ -30,7 +30,7 @@ impl Completer for FileCompletion {
_: usize,
options: &CompletionOptions,
) -> Vec<Suggestion> {
let cwd = if let Some(d) = self.engine_state.env_vars.get("PWD") {
let cwd = if let Some(d) = self.engine_state.get_env_var("PWD") {
match d.as_string() {
Ok(s) => s,
Err(_) => "".to_string(),
@ -39,7 +39,7 @@ impl Completer for FileCompletion {
"".to_string()
};
let prefix = String::from_utf8_lossy(&prefix).to_string();
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options.match_algorithm)
let output: Vec<_> = file_path_completion(span, &prefix, &cwd, options)
.into_iter()
.map(move |x| Suggestion {
value: x.1,
@ -112,7 +112,7 @@ pub fn file_path_completion(
span: nu_protocol::Span,
partial: &str,
cwd: &str,
match_algorithm: MatchAlgorithm,
options: &CompletionOptions,
) -> Vec<(nu_protocol::Span, String)> {
let original_input = partial;
let (base_dir_name, partial) = partial_from(partial);
@ -129,7 +129,7 @@ pub fn file_path_completion(
.filter_map(|entry| {
entry.ok().and_then(|entry| {
let mut file_name = entry.file_name().to_string_lossy().into_owned();
if matches(&partial, &file_name, match_algorithm) {
if matches(&partial, &file_name, options) {
let mut path = if prepend_base_dir(original_input, &base_dir_name) {
format!("{}{}", base_dir_name, file_name)
} else {
@ -158,8 +158,15 @@ pub fn file_path_completion(
Vec::new()
}
pub fn matches(partial: &str, from: &str, match_algorithm: MatchAlgorithm) -> bool {
match_algorithm.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase())
pub fn matches(partial: &str, from: &str, options: &CompletionOptions) -> bool {
// Check for case sensitive
if !options.case_sensitive {
return options
.match_algorithm
.matches_str(&from.to_ascii_lowercase(), &partial.to_ascii_lowercase());
}
options.match_algorithm.matches_str(from, partial)
}
/// Returns whether the base_dir should be prepended to the file path

View File

@ -11,7 +11,7 @@ use std::sync::Arc;
#[derive(Clone)]
pub struct VariableCompletion {
engine_state: Arc<EngineState>,
engine_state: Arc<EngineState>, // TODO: Is engine state necessary? It's already a part of working set in fetch()
stack: Stack,
var_context: (Vec<u8>, Vec<Vec<u8>>), // tuple with $var and the sublevels (.b.c.d)
}
@ -41,7 +41,7 @@ impl Completer for VariableCompletion {
options: &CompletionOptions,
) -> Vec<Suggestion> {
let mut output = vec![];
let builtins = ["$nu", "$in", "$config", "$env", "$nothing"];
let builtins = ["$nu", "$in", "$env", "$nothing"];
let var_str = std::str::from_utf8(&self.var_context.0)
.unwrap_or("")
.to_lowercase();
@ -70,7 +70,18 @@ impl Completer for VariableCompletion {
self.var_context.1.clone().into_iter().skip(1).collect();
if let Some(val) = env_vars.get(&target_var_str) {
return nested_suggestions(val.clone(), nested_levels, current_span);
for suggestion in
nested_suggestions(val.clone(), nested_levels, current_span)
{
if options
.match_algorithm
.matches_u8(suggestion.value.as_bytes(), &prefix)
{
output.push(suggestion);
}
}
return output;
}
} else {
// No nesting provided, return all env vars
@ -105,7 +116,18 @@ impl Completer for VariableCompletion {
end: current_span.end,
},
) {
return nested_suggestions(nuval, self.var_context.1.clone(), current_span);
for suggestion in
nested_suggestions(nuval, self.var_context.1.clone(), current_span)
{
if options
.match_algorithm
.matches_u8(suggestion.value.as_bytes(), &prefix)
{
output.push(suggestion);
}
}
return output;
}
}
@ -122,7 +144,18 @@ impl Completer for VariableCompletion {
// If the value exists and it's of type Record
if let Ok(value) = var {
return nested_suggestions(value, self.var_context.1.clone(), current_span);
for suggestion in
nested_suggestions(value, self.var_context.1.clone(), current_span)
{
if options
.match_algorithm
.matches_u8(suggestion.value.as_bytes(), &prefix)
{
output.push(suggestion);
}
}
return output;
}
}
}
@ -143,24 +176,39 @@ impl Completer for VariableCompletion {
}
}
// TODO: The following can be refactored (see find_commands_by_predicate() used in
// command_completions).
let mut removed_overlays = vec![];
// Working set scope vars
for scope in &working_set.delta.scope {
for v in &scope.vars {
if options.match_algorithm.matches_u8(v.0, &prefix) {
output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(),
description: None,
extra: None,
span: current_span,
append_whitespace: false,
});
for scope_frame in working_set.delta.scope.iter().rev() {
for overlay_frame in scope_frame
.active_overlays(&mut removed_overlays)
.iter()
.rev()
{
for v in &overlay_frame.vars {
if options.match_algorithm.matches_u8(v.0, &prefix) {
output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(),
description: None,
extra: None,
span: current_span,
append_whitespace: false,
});
}
}
}
}
// Permanent state vars
for scope in &self.engine_state.scope {
for v in &scope.vars {
// for scope in &self.engine_state.scope {
for overlay_frame in self
.engine_state
.active_overlays(&removed_overlays)
.iter()
.rev()
{
for v in &overlay_frame.vars {
if options.match_algorithm.matches_u8(v.0, &prefix) {
output.push(Suggestion {
value: String::from_utf8_lossy(v.0).to_string(),
@ -173,7 +221,7 @@ impl Completer for VariableCompletion {
}
}
output.dedup();
output.dedup(); // TODO: Removes only consecutive duplicates, is it intended?
output
}

View File

@ -1,23 +1,33 @@
use crate::util::{eval_source, report_error};
#[cfg(feature = "plugin")]
use log::info;
use nu_protocol::engine::{EngineState, Stack, StateDelta, StateWorkingSet};
use nu_protocol::{PipelineData, Span};
#[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;
#[cfg(feature = "plugin")]
const PLUGIN_FILE: &str = "plugin.nu";
const HISTORY_FILE_TXT: &str = "history.txt";
const HISTORY_FILE_SQLITE: &str = "history.sqlite3";
#[cfg(feature = "plugin")]
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 {
@ -40,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);
@ -66,10 +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(), 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);
}
@ -82,3 +107,14 @@ pub fn eval_config_contents(
}
}
}
pub(crate) fn get_history_path(storage_path: &str, mode: HistoryFileFormat) -> Option<PathBuf> {
nu_path::config_dir().map(|mut history_path| {
history_path.push(storage_path);
history_path.push(match mode {
HistoryFileFormat::PlainText => HISTORY_FILE_TXT,
HistoryFileFormat::Sqlite => HISTORY_FILE_SQLITE,
});
history_path
})
}

View File

@ -4,12 +4,13 @@ use log::trace;
use miette::{IntoDiagnostic, Result};
use nu_engine::convert_env_values;
use nu_parser::parse;
use nu_protocol::Type;
use nu_protocol::{
ast::Call,
engine::{EngineState, Stack, StateWorkingSet},
Config, PipelineData, Span, Value,
};
use std::io::Write;
use nu_utils::stdout_write_all_and_flush;
/// Main function used when a file path is found as argument for nu
pub fn evaluate_file(
@ -34,7 +35,7 @@ pub fn evaluate_file(
let _ = parse(&mut working_set, Some(&path), &file, false, &[]);
if working_set.find_decl(b"main").is_some() {
if working_set.find_decl(b"main", &Type::Any).is_some() {
let args = format!("main {}", args.join(" "));
if !eval_source(
@ -61,81 +62,78 @@ pub fn evaluate_file(
}
pub fn print_table_or_error(
engine_state: &EngineState,
engine_state: &mut EngineState,
stack: &mut Stack,
mut pipeline_data: PipelineData,
config: &Config,
) {
config: &mut Config,
) -> Option<i64> {
let exit_code = match &mut pipeline_data {
PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
_ => None,
};
match engine_state.find_decl("table".as_bytes()) {
// Change the engine_state config to use the passed in configuration
engine_state.set_config(config);
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 {
let stdout = std::io::stdout();
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');
match stdout.lock().write_all(out.as_bytes()) {
Ok(_) => (),
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 {
let stdout = std::io::stdout();
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');
match stdout.lock().write_all(out.as_bytes()) {
Ok(_) => (),
Err(err) => eprintln!("{}", err),
};
}
print_or_exit(pipeline_data, engine_state, config);
}
};
// Make sure everything has finished
if let Some(exit_code) = exit_code {
let _: Vec<_> = exit_code.into_iter().collect();
let mut exit_code: Vec<_> = exit_code.into_iter().collect();
exit_code
.pop()
.and_then(|last_exit_code| match last_exit_code {
Value::Int { val: code, .. } => Some(code),
_ => None,
})
} else {
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

@ -586,7 +586,7 @@ impl Menu for DescriptionMenu {
} else {
self.example_index = Some(self.examples.len().saturating_sub(1));
}
} else {
} else if !self.examples.is_empty() {
self.example_index = Some(0);
}
}
@ -598,7 +598,7 @@ impl Menu for DescriptionMenu {
} else {
self.example_index = Some(0);
}
} else {
} else if !self.examples.is_empty() {
self.example_index = Some(0);
}
}

View File

@ -1,6 +1,7 @@
use nu_engine::documentation::get_flags_section;
use nu_protocol::{engine::EngineState, levenshtein_distance};
use reedline::{Completer, Suggestion};
use std::fmt::Write;
use std::sync::Arc;
pub struct NuHelpCompleter(Arc<EngineState>);
@ -19,6 +20,10 @@ impl NuHelpCompleter {
.filter(|(sig, _, _, _)| {
sig.name.to_lowercase().contains(&line.to_lowercase())
|| sig.usage.to_lowercase().contains(&line.to_lowercase())
|| sig
.search_terms
.iter()
.any(|term| term.to_lowercase().contains(&line.to_lowercase()))
|| sig
.extra_usage
.to_lowercase()
@ -49,7 +54,7 @@ impl NuHelpCompleter {
long_desc.push_str("\r\n\r\n");
}
long_desc.push_str(&format!("Usage:\r\n > {}\r\n", sig.call_signature()));
let _ = write!(long_desc, "Usage:\r\n > {}\r\n", sig.call_signature());
if !sig.named.is_empty() {
long_desc.push_str(&get_flags_section(sig))
@ -61,27 +66,28 @@ impl NuHelpCompleter {
{
long_desc.push_str("\r\nParameters:\r\n");
for positional in &sig.required_positional {
long_desc
.push_str(&format!(" {}: {}\r\n", positional.name, positional.desc));
let _ = write!(long_desc, " {}: {}\r\n", positional.name, positional.desc);
}
for positional in &sig.optional_positional {
long_desc.push_str(&format!(
let _ = write!(
long_desc,
" (optional) {}: {}\r\n",
positional.name, positional.desc
));
);
}
if let Some(rest_positional) = &sig.rest_positional {
long_desc.push_str(&format!(
let _ = write!(
long_desc,
" ...{}: {}\r\n",
rest_positional.name, rest_positional.desc
));
);
}
}
let extra: Vec<String> = examples
.iter()
.map(|example| example.example.to_string())
.map(|example| example.example.replace('\n', "\r\n"))
.collect();
Suggestion {

View File

@ -19,6 +19,10 @@ impl Command for NuHighlight {
"Syntax highlight the input string."
}
fn search_terms(&self) -> Vec<&str> {
vec!["syntax", "color", "convert"]
}
fn run(
&self,
engine_state: &EngineState,

View File

@ -16,11 +16,28 @@ impl Command for Print {
fn signature(&self) -> Signature {
Signature::build("print")
.rest("rest", SyntaxShape::Any, "the values to print")
.switch(
"no-newline",
"print without inserting a newline for the line ending",
Some('n'),
)
.switch("stderr", "print to stderr instead of stdout", Some('e'))
.category(Category::Strings)
}
fn usage(&self) -> &str {
"Prints the values given"
"Print the given values to stdout"
}
fn extra_usage(&self) -> &str {
r#"Unlike `echo`, this command does not return any value (`print | describe` will return "nothing").
Since this command has no output, there is no point in piping it with other commands.
`print` may be used inside blocks of code (e.g.: hooks) to display text during execution without interfering with the pipeline."#
}
fn search_terms(&self) -> Vec<&str> {
vec!["display"]
}
fn run(
@ -31,10 +48,13 @@ impl Command for Print {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let args: Vec<Value> = call.rest(engine_state, stack, 0)?;
let no_newline = call.has_flag("no-newline");
let to_stderr = call.has_flag("stderr");
let head = call.head;
for arg in args {
arg.into_pipeline_data().print(engine_state, stack)?;
arg.into_pipeline_data()
.print(engine_state, stack, no_newline, to_stderr)?;
}
Ok(PipelineData::new(head))

View File

@ -1,5 +1,6 @@
#[cfg(windows)]
use nu_utils::enable_vt_processing;
use reedline::DefaultPrompt;
use {
reedline::{
Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus, PromptViMode,
@ -7,9 +8,6 @@ use {
std::borrow::Cow,
};
const PROMPT_MARKER_BEFORE_PS1: &str = "\x1b]133;A\x1b\\"; // OSC 133;A ST
const PROMPT_MARKER_BEFORE_PS2: &str = "\x1b]133;A;k=s\x1b\\"; // OSC 133;A;k=s ST
/// Nushell prompt definition
#[derive(Clone)]
pub struct NushellPrompt {
@ -19,7 +17,6 @@ pub struct NushellPrompt {
default_vi_insert_prompt_indicator: Option<String>,
default_vi_normal_prompt_indicator: Option<String>,
default_multiline_indicator: Option<String>,
shell_integration: bool,
}
impl Default for NushellPrompt {
@ -37,7 +34,6 @@ impl NushellPrompt {
default_vi_insert_prompt_indicator: None,
default_vi_normal_prompt_indicator: None,
default_multiline_indicator: None,
shell_integration: false,
}
}
@ -87,34 +83,25 @@ impl NushellPrompt {
fn default_wrapped_custom_string(&self, str: String) -> String {
format!("({})", str)
}
pub(crate) fn enable_shell_integration(&mut self) {
self.shell_integration = true
}
}
impl Prompt for NushellPrompt {
fn render_prompt_left(&self) -> Cow<str> {
// Just before starting to draw the PS1 prompt send the escape code (see
// https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers)
let mut prompt = if self.shell_integration {
String::from(PROMPT_MARKER_BEFORE_PS1)
#[cfg(windows)]
{
let _ = enable_vt_processing();
}
if let Some(prompt_string) = &self.left_prompt_string {
prompt_string.replace('\n', "\r\n").into()
} else {
String::new()
};
prompt.push_str(&match &self.left_prompt_string {
Some(prompt_string) => prompt_string.replace('\n', "\r\n"),
None => {
let default = DefaultPrompt::new();
default
.render_prompt_left()
.to_string()
.replace('\n', "\r\n")
}
});
prompt.into()
let default = DefaultPrompt::new();
default
.render_prompt_left()
.to_string()
.replace('\n', "\r\n")
.into()
}
}
fn render_prompt_right(&self) -> Cow<str> {
@ -155,21 +142,10 @@ impl Prompt for NushellPrompt {
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
// Just before starting to draw the PS1 prompt send the escape code (see
// https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers)
let mut prompt = if self.shell_integration {
String::from(PROMPT_MARKER_BEFORE_PS2)
} else {
String::new()
};
prompt.push_str(
self.default_multiline_indicator
.as_ref()
.unwrap_or(&String::from("::: ")),
);
prompt.into()
match &self.default_multiline_indicator {
Some(indicator) => indicator.as_str().into(),
None => "::: ".into(),
}
}
fn render_prompt_history_search_indicator(

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,
@ -147,10 +165,6 @@ pub(crate) fn update_prompt<'prompt>(
(prompt_vi_insert_string, prompt_vi_normal_string),
);
if config.shell_integration {
nu_prompt.enable_shell_integration();
}
let ret_val = nu_prompt as &dyn Prompt;
if is_perf_true {
info!("update_prompt {}:{}:{}", file!(), line!(), column!());

View File

@ -24,7 +24,7 @@ const DEFAULT_COMPLETION_MENU: &str = r#"
type: {
layout: columnar
columns: 4
col_width: 20
col_width: 20
col_padding: 2
}
style: {
@ -58,7 +58,7 @@ const DEFAULT_HELP_MENU: &str = r#"
type: {
layout: description
columns: 4
col_width: 20
col_width: 20
col_padding: 2
selection_rows: 4
description_rows: 10
@ -501,14 +501,16 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
ReedlineEvent::MenuPrevious,
);
// History menu keybinding
keybindings.add_binding(
KeyModifiers::CONTROL,
KeyCode::Char('r'),
ReedlineEvent::Menu("history_menu".to_string()),
);
keybindings.add_binding(
KeyModifiers::CONTROL,
KeyCode::Char('x'),
ReedlineEvent::UntilFound(vec![
ReedlineEvent::Menu("history_menu".to_string()),
ReedlineEvent::MenuPageNext,
]),
ReedlineEvent::MenuPageNext,
);
keybindings.add_binding(
@ -522,8 +524,8 @@ fn add_menu_keybindings(keybindings: &mut Keybindings) {
// Help menu keybinding
keybindings.add_binding(
KeyModifiers::CONTROL,
KeyCode::Char('q'),
KeyModifiers::NONE,
KeyCode::F(1),
ReedlineEvent::Menu("help_menu".to_string()),
);
}
@ -873,7 +875,16 @@ fn edit_from_record(
"moveleft" => EditCommand::MoveLeft,
"moveright" => EditCommand::MoveRight,
"movewordleft" => EditCommand::MoveWordLeft,
"movebigwordleft" => EditCommand::MoveBigWordLeft,
"movewordright" => EditCommand::MoveWordRight,
"movewordrightend" => EditCommand::MoveWordRightEnd,
"movebigwordrightend" => EditCommand::MoveBigWordRightEnd,
"movewordrightstart" => EditCommand::MoveWordRightStart,
"movebigwordrightstart" => EditCommand::MoveBigWordRightStart,
"movetoposition" => {
let value = extract_value("value", cols, vals, span)?;
EditCommand::MoveToPosition(value.as_integer()? as usize)
}
"insertchar" => {
let value = extract_value("value", cols, vals, span)?;
let char = extract_char(value, config)?;
@ -886,6 +897,7 @@ fn edit_from_record(
"insertnewline" => EditCommand::InsertNewline,
"backspace" => EditCommand::Backspace,
"delete" => EditCommand::Delete,
"cutchar" => EditCommand::CutChar,
"backspaceword" => EditCommand::BackspaceWord,
"deleteword" => EditCommand::DeleteWord,
"clear" => EditCommand::Clear,
@ -896,7 +908,11 @@ fn edit_from_record(
"cuttoend" => EditCommand::CutToEnd,
"cuttolineend" => EditCommand::CutToLineEnd,
"cutwordleft" => EditCommand::CutWordLeft,
"cutbigwordleft" => EditCommand::CutBigWordLeft,
"cutwordright" => EditCommand::CutWordRight,
"cutbigwordright" => EditCommand::CutBigWordRight,
"cutwordrighttonext" => EditCommand::CutWordRightToNext,
"cutbigwordrighttonext" => EditCommand::CutBigWordRightToNext,
"pastecutbufferbefore" => EditCommand::PasteCutBufferBefore,
"pastecutbufferafter" => EditCommand::PasteCutBufferAfter,
"uppercaseword" => EditCommand::UppercaseWord,

View File

@ -1,34 +1,41 @@
use crate::reedline_config::add_menus;
use crate::{completions::NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
use crate::{prompt_update, reedline_config};
use crate::{
reedline_config::KeybindingsMode,
util::{eval_source, report_error},
completions::NuCompleter,
prompt_update,
reedline_config::{add_menus, create_keybindings, KeybindingsMode},
util::{eval_source, get_guaranteed_cwd, report_error, report_error_new},
NuHighlighter, NuValidator, NushellPrompt,
};
use log::info;
use log::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;
use nu_parser::lex;
use nu_protocol::engine::Stack;
use nu_protocol::PipelineData;
use nu_engine::{convert_env_values, eval_block};
use nu_parser::{lex, parse};
use nu_protocol::{
engine::{EngineState, StateWorkingSet},
ShellError, Span, Value,
ast::PathMember,
engine::{EngineState, Stack, StateWorkingSet},
BlockId, HistoryFileFormat, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId,
};
use reedline::{DefaultHinter, Emacs, Vi};
use reedline::{DefaultHinter, Emacs, SqliteBackedHistory, Vi};
use regex::Regex;
use std::io::{self, Write};
use std::path::PathBuf;
use std::{sync::atomic::Ordering, time::Instant};
use sysinfo::SystemExt;
const PROMPT_MARKER_BEFORE_CMD: &str = "\x1b]133;C\x1b\\"; // OSC 133;C ST
// 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\\";
// 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(
engine_state: &mut EngineState,
stack: &mut Stack,
history_path: Option<PathBuf>,
nushell_path: &str,
is_perf_true: bool,
) -> Result<()> {
use reedline::{FileBackedHistory, Reedline, Signal};
@ -80,26 +87,38 @@ 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!());
}
let mut line_editor = Reedline::create();
let history_path = crate::config_files::get_history_path(
nushell_path,
engine_state.config.history_file_format,
);
if let Some(history_path) = history_path.as_deref() {
if is_perf_true {
info!("setup history {}:{}:{}", file!(), line!(), column!());
}
let history = Box::new(
FileBackedHistory::with_file(
config.max_history_size as usize,
history_path.to_path_buf(),
)
.into_diagnostic()?,
);
let history: Box<dyn reedline::History> = match engine_state.config.history_file_format {
HistoryFileFormat::PlainText => Box::new(
FileBackedHistory::with_file(
config.max_history_size as usize,
history_path.to_path_buf(),
)
.into_diagnostic()?,
),
HistoryFileFormat::Sqlite => Box::new(
SqliteBackedHistory::with_file(history_path.to_path_buf()).into_diagnostic()?,
),
};
line_editor = line_editor.with_history(history);
};
let sys = sysinfo::System::new();
loop {
if is_perf_true {
info!(
@ -110,12 +129,24 @@ 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);
}
// Reset the SIGQUIT handler
if let Some(sig_quit) = engine_state.get_sig_quit() {
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!());
@ -132,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(),
}))
@ -161,13 +191,35 @@ pub fn evaluate_repl(
}
};
line_editor = line_editor.with_buffer_editor(config.buffer_editor.clone(), "nu".into());
let buffer_editor = if !config.buffer_editor.is_empty() {
Some(config.buffer_editor.clone())
} else {
stack
.get_env_var(engine_state, "EDITOR")
.map(|v| v.as_string().unwrap_or_default())
.filter(|v| !v.is_empty())
.or_else(|| {
stack
.get_env_var(engine_state, "VISUAL")
.map(|v| v.as_string().unwrap_or_default())
.filter(|v| !v.is_empty())
})
};
line_editor = if let Some(buffer_editor) = buffer_editor {
line_editor.with_buffer_editor(buffer_editor, "nu".into())
} else {
line_editor
};
if config.sync_history_on_enter {
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 {
@ -175,7 +227,7 @@ pub fn evaluate_repl(
}
// Changing the line editor based on the found keybindings
line_editor = match reedline_config::create_keybindings(config) {
line_editor = match create_keybindings(config) {
Ok(keybindings) => match keybindings {
KeybindingsMode::Emacs(keybindings) => {
let edit_mode = Box::new(Emacs::new(keybindings));
@ -200,6 +252,24 @@ pub fn evaluate_repl(
info!("prompt_update {}:{}:{}", file!(), line!(), column!());
}
// 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.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
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);
@ -215,10 +285,53 @@ pub fn evaluate_repl(
}
let input = line_editor.read_line(prompt);
let use_shell_integration = config.shell_integration;
let shell_integration = config.shell_integration;
match input {
Ok(Signal::Success(s)) => {
let history_supports_meta =
matches!(config.history_file_format, HistoryFileFormat::Sqlite);
if history_supports_meta && !s.is_empty() {
line_editor
.update_last_command_context(&|mut c| {
c.start_timestamp = Some(chrono::Utc::now());
c.hostname = sys.host_name();
c.cwd = Some(StateWorkingSet::new(engine_state).get_cwd());
c
})
.into_diagnostic()?; // todo: don't stop repl if error here?
}
// Right before we start running the code the user gave us,
// fire the "pre_execution" hook
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
// };
// // 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();
let tokens = lex(s.as_bytes(), 0, &[], &[], false);
// Check if this is a single call to a directory, if so auto-cd
@ -227,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() {
@ -244,12 +351,19 @@ pub fn evaluate_repl(
&ShellError::DirectoryNotFound(tokens.0[0].span, None),
);
}
let path = nu_path::canonicalize_with(path, &cwd)
.expect("internal error: cannot canonicalize known path");
(path.to_string_lossy().to_string(), tokens.0[0].span)
};
stack.add_env_var(
"OLDPWD".into(),
Value::String {
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(
@ -290,44 +404,60 @@ pub fn evaluate_repl(
&format!("entry #{}", entry_num),
PipelineData::new(Span::new(0, 0)),
);
stack.add_env_var(
"CMD_DURATION_MS".into(),
Value::String {
val: format!("{}", start_time.elapsed().as_millis()),
span: Span { start: 0, end: 0 },
},
);
}
// 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.env_vars.insert("PWD".into(), cwd);
let cmd_duration = start_time.elapsed();
stack.add_env_var(
"CMD_DURATION_MS".into(),
Value::String {
val: format!("{}", cmd_duration.as_millis()),
span: Span { start: 0, end: 0 },
},
);
if history_supports_meta && !s.is_empty() {
line_editor
.update_last_command_context(&|mut c| {
c.duration = Some(cmd_duration);
c.exit_status = stack
.get_env_var(engine_state, "LAST_EXIT_CODE")
.and_then(|e| e.as_i64().ok());
c
})
.into_diagnostic()?; // todo: don't stop repl if error here?
}
if use_shell_integration {
// Just before running a command/program, send the escape code (see
// https://sw.kovidgoyal.net/kitty/shell-integration/#notes-for-shell-developers)
let mut ansi_escapes = String::from(PROMPT_MARKER_BEFORE_CMD);
ansi_escapes.push_str(RESET_APPLICATION_MODE);
if shell_integration {
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()?;
ansi_escapes.push_str(&format!("\x1b]2;{}\x07", path));
// 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))?;
}
// print!("{}", ansi_escapes);
match io::stdout().write_all(ansi_escapes.as_bytes()) {
Ok(it) => it,
Err(err) => print!("error: {}", err),
};
}
}
Ok(Signal::CtrlC) => {
// `Reedline` clears the line content. New prompt is shown
if shell_integration {
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(&get_command_finished_marker(stack, engine_state))?;
}
println!();
break;
}
@ -336,9 +466,346 @@ pub fn evaluate_repl(
if !message.contains("duration") {
println!("Error: {:?}", err);
}
if shell_integration {
run_ansi_sequence(&get_command_finished_marker(stack, engine_state))?;
}
}
}
}
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,
Err(err) => {
return Err(ShellError::GenericError(
"Error writing ansi sequence".into(),
err.to_string(),
Some(Span { start: 0, end: 0 }),
None,
Vec::new(),
));
}
};
io::stdout().flush().map_err(|e| {
ShellError::GenericError(
"Error flushing stdio".into(),
e.to_string(),
Some(Span { start: 0, end: 0 }),
None,
Vec::new(),
)
})
}
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");
}
// 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 DRIVE_PATH_REGEX.is_match(orig) {
return true;
}
}
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

@ -9,18 +9,35 @@ use nu_protocol::{
};
#[cfg(windows)]
use nu_utils::enable_vt_processing;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
// This will collect environment variables from std::env and adds them to a stack.
//
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
// env vars into it (in a "NAME"="value" format, quite similar to the output of the Unix 'env'
// tool), then uses the file to get the spans. The file stays in memory, no filesystem IO is done.
pub fn gather_parent_env_vars(engine_state: &mut EngineState) {
gather_env_vars(std::env::vars(), engine_state);
//
// The "PWD" env value will be forced to `init_cwd`.
// The reason to use `init_cwd`:
//
// While gathering parent env vars, the parent `PWD` may not be the same as `current working directory`.
// Consider to the following command as the case (assume we execute command inside `/tmp`):
//
// tmux split-window -v -c "#{pane_current_path}"
//
// Here nu execute external command `tmux`, and tmux starts a new `nushell`, with `init_cwd` value "#{pane_current_path}".
// But at the same time `PWD` still remains to be `/tmp`.
//
// In this scenario, the new `nushell`'s PWD should be "#{pane_current_path}" rather init_cwd.
pub fn gather_parent_env_vars(engine_state: &mut EngineState, init_cwd: &Path) {
gather_env_vars(std::env::vars(), engine_state, init_cwd);
}
fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &mut EngineState) {
fn gather_env_vars(
vars: impl Iterator<Item = (String, String)>,
engine_state: &mut EngineState,
init_cwd: &Path,
) {
fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
let working_set = StateWorkingSet::new(engine_state);
report_error(
@ -43,35 +60,31 @@ fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &
}
let mut fake_env_file = String::new();
let mut has_pwd = false;
// Write all the env vars into a fake file
for (name, val) in vars {
if name == "PWD" {
has_pwd = true;
}
put_env_to_fake_file(&name, &val, &mut fake_env_file);
}
if !has_pwd {
match std::env::current_dir() {
Ok(cwd) => {
put_env_to_fake_file("PWD", &cwd.to_string_lossy(), &mut fake_env_file);
}
Err(e) => {
// Could not capture current working directory
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::GenericError(
"Current directory not found".to_string(),
"".to_string(),
None,
Some(format!("Retrieving current directory failed: {:?}", e)),
Vec::new(),
),
);
}
match init_cwd.to_str() {
Some(cwd) => {
put_env_to_fake_file("PWD", cwd, &mut fake_env_file);
}
None => {
// Could not capture current working directory
let working_set = StateWorkingSet::new(engine_state);
report_error(
&working_set,
&ShellError::GenericError(
"Current directory is not a valid utf-8 path".to_string(),
"".to_string(),
None,
Some(format!(
"Retrieving current directory failed: {:?} not a valid utf-8 path",
init_cwd
)),
Vec::new(),
),
);
}
}
@ -179,7 +192,7 @@ fn gather_env_vars(vars: impl Iterator<Item = (String, String)>, engine_state: &
};
// stack.add_env_var(name, value);
engine_state.env_vars.insert(name, value);
engine_state.add_env_var(name, value);
}
}
}
@ -211,18 +224,10 @@ pub fn eval_source(
(output, working_set.render())
};
let cwd = match nu_engine::env::current_dir_str(engine_state, stack) {
Ok(p) => PathBuf::from(p),
Err(e) => {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &e);
get_init_cwd()
}
};
if let Err(err) = engine_state.merge_delta(delta, Some(stack), &cwd) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
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) {
@ -237,7 +242,7 @@ pub fn eval_source(
set_last_exit_code(stack, 0);
}
if let Err(err) = pipeline_data.print(engine_state, stack) {
if let Err(err) = pipeline_data.print(engine_state, stack, false, false) {
let working_set = StateWorkingSet::new(engine_state);
report_error(&working_set, &err);
@ -287,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,
@ -300,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::*;
@ -317,14 +342,21 @@ mod test {
]
.into_iter(),
&mut engine_state,
Path::new("t"),
);
let env = engine_state.env_vars;
let env = engine_state.render_env_vars();
assert!(matches!(env.get("FOO"), Some(Value::String { val, .. }) if val == "foo"));
assert!(matches!(env.get("SYMBOLS"), Some(Value::String { val, .. }) if val == symbols));
assert!(matches!(env.get(symbols), Some(Value::String { val, .. }) if val == "symbols"));
assert!(env.get("PWD").is_some());
assert!(
matches!(env.get(&"FOO".to_string()), Some(&Value::String { val, .. }) if val == "foo")
);
assert!(
matches!(env.get(&"SYMBOLS".to_string()), Some(&Value::String { val, .. }) if val == symbols)
);
assert!(
matches!(env.get(&symbols.to_string()), Some(&Value::String { val, .. }) if val == "symbols")
);
assert!(env.get(&"PWD".to_string()).is_some());
assert_eq!(env.len(), 4);
}
}

View File

@ -0,0 +1,65 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{match_suggestions, new_engine};
#[test]
fn alias_of_command_and_flags() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls -l"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn alias_of_basic_command() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls "#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("ll t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn alias_of_another_alias() {
let (dir, _, mut engine, mut stack) = new_engine();
// Create an alias
let alias = r#"alias ll = ls -la"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir.clone()).is_ok());
// Create the second alias
let alias = r#"alias lf = ll -f"#;
assert!(support::merge_input(alias.as_bytes(), &mut engine, &mut stack, dir).is_ok());
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let suggestions = completer.complete("lf t", 4);
#[cfg(windows)]
let expected_paths: Vec<String> = vec!["test_a\\".to_string(), "test_b\\".to_string()];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec!["test_a/".to_string(), "test_b/".to_string()];
match_suggestions(expected_paths, suggestions)
}

View File

@ -0,0 +1,69 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use rstest::{fixture, rstest};
use support::{match_suggestions, new_engine};
#[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();
// Add record value as example
let record = r#"def animals [] { ["cat", "dog", "eel" ] }
def my-command [animal: string@animals] { print $animal }"#;
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)
}
#[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

@ -0,0 +1,28 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::new_engine;
#[test]
fn dotnu_completions() {
// Create a new engine
let (_, _, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test source completion
let completion_str = "source ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
// Test use completion
let completion_str = "use ".to_string();
let suggestions = completer.complete(&completion_str, completion_str.len());
assert_eq!(1, suggestions.len());
assert_eq!("custom_completion.nu", suggestions.get(0).unwrap().value);
}

View File

@ -0,0 +1,272 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{file, folder, match_suggestions, new_engine};
#[test]
fn file_completions() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cp {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("nushell")),
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
file(dir.join("custom_completion.nu")),
file(dir.join(".hidden_file")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
// 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
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
// Match the results
match_suggestions(expected_paths, suggestions);
}
#[test]
fn command_ls_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "ls ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_open_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "open ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_rm_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "rm ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_cp_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "cp ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_save_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "save ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_touch_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "touch ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions)
}
#[test]
fn command_watch_completion() {
let (_, _, engine, stack) = new_engine();
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
let target_dir = "watch ";
let suggestions = completer.complete(target_dir, target_dir.len());
#[cfg(windows)]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a\\".to_string(),
"test_b\\".to_string(),
"another\\".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder\\".to_string(),
];
#[cfg(not(windows))]
let expected_paths: Vec<String> = vec![
"nushell".to_string(),
"test_a/".to_string(),
"test_b/".to_string(),
"another/".to_string(),
"custom_completion.nu".to_string(),
".hidden_file".to_string(),
".hidden_folder/".to_string(),
];
match_suggestions(expected_paths, suggestions)
}

View File

@ -0,0 +1,38 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{match_suggestions, new_engine};
#[test]
fn flag_completions() {
// Create a new engine
let (_, _, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the 'ls' flags
let suggestions = completer.complete("ls -", 4);
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(),
"-h".into(),
"-l".into(),
"-s".into(),
];
// Match results
match_suggestions(expected, suggestions);
}

View File

@ -0,0 +1,29 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{folder, match_suggestions, new_engine};
#[test]
fn folder_completions() {
// Create a new engine
let (dir, dir_str, engine, stack) = new_engine();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cd {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
}

View File

@ -0,0 +1,128 @@
use std::path::PathBuf;
use nu_command::create_default_context;
use nu_engine::eval_block;
use nu_parser::parse;
use nu_protocol::{
engine::{EngineState, Stack, StateWorkingSet},
PipelineData, ShellError, Span, Value,
};
use nu_test_support::fs;
use reedline::Suggestion;
const SEP: char = std::path::MAIN_SEPARATOR;
// creates a new engine with the current path into the completions fixtures folder
pub fn new_engine() -> (PathBuf, String, EngineState, Stack) {
// Target folder inside assets
let dir = fs::fixtures().join("completions");
let mut dir_str = dir
.clone()
.into_os_string()
.into_string()
.unwrap_or_default();
dir_str.push(SEP);
// Create a new engine with default context
let mut engine_state = create_default_context();
// New stack
let mut stack = Stack::new();
// Add pwd as env var
stack.add_env_var(
"PWD".to_string(),
Value::String {
val: dir_str.clone(),
span: nu_protocol::Span {
start: 0,
end: dir_str.len(),
},
},
);
stack.add_env_var(
"TEST".to_string(),
Value::String {
val: "NUSHELL".to_string(),
span: nu_protocol::Span {
start: 0,
end: dir_str.len(),
},
},
);
// 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)
}
// 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);
});
}
// append the separator to the converted path
pub fn folder(path: PathBuf) -> String {
let mut converted_path = file(path);
converted_path.push(SEP);
converted_path
}
// convert a given path to string
pub fn file(path: PathBuf) -> String {
path.into_os_string().into_string().unwrap_or_default()
}
// merge_input executes the given input into the engine
// and merges the state
pub fn merge_input(
input: &[u8],
engine_state: &mut EngineState,
stack: &mut Stack,
dir: PathBuf,
) -> Result<(), ShellError> {
let (block, delta) = {
let mut working_set = StateWorkingSet::new(engine_state);
let (block, err) = parse(&mut working_set, None, input, false, &[]);
assert!(err.is_none());
(block, working_set.render())
};
if let Err(err) = engine_state.merge_delta(delta) {
return Err(err);
}
assert!(eval_block(
engine_state,
stack,
&block,
PipelineData::Value(
Value::Nothing {
span: Span { start: 0, end: 0 },
},
None
),
false,
false
)
.is_ok());
// Merge environment into the permanent state
engine_state.merge_env(stack, &dir)
}

View File

@ -0,0 +1,3 @@
pub mod completions_helpers;
pub use completions_helpers::{file, folder, match_suggestions, merge_input, new_engine};

View File

@ -1,107 +0,0 @@
use std::path::PathBuf;
use nu_cli::NuCompleter;
use nu_command::create_default_context;
use nu_protocol::engine::{EngineState, Stack};
use nu_test_support::fs;
use reedline::{Completer, Suggestion};
const SEP: char = std::path::MAIN_SEPARATOR;
#[test]
fn file_completions() {
// Create a new engine
let (dir, dir_str, engine) = new_engine();
let stack = Stack::new();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cp {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
file(dir.join("nushell")),
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
file(dir.join(".hidden_file")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
// Test completions for the completions/another folder
let target_dir = format!("cd {}", folder(dir.join("another")));
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![file(dir.join("another").join("newfile"))];
// Match the results
match_suggestions(expected_paths, suggestions);
}
#[test]
fn folder_completions() {
// Create a new engine
let (dir, dir_str, engine) = new_engine();
let stack = Stack::new();
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for the current folder
let target_dir = format!("cd {}", dir_str);
let suggestions = completer.complete(&target_dir, target_dir.len());
// Create the expected values
let expected_paths: Vec<String> = vec![
folder(dir.join("test_a")),
folder(dir.join("test_b")),
folder(dir.join("another")),
folder(dir.join(".hidden_folder")),
];
// Match the results
match_suggestions(expected_paths, suggestions);
}
// creates a new engine with the current path into the completions fixtures folder
pub fn new_engine() -> (PathBuf, String, EngineState) {
// Target folder inside assets
let dir = fs::fixtures().join("completions");
let mut dir_str = dir
.clone()
.into_os_string()
.into_string()
.unwrap_or_default();
dir_str.push(SEP);
// Create a default engine
(dir.clone(), dir_str, create_default_context(dir))
}
// match a list of suggestions with the expected values
pub fn match_suggestions(expected: Vec<String>, suggestions: Vec<Suggestion>) {
expected.iter().zip(suggestions).for_each(|it| {
assert_eq!(it.0, &it.1.value);
});
}
// append the separator to the converted path
pub fn folder(path: PathBuf) -> String {
let mut converted_path = file(path);
converted_path.push(SEP);
converted_path
}
// convert a given path to string
pub fn file(path: PathBuf) -> String {
path.into_os_string().into_string().unwrap_or_default()
}

View File

@ -0,0 +1,88 @@
pub mod support;
use nu_cli::NuCompleter;
use reedline::Completer;
use support::{match_suggestions, new_engine};
#[test]
fn variables_completions() {
// Create a new engine
let (dir, _, mut engine, mut stack) = new_engine();
// Add record value as example
let record = "let actor = { name: 'Tom Hardy', age: 44 }";
assert!(support::merge_input(record.as_bytes(), &mut engine, &mut stack, dir).is_ok());
// Instatiate a new completer
let mut completer = NuCompleter::new(std::sync::Arc::new(engine), stack);
// Test completions for $nu
let suggestions = completer.complete("$nu.", 4);
assert_eq!(9, suggestions.len());
let expected: Vec<String> = vec![
"config-path".into(),
"env-path".into(),
"history-path".into(),
"home-path".into(),
"loginshell-path".into(),
"os-info".into(),
"pid".into(),
"scope".into(),
"temp-path".into(),
];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $nu.h (filter)
let suggestions = completer.complete("$nu.h", 5);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["history-path".into(), "home-path".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for custom var
let suggestions = completer.complete("$actor.", 7);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["age".into(), "name".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for custom var (filtering)
let suggestions = completer.complete("$actor.n", 8);
assert_eq!(1, suggestions.len());
let expected: Vec<String> = vec!["name".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $env
let suggestions = completer.complete("$env.", 5);
assert_eq!(2, suggestions.len());
let expected: Vec<String> = vec!["PWD".into(), "TEST".into()];
// Match results
match_suggestions(expected, suggestions);
// Test completions for $env
let suggestions = completer.complete("$env.T", 6);
assert_eq!(1, suggestions.len());
let expected: Vec<String> = vec!["TEST".into()];
// Match results
match_suggestions(expected, suggestions);
}

View File

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

View File

@ -1,7 +1,7 @@
use nu_ansi_term::{Color, Style};
use serde::Deserialize;
#[derive(Deserialize, PartialEq, Debug)]
#[derive(Deserialize, PartialEq, Eq, Debug)]
pub struct NuStyle {
pub fg: Option<String>,
pub bg: Option<String>,

View File

@ -4,29 +4,31 @@ description = "Nushell's built-in commands"
edition = "2021"
license = "MIT"
name = "nu-command"
version = "0.62.0"
version = "0.66.1"
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.62.0" }
nu-engine = { path = "../nu-engine", version = "0.62.0" }
nu-glob = { path = "../nu-glob", version = "0.62.0" }
nu-json = { path = "../nu-json", version = "0.62.0" }
nu-parser = { path = "../nu-parser", version = "0.62.0" }
nu-path = { path = "../nu-path", version = "0.62.0" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.62.0" }
nu-protocol = { path = "../nu-protocol", version = "0.62.0" }
nu-system = { path = "../nu-system", version = "0.62.0" }
nu-table = { path = "../nu-table", version = "0.62.0" }
nu-term-grid = { path = "../nu-term-grid", version = "0.62.0" }
nu-test-support = { path = "../nu-test-support", version = "0.62.0" }
nu-utils = { path = "../nu-utils", version = "0.62.0" }
nu-ansi-term = "0.45.1"
nu-color-config = { path = "../nu-color-config", version = "0.66.1" }
nu-engine = { path = "../nu-engine", version = "0.66.1" }
nu-glob = { path = "../nu-glob", version = "0.66.1" }
nu-json = { path = "../nu-json", version = "0.66.1" }
nu-parser = { path = "../nu-parser", version = "0.66.1" }
nu-path = { path = "../nu-path", version = "0.66.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.66.1" }
nu-protocol = { path = "../nu-protocol", version = "0.66.1" }
nu-system = { path = "../nu-system", version = "0.66.1" }
nu-table = { path = "../nu-table", version = "0.66.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.66.1" }
nu-test-support = { path = "../nu-test-support", version = "0.66.1" }
nu-utils = { path = "../nu-utils", version = "0.66.1" }
nu-ansi-term = "0.46.0"
# Potential dependencies for extras
alphanumeric-sort = "1.4.4"
base64 = "0.13.0"
byteorder = "1.4.3"
bytesize = "1.1.0"
calamine = "0.18.0"
chrono = { version = "0.4.19", features = ["serde"] }
@ -46,17 +48,19 @@ htmlescape = "0.3.1"
ical = "0.7.0"
indexmap = { version="1.7", features=["serde-1"] }
Inflector = "0.11"
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"
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"
@ -68,38 +72,50 @@ serde_ini = "0.2.0"
serde_urlencoded = "0.7.0"
serde_yaml = "0.8.16"
sha2 = "0.10.0"
shadow-rs = "0.11.0"
# Disable default features b/c the default features build Git (very slow to compile)
shadow-rs = { version = "0.16.1", default-features = false }
strip-ansi-escapes = "0.1.1"
sysinfo = "0.23.5"
terminal_size = "0.1.17"
thiserror = "1.0.29"
titlecase = "1.1.0"
sysinfo = "0.24.6"
terminal_size = "0.2.1"
thiserror = "1.0.31"
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.5.0", features = ["bashisms"]}
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 = "1.0.0"
umask = "2.0.0"
users = "0.11.0"
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies.trash]
version = "2.0.2"
version = "2.1.3"
optional = true
[dependencies.polars]
version = "0.20.0"
version = "0.22.8"
# path = "../../../../polars/polars"
optional = true
features = [
"default", "parquet", "json", "serde", "object",
"checked_arithmetic", "strings", "cum_agg", "is_in",
"default", "to_dummies", "parquet", "json", "serde", "serde-lazy",
"object", "checked_arithmetic", "strings", "cum_agg", "is_in",
"rolling_window", "strings", "rows", "random",
"dtype-datetime"
"dtype-datetime", "dtype-struct", "lazy", "cross_join",
"dynamic_groupby", "dtype-categorical", "concat_str"
]
[target.'cfg(windows)'.dependencies.windows]
version = "0.37.0"
features = [
"alloc",
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_SystemServices",
]
[features]
@ -110,10 +126,11 @@ dataframe = ["polars", "num"]
database = ["sqlparser", "rusqlite"]
[build-dependencies]
shadow-rs = "0.11.0"
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.15.0"

View File

@ -1,3 +1,18 @@
use std::process::Command;
fn main() -> shadow_rs::SdResult<()> {
// Look up the current Git commit ourselves instead of relying on shadow_rs,
// because shadow_rs does it in a really slow-to-compile way (it builds libgit2)
let hash = get_git_hash().expect("failed to get latest git commit hash");
println!("cargo:rustc-env=NU_COMMIT_HASH={}", hash);
shadow_rs::new()
}
fn get_git_hash() -> Result<String, std::io::Error> {
let out = Command::new("git").args(["rev-parse", "HEAD"]).output()?;
Ok(String::from_utf8(out.stdout)
.expect("could not convert stdout to string")
.trim()
.to_string())
}

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

@ -0,0 +1,98 @@
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};
#[derive(Clone)]
pub struct BytesLen;
struct Arguments {
column_paths: Option<Vec<CellPath>>,
}
impl BytesArgument for Arguments {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>> {
self.column_paths.take()
}
}
impl Command for BytesLen {
fn name(&self) -> &str {
"bytes length"
}
fn signature(&self) -> Signature {
Signature::build("bytes length")
.rest(
"rest",
SyntaxShape::CellPath,
"optionally find length of binary by column paths",
)
.category(Category::Bytes)
}
fn usage(&self) -> &str {
"Output the length of any bytes in the pipeline"
}
fn search_terms(&self) -> Vec<&str> {
vec!["len", "size", "count"]
}
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 arg = Arguments { column_paths };
operate(length, arg, input, call.head, engine_state.ctrlc.clone())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Return the lengths of multiple strings",
example: "0x[1F FF AA AB] | bytes length",
result: Some(Value::test_int(4)),
},
Example {
description: "Return the lengths of multiple strings",
example: "[0x[1F FF AA AB] 0x[1F]] | bytes length",
result: Some(Value::List {
vals: vec![Value::test_int(4), Value::test_int(1)],
span: Span::test_data(),
}),
},
]
}
}
fn length(input: &[u8], _arg: &Arguments, span: Span) -> Value {
Value::Int {
val: input.len() as i64,
span,
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesLen {})
}
}

View File

@ -0,0 +1,98 @@
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 {
fn take_column_paths(&mut self) -> Option<Vec<CellPath>>;
}
/// map input pipeline data, for each elements, if it's Binary, invoke relative `cmd` with `arg`.
fn operate<C, A>(
cmd: C,
mut arg: A,
input: PipelineData,
span: Span,
ctrlc: Option<Arc<AtomicBool>>,
) -> Result<PipelineData, ShellError>
where
A: BytesArgument + Send + Sync + 'static,
C: Fn(&[u8], &A, Span) -> Value + Send + Sync + 'static + Clone + Copy,
{
match arg.take_column_paths() {
None => input.map(
move |v| match v {
Value::Binary {
val,
span: val_span,
} => cmd(&val, &arg, val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
},
},
ctrlc,
),
Some(column_paths) => {
let arg = Arc::new(arg);
input.map(
move |mut v| {
for path in &column_paths {
let opt = arg.clone();
let r = v.update_cell_path(
&path.members,
Box::new(move |old| {
match old {
Value::Binary {val, span: val_span} => cmd(val, &opt, *val_span),
other => Value::Error {
error: ShellError::UnsupportedInput(
format!(
"Input's type is {}. This command only works with bytes.",
other.get_type()
),
span,
),
}}}),
);
if let Err(error) = r {
return Value::Error { error };
}
}
v
},
ctrlc,
)
}
}
}

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

@ -0,0 +1,122 @@
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 BytesStartsWith;
impl Command for BytesStartsWith {
fn name(&self) -> &str {
"bytes starts-with"
}
fn signature(&self) -> Signature {
Signature::build("bytes starts-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 starts 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(
starts_with,
arg,
input,
call.head,
engine_state.ctrlc.clone(),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Checks if binary starts with `0x[1F FF AA]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[1F]",
result: Some(Value::Bool {
val: true,
span: Span::test_data(),
}),
},
Example {
description: "Checks if binary starts with `0x[1F]`",
example: "0x[1F FF AA AA] | bytes starts-with 0x[11]",
result: Some(Value::Bool {
val: false,
span: Span::test_data(),
}),
},
]
}
}
fn starts_with(input: &[u8], Arguments { pattern, .. }: &Arguments, span: Span) -> Value {
Value::Bool {
val: input.starts_with(pattern),
span,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(BytesStartsWith {})
}
}

View File

@ -0,0 +1,317 @@
use chrono::{DateTime, FixedOffset};
use nu_protocol::{ShellError, Span, Value};
use std::hash::{Hash, Hasher};
/// A subset of [Value](crate::Value), which is hashable.
/// And it means that we can put the value into something like [HashMap](std::collections::HashMap) or [HashSet](std::collections::HashSet)
/// for further usage like value statistics.
///
/// For now the main way to crate a [HashableValue] is using [from_value](HashableValue::from_value)
///
/// Please note that although each variant contains `span` field, but during hashing, this field will not be concerned.
/// Which means that the following will be true:
/// ```text
/// assert_eq!(HashableValue::Bool {val: true, span: Span{start: 0, end: 1}}, HashableValue::Bool {val: true, span: Span{start: 90, end: 1000}})
/// ```
#[derive(Eq, Debug)]
pub enum HashableValue {
Bool {
val: bool,
span: Span,
},
Int {
val: i64,
span: Span,
},
Float {
val: [u8; 8], // because f64 is not hashable, we save it as [u8;8] array to make it hashable.
span: Span,
},
Filesize {
val: i64,
span: Span,
},
Duration {
val: i64,
span: Span,
},
Date {
val: DateTime<FixedOffset>,
span: Span,
},
String {
val: String,
span: Span,
},
Binary {
val: Vec<u8>,
span: Span,
},
}
impl Default for HashableValue {
fn default() -> Self {
HashableValue::Bool {
val: false,
span: Span { start: 0, end: 0 },
}
}
}
impl HashableValue {
/// Try to convert from `value` to self
///
/// A `span` is required because when there is an error in value, it may not contain `span` field.
///
/// If the given value is not hashable(mainly because of it is structured data), an error will returned.
pub fn from_value(value: Value, span: Span) -> Result<Self, ShellError> {
match value {
Value::Bool { val, span } => Ok(HashableValue::Bool { val, span }),
Value::Int { val, span } => Ok(HashableValue::Int { val, span }),
Value::Filesize { val, span } => Ok(HashableValue::Filesize { val, span }),
Value::Duration { val, span } => Ok(HashableValue::Duration { val, span }),
Value::Date { val, span } => Ok(HashableValue::Date { val, span }),
Value::Float { val, span } => Ok(HashableValue::Float {
val: val.to_ne_bytes(),
span,
}),
Value::String { val, span } => Ok(HashableValue::String { val, span }),
Value::Binary { val, span } => Ok(HashableValue::Binary { val, span }),
_ => {
let input_span = value.span().unwrap_or(span);
Err(ShellError::UnsupportedInput(
format!("input value {value:?} is not hashable"),
input_span,
))
}
}
}
/// Convert from self to nu's core data type `Value`.
pub fn into_value(self) -> Value {
match self {
HashableValue::Bool { val, span } => Value::Bool { val, span },
HashableValue::Int { val, span } => Value::Int { val, span },
HashableValue::Filesize { val, span } => Value::Filesize { val, span },
HashableValue::Duration { val, span } => Value::Duration { val, span },
HashableValue::Date { val, span } => Value::Date { val, span },
HashableValue::Float { val, span } => Value::Float {
val: f64::from_ne_bytes(val),
span,
},
HashableValue::String { val, span } => Value::String { val, span },
HashableValue::Binary { val, span } => Value::Binary { val, span },
}
}
}
impl Hash for HashableValue {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
HashableValue::Bool { val, .. } => val.hash(state),
HashableValue::Int { val, .. } => val.hash(state),
HashableValue::Filesize { val, .. } => val.hash(state),
HashableValue::Duration { val, .. } => val.hash(state),
HashableValue::Date { val, .. } => val.hash(state),
HashableValue::Float { val, .. } => val.hash(state),
HashableValue::String { val, .. } => val.hash(state),
HashableValue::Binary { val, .. } => val.hash(state),
}
}
}
impl PartialEq for HashableValue {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(HashableValue::Bool { val: lhs, .. }, HashableValue::Bool { val: rhs, .. }) => {
lhs == rhs
}
(HashableValue::Int { val: lhs, .. }, HashableValue::Int { val: rhs, .. }) => {
lhs == rhs
}
(
HashableValue::Filesize { val: lhs, .. },
HashableValue::Filesize { val: rhs, .. },
) => lhs == rhs,
(
HashableValue::Duration { val: lhs, .. },
HashableValue::Duration { val: rhs, .. },
) => lhs == rhs,
(HashableValue::Date { val: lhs, .. }, HashableValue::Date { val: rhs, .. }) => {
lhs == rhs
}
(HashableValue::Float { val: lhs, .. }, HashableValue::Float { val: rhs, .. }) => {
lhs == rhs
}
(HashableValue::String { val: lhs, .. }, HashableValue::String { val: rhs, .. }) => {
lhs == rhs
}
(HashableValue::Binary { val: lhs, .. }, HashableValue::Binary { val: rhs, .. }) => {
lhs == rhs
}
_ => false,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use nu_protocol::ast::{CellPath, PathMember};
use std::collections::{HashMap, HashSet};
#[test]
fn from_value() {
let span = Span::test_data();
let values = vec![
(
Value::Bool { val: true, span },
HashableValue::Bool { val: true, span },
),
(
Value::Int { val: 1, span },
HashableValue::Int { val: 1, span },
),
(
Value::Filesize { val: 1, span },
HashableValue::Filesize { val: 1, span },
),
(
Value::Duration { val: 1, span },
HashableValue::Duration { val: 1, span },
),
(
Value::Date {
val: DateTime::<FixedOffset>::parse_from_rfc2822(
"Wed, 18 Feb 2015 23:16:09 GMT",
)
.unwrap(),
span,
},
HashableValue::Date {
val: DateTime::<FixedOffset>::parse_from_rfc2822(
"Wed, 18 Feb 2015 23:16:09 GMT",
)
.unwrap(),
span,
},
),
(
Value::String {
val: "1".to_string(),
span,
},
HashableValue::String {
val: "1".to_string(),
span,
},
),
(
Value::Binary { val: vec![1], span },
HashableValue::Binary { val: vec![1], span },
),
];
for (val, expect_hashable_val) in values.into_iter() {
assert_eq!(
HashableValue::from_value(val, Span { start: 0, end: 0 }).unwrap(),
expect_hashable_val
);
}
}
#[test]
fn from_unhashable_value() {
let span = Span::test_data();
let values = [
Value::List {
vals: vec![Value::Bool { val: true, span }],
span,
},
Value::Block {
val: 0,
captures: HashMap::new(),
span,
},
Value::Nothing { span },
Value::Error {
error: ShellError::DidYouMean("what?".to_string(), span),
},
Value::CellPath {
val: CellPath {
members: vec![PathMember::Int { val: 0, span }],
},
span,
},
];
for v in values {
assert!(HashableValue::from_value(v, Span { start: 0, end: 0 }).is_err())
}
}
#[test]
fn from_to_tobe_same() {
let span = Span::test_data();
let values = vec![
Value::Bool { val: true, span },
Value::Int { val: 1, span },
Value::Filesize { val: 1, span },
Value::Duration { val: 1, span },
Value::String {
val: "1".to_string(),
span,
},
Value::Binary { val: vec![1], span },
];
for val in values.into_iter() {
let expected_val = val.clone();
assert_eq!(
HashableValue::from_value(val, Span { start: 0, end: 0 })
.unwrap()
.into_value(),
expected_val
);
}
}
#[test]
fn hashable_value_eq_without_concern_span() {
assert_eq!(
HashableValue::Bool {
val: true,
span: Span { start: 0, end: 1 }
},
HashableValue::Bool {
val: true,
span: Span {
start: 90,
end: 1000
}
}
)
}
#[test]
fn put_to_hashset() {
let span = Span::test_data();
let mut set = HashSet::new();
set.insert(HashableValue::Bool { val: true, span });
assert!(set.contains(&HashableValue::Bool { val: true, span }));
// hashable value doesn't care about span.
let diff_span = Span { start: 1, end: 2 };
set.insert(HashableValue::Bool {
val: true,
span: diff_span,
});
assert!(set.contains(&HashableValue::Bool { val: true, span }));
assert!(set.contains(&HashableValue::Bool {
val: true,
span: diff_span
}));
assert_eq!(set.len(), 1);
set.insert(HashableValue::Int { val: 2, span });
assert_eq!(set.len(), 2);
}
}

View File

@ -0,0 +1,256 @@
use super::hashable_value::HashableValue;
use nu_engine::CallExt;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape,
Value,
};
use std::collections::HashMap;
use std::iter;
#[derive(Clone)]
pub struct Histogram;
enum PercentageCalcMethod {
Normalize,
Relative,
}
impl Command for Histogram {
fn name(&self) -> &str {
"histogram"
}
fn signature(&self) -> Signature {
Signature::build("histogram")
.optional("column-name", SyntaxShape::String, "column name to calc frequency, no need to provide if input is just a list")
.optional("frequency-column-name", SyntaxShape::String, "histogram's frequency column, default to be frequency column output")
.named("percentage-type", SyntaxShape::String, "percentage calculate method, can be 'normalize' or 'relative', in 'normalize', defaults to be 'normalize'", Some('t'))
}
fn usage(&self) -> &str {
"Creates a new table with a histogram based on the column name passed in."
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get a histogram for the types of files",
example: "ls | histogram type",
result: None,
},
Example {
description:
"Get a histogram for the types of files, with frequency column named freq",
example: "ls | histogram type freq",
result: None,
},
Example {
description: "Get a histogram for a list of numbers",
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram",
result: None,
},
Example {
description: "Get a histogram for a list of numbers, and percentage is based on the maximum value",
example: "echo [1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative",
result: None,
}
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// input check.
let column_name: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
let frequency_name_arg = call.opt::<Spanned<String>>(engine_state, stack, 1)?;
let frequency_column_name = match frequency_name_arg {
Some(inner) => {
let span = inner.span;
if ["value", "count", "quantile", "percentage"].contains(&inner.item.as_str()) {
return Err(ShellError::UnsupportedInput(
"frequency-column-name can't be 'value', 'count' or 'percentage'"
.to_string(),
span,
));
}
inner.item
}
None => "frequency".to_string(),
};
let calc_method: Option<Spanned<String>> =
call.get_flag(engine_state, stack, "percentage-type")?;
let calc_method = match calc_method {
None => PercentageCalcMethod::Normalize,
Some(inner) => match inner.item.as_str() {
"normalize" => PercentageCalcMethod::Normalize,
"relative" => PercentageCalcMethod::Relative,
_ => {
return Err(ShellError::UnsupportedInput(
"calc method can only be 'normalize' or 'relative'".to_string(),
inner.span,
))
}
},
};
let span = call.head;
let data_as_value = input.into_value(span);
// `input` is not a list, here we can return an error.
match data_as_value.as_list() {
Ok(list_value) => run_histogram(
list_value.to_vec(),
column_name,
frequency_column_name,
calc_method,
span,
),
Err(e) => Err(e),
}
}
}
fn run_histogram(
values: Vec<Value>,
column_name: Option<Spanned<String>>,
freq_column: String,
calc_method: PercentageCalcMethod,
head_span: Span,
) -> Result<PipelineData, ShellError> {
let mut inputs = vec![];
// convert from inputs to hashable values.
match column_name {
None => {
// some invalid input scenario needs to handle:
// Expect input is a list of hashable value, if one value is not hashable, throw out error.
for v in values {
let current_span = v.span().unwrap_or(head_span);
inputs.push(HashableValue::from_value(v, head_span).map_err(|_| {
ShellError::UnsupportedInput(
"--column-name is not provided, can only support a list of simple value."
.to_string(),
current_span,
)
})?);
}
}
Some(ref col) => {
// some invalid input scenario needs to handle:
// * item in `input` is not a record, just skip it.
// * a record doesn't contain specific column, just skip it.
// * all records don't contain specific column, throw out error, indicate at least one row should contains specific column.
// * a record contain a value which can't be hashed, skip it.
let col_name = &col.item;
for v in values {
match v {
// parse record, and fill valid value to actual input.
Value::Record { cols, vals, .. } => {
for (c, v) in iter::zip(cols, vals) {
if &c == col_name {
if let Ok(v) = HashableValue::from_value(v, head_span) {
inputs.push(v);
}
}
}
}
_ => continue,
}
}
if inputs.is_empty() {
return Err(ShellError::UnsupportedInput(
format!("expect input is table, and inputs doesn't contain any value which has {col_name} column"),
head_span,
));
}
}
}
let value_column_name = column_name
.map(|x| x.item)
.unwrap_or_else(|| "value".to_string());
Ok(histogram_impl(
inputs,
&value_column_name,
calc_method,
&freq_column,
head_span,
))
}
fn histogram_impl(
inputs: Vec<HashableValue>,
value_column_name: &str,
calc_method: PercentageCalcMethod,
freq_column: &str,
span: Span,
) -> PipelineData {
// here we can make sure that inputs is not empty, and every elements
// is a simple val and ok to make count.
let mut counter = HashMap::new();
let mut max_cnt = 0;
let total_cnt = inputs.len();
for i in inputs {
let new_cnt = *counter.get(&i).unwrap_or(&0) + 1;
counter.insert(i, new_cnt);
if new_cnt > max_cnt {
max_cnt = new_cnt;
}
}
let mut result = vec![];
let result_cols = vec![
value_column_name.to_string(),
"count".to_string(),
"quantile".to_string(),
"percentage".to_string(),
freq_column.to_string(),
];
const MAX_FREQ_COUNT: f64 = 100.0;
for (val, count) in counter.into_iter() {
let quantile = match calc_method {
PercentageCalcMethod::Normalize => (count as f64 / total_cnt as f64),
PercentageCalcMethod::Relative => (count as f64 / max_cnt as f64),
};
let percentage = format!("{:.2}%", quantile * 100_f64);
let freq = "*".repeat((MAX_FREQ_COUNT * quantile).floor() as usize);
result.push(Value::Record {
cols: result_cols.clone(),
vals: vec![
val.into_value(),
Value::Int { val: count, span },
Value::Float {
val: quantile,
span,
},
Value::String {
val: percentage,
span,
},
Value::String { val: freq, span },
],
span,
});
}
Value::List { vals: result, span }.into_pipeline_data()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(Histogram)
}
}

View File

@ -0,0 +1,4 @@
mod hashable_value;
mod histogram;
pub use histogram::Histogram;

View File

@ -1,3 +1,4 @@
use crate::{generate_strftime_list, parse_date_from_string};
use chrono::{DateTime, FixedOffset, Local, TimeZone, Utc};
use nu_engine::CallExt;
use nu_protocol::ast::Call;
@ -7,9 +8,6 @@ use nu_protocol::{
Category, Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
};
use crate::generate_strftime_list;
use crate::parse_date_from_string;
struct Arguments {
timezone: Option<Spanned<String>>,
offset: Option<Spanned<i64>>,
@ -24,13 +22,13 @@ enum Zone {
Local,
East(u8),
West(u8),
Error, // we want the nullshell to cast it instead of rust
Error, // we want Nushell to cast it instead of Rust
}
impl Zone {
fn new(i: i64) -> Self {
if i.abs() <= 12 {
// guanranteed here
// guaranteed here
if i >= 0 {
Self::East(i as u8) // won't go out of range
} else {
@ -59,29 +57,29 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("into datetime")
.switch(
"list",
"lists strftime cheatsheet",
Some('l'),
)
.named(
"timezone",
SyntaxShape::String,
"Specify timezone if the input is timestamp, like 'UTC/u' or 'LOCAL/l'",
"Specify timezone if the input is a Unix timestamp. Valid options: 'UTC' ('u') or 'LOCAL' ('l')",
Some('z'),
)
.named(
"offset",
SyntaxShape::Int,
"Specify timezone by offset if the input is timestamp, like '+8', '-4', prior than timezone",
"Specify timezone by offset from UTC if the input is a Unix timestamp, like '+8', '-4'",
Some('o'),
)
.named(
"format",
SyntaxShape::String,
"Specify date and time formatting",
"Specify an expected format for parsing strings to datetimes. Use --list to see all possible options",
Some('f'),
)
.switch(
"list",
"Show all possible variables for use with the --format flag",
Some('l'),
)
.rest(
"rest",
SyntaxShape::CellPath,
@ -112,30 +110,51 @@ impl Command for SubCommand {
vec![
Example {
description: "Convert to datetime",
example: "'16.11.1984 8:00 am +0000' | into datetime",
result: None,
example: "'27.02.2021 1:55 pm +0000' | into datetime",
result: Some(Value::Date {
val: Utc.timestamp(1614434100, 0).into(),
span: Span::test_data(),
}),
},
Example {
description: "Convert to datetime",
example: "'2020-08-04T16:39:18+00:00' | into datetime",
result: None,
example: "'2021-02-27T13:55:40+00:00' | into datetime",
result: Some(Value::Date {
val: Utc.timestamp(1614434140, 0).into(),
span: Span::test_data(),
}),
},
Example {
description: "Convert to datetime using a custom format",
example: "'20200904_163918+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
result: None,
example: "'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z'",
result: Some(Value::Date {
val: Utc.timestamp(1614434140, 0).into(),
span: Span::test_data(),
}),
},
Example {
description: "Convert timestamp (no larger than 8e+12) to datetime using a specified timezone",
example: "'1614434140' | into datetime -z 'UTC'",
result: None,
description: "Convert timestamp (no larger than 8e+12) to a UTC datetime",
example: "1614434140 | into datetime",
result: Some(Value::Date {
val: Utc.timestamp(1614434140, 0).into(),
span: Span::test_data(),
}),
},
Example {
description:
"Convert timestamp (no larger than 8e+12) to datetime using a specified timezone offset (between -12 and 12)",
example: "'1614434140' | into datetime -o +9",
example: "1614434140 | into datetime -o +9",
result: None,
},
Example {
description:
"Convert timestamps like the sqlite history t",
example: "1656165681720 | into datetime",
result: Some(Value::Date {
val: Utc.timestamp_millis(1656165681720).into(),
span: Span::test_data(),
}),
},
]
}
}
@ -209,58 +228,87 @@ fn action(
dateformat: &Option<DatetimeFormat>,
head: Span,
) -> Value {
match input {
Value::String { val: s, span } => {
let ts = s.parse::<i64>();
// if timezone if specified, first check if the input is a timestamp.
if let Some(tz) = timezone {
const TIMESTAMP_BOUND: i64 = 8.2e+12 as i64;
// Since the timestamp method of chrono itself don't throw an error (it just panicked)
// We have to manually guard it.
if let Ok(t) = ts {
if t.abs() > TIMESTAMP_BOUND {
return Value::Error{error: ShellError::UnsupportedInput(
"Given timestamp is out of range, it should between -8e+12 and 8e+12".to_string(),
head,
)};
}
const HOUR: i32 = 3600;
let stampout = match tz.item {
Zone::Utc => Value::Date {
val: Utc.timestamp(t, 0).into(),
span: head,
},
Zone::Local => Value::Date {
val: Local.timestamp(t, 0).into(),
span: head,
},
Zone::East(i) => {
let eastoffset = FixedOffset::east((i as i32) * HOUR);
Value::Date {
val: eastoffset.timestamp(t, 0),
span: head,
}
}
Zone::West(i) => {
let westoffset = FixedOffset::west((i as i32) * HOUR);
Value::Date {
val: westoffset.timestamp(t, 0),
span: head,
}
}
Zone::Error => Value::Error {
error: ShellError::UnsupportedInput(
"Cannot convert given timezone or offset to timestamp".to_string(),
tz.span,
),
},
};
return stampout;
}
// Check to see if input looks like a Unix timestamp (i.e. can it be parsed to an int?)
let timestamp = match input {
Value::Int { val, .. } => Ok(*val),
Value::String { val, .. } => val.parse::<i64>(),
other => {
return Value::Error {
error: ShellError::UnsupportedInput(
format!("Expected string or int, got {} instead", other.get_type()),
head,
),
};
// if it's not, continue and default to the system's local timezone.
let out = match dateformat {
Some(dt) => match DateTime::parse_from_str(s, &dt.0) {
}
};
if let Ok(ts) = timestamp {
const TIMESTAMP_BOUND: i64 = 8.2e+12 as i64;
const HOUR: i32 = 3600;
if ts.abs() > TIMESTAMP_BOUND {
return Value::Error {
error: ShellError::UnsupportedInput(
"Given timestamp is out of range, it should between -8e+12 and 8e+12"
.to_string(),
head,
),
};
}
return match timezone {
// default to UTC
None => {
// be able to convert chrono::Utc::now()
let dt = match ts.to_string().len() {
x if x > 13 => Utc.timestamp_nanos(ts).into(),
x if x > 10 => Utc.timestamp_millis(ts).into(),
_ => Utc.timestamp(ts, 0).into(),
};
Value::Date {
val: dt,
span: head,
}
}
Some(Spanned { item, span }) => match item {
Zone::Utc => Value::Date {
val: Utc.timestamp(ts, 0).into(),
span: head,
},
Zone::Local => Value::Date {
val: Local.timestamp(ts, 0).into(),
span: head,
},
Zone::East(i) => {
let eastoffset = FixedOffset::east((*i as i32) * HOUR);
Value::Date {
val: eastoffset.timestamp(ts, 0),
span: head,
}
}
Zone::West(i) => {
let westoffset = FixedOffset::west((*i as i32) * HOUR);
Value::Date {
val: westoffset.timestamp(ts, 0),
span: head,
}
}
Zone::Error => Value::Error {
error: ShellError::UnsupportedInput(
"Cannot convert given timezone or offset to timestamp".to_string(),
*span,
),
},
},
};
}
// If input is not a timestamp, try parsing it as a string
match input {
Value::String { val, span } => {
match dateformat {
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
Ok(d) => Value::Date { val: d, span: head },
Err(reason) => {
return Value::Error {
@ -276,23 +324,21 @@ fn action(
// Tries to automatically parse the date
// (i.e. without a format string)
// and assumes the system's local timezone if none is specified
None => match parse_date_from_string(s, *span) {
None => match parse_date_from_string(val, *span) {
Ok(date) => Value::Date {
val: date,
span: *span,
},
Err(err) => err,
},
};
out
}
other => {
let got = format!("Expected string, got {} instead", other.get_type());
Value::Error {
error: ShellError::UnsupportedInput(got, head),
}
}
other => Value::Error {
error: ShellError::UnsupportedInput(
format!("Expected string, got {} instead", other.get_type()),
head,
),
},
}
}
@ -351,6 +397,23 @@ mod tests {
assert_eq!(actual, expected)
}
#[test]
fn takes_timestamp_offset_as_int() {
let date_int = Value::test_int(1614434140);
let timezone_option = Some(Spanned {
item: Zone::East(8),
span: Span::test_data(),
});
let actual = action(&date_int, &timezone_option, &None, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
.unwrap(),
span: Span::test_data(),
};
assert_eq!(actual, expected)
}
#[test]
fn takes_timestamp() {
let date_str = Value::test_string("1614434140");
@ -367,6 +430,20 @@ mod tests {
assert_eq!(actual, expected)
}
#[test]
fn takes_timestamp_without_timezone() {
let date_str = Value::test_string("1614434140");
let timezone_option = None;
let actual = action(&date_str, &timezone_option, &None, Span::test_data());
let expected = Value::Date {
val: Utc.timestamp(1614434140, 0).into(),
span: Span::test_data(),
};
assert_eq!(actual, expected)
}
#[test]
fn takes_invalid_timestamp() {
let date_str = Value::test_string("10440970000000");

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

@ -133,6 +133,10 @@ pub fn action(input: &Value, span: Span) -> Value {
},
Err(error) => Value::Error { error },
},
Value::Nothing { .. } => Value::Filesize {
val: 0,
span: value_span,
},
_ => Value::Error {
error: ShellError::UnsupportedInput(
"'into filesize' for unsupported type".into(),

View File

@ -8,6 +8,7 @@ use nu_protocol::{
struct Arguments {
radix: Option<Value>,
column_paths: Vec<CellPath>,
little_endian: bool,
}
#[derive(Clone)]
@ -21,6 +22,7 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("into int")
.named("radix", SyntaxShape::Number, "radix of integer", Some('r'))
.switch("little-endian", "use little-endian byte decoding", None)
.rest(
"rest",
SyntaxShape::CellPath,
@ -85,6 +87,11 @@ impl Command for SubCommand {
span: Span::test_data(),
}),
},
Example {
description: "Convert date to integer (Unix timestamp)",
example: "2022-02-02 | into int",
result: Some(Value::test_int(1643760000)),
},
Example {
description: "Convert to integer from binary",
example: "'1101' | into int -r 2",
@ -95,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)),
},
]
}
}
@ -109,6 +131,7 @@ fn into_int(
let options = Arguments {
radix: call.get_flag(engine_state, stack, "radix")?,
little_endian: call.has_flag("little-endian"),
column_paths: call.rest(engine_state, stack, 0)?,
};
@ -130,13 +153,13 @@ fn into_int(
input.map(
move |v| {
if options.column_paths.is_empty() {
action(&v, head, radix)
action(&v, head, radix, options.little_endian)
} else {
let mut ret = v;
for path in &options.column_paths {
let r = ret.update_cell_path(
&path.members,
Box::new(move |old| action(old, head, radix)),
Box::new(move |old| action(old, head, radix, options.little_endian)),
);
if let Err(error) = r {
return Value::Error { error };
@ -150,7 +173,7 @@ fn into_int(
)
}
pub fn action(input: &Value, span: Span, radix: u32) -> Value {
pub fn action(input: &Value, span: Span, radix: u32, little_endian: bool) -> Value {
match input {
Value::Int { val: _, .. } => {
if radix == 10 {
@ -161,7 +184,32 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
}
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, .. } => {
@ -181,8 +229,42 @@ pub fn action(input: &Value, span: Span, radix: u32) -> Value {
Value::Int { val: 0, span }
}
}
Value::Date { val, .. } => Value::Int {
val: val.timestamp(),
span,
},
Value::Binary { val, span } => {
use byteorder::{BigEndian, ByteOrder, LittleEndian};
let mut val = val.to_vec();
if little_endian {
while val.len() < 8 {
val.push(0);
}
val.resize(8, 0);
Value::Int {
val: LittleEndian::read_i64(&val),
span: *span,
}
} else {
while val.len() < 8 {
val.insert(0, 0);
}
val.resize(8, 0);
Value::Int {
val: BigEndian::read_i64(&val),
span: *span,
}
}
}
_ => Value::Error {
error: ShellError::UnsupportedInput("'into int' for unsupported type".into(), span),
error: ShellError::UnsupportedInput(
format!("'into int' for unsupported type '{}'", input.get_type()),
span,
),
},
}
}
@ -191,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()
}
@ -208,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),
},
}
}
@ -249,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),
@ -257,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
)),
)),
},
},
@ -282,21 +401,21 @@ mod test {
let word = Value::test_string("10");
let expected = Value::test_int(10);
let actual = action(&word, Span::test_data(), 10);
let actual = action(&word, Span::test_data(), 10, false);
assert_eq!(actual, expected);
}
#[test]
fn turns_binary_to_integer() {
let s = Value::test_string("0b101");
let actual = action(&s, Span::test_data(), 10);
let actual = action(&s, Span::test_data(), 10, false);
assert_eq!(actual, Value::test_int(5));
}
#[test]
fn turns_hex_to_integer() {
let s = Value::test_string("0xFF");
let actual = action(&s, Span::test_data(), 16);
let actual = action(&s, Span::test_data(), 16, false);
assert_eq!(actual, Value::test_int(255));
}
@ -304,7 +423,7 @@ mod test {
fn communicates_parsing_error_given_an_invalid_integerlike_string() {
let integer_str = Value::test_string("36anra");
let actual = action(&integer_str, Span::test_data(), 10);
let actual = action(&integer_str, Span::test_data(), 10, false);
assert_eq!(actual.get_type(), Error)
}

View File

@ -2,8 +2,8 @@ use nu_engine::CallExt;
use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span,
SyntaxShape, Value,
into_code, Category, Config, Example, IntoPipelineData, PipelineData, ShellError, Signature,
Span, SyntaxShape, Value,
};
// TODO num_format::SystemLocale once platform-specific dependencies are stable (see Cargo.toml)
@ -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()
};
@ -247,8 +264,17 @@ pub fn action(
val: input.into_string(", ", config),
span,
},
Value::Error { error } => Value::String {
val: {
match into_code(error) {
Some(code) => code,
None => "".to_string(),
}
},
span,
},
Value::Nothing { .. } => Value::String {
val: "nothing".to_string(),
val: "".to_string(),
span,
},
Value::Record {

View File

@ -26,14 +26,18 @@ impl Command for Alias {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {
true
}
fn search_terms(&self) -> Vec<&str> {
vec!["abbr", "aka", "fn", "func", "function"]
}
fn run(
&self,
_engine_state: &EngineState,

View File

@ -27,8 +27,8 @@ impl Command for Def {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -27,8 +27,34 @@ impl Command for DefEnv {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html
=== EXTRA NOTE ===
All blocks are scoped, including variable definition and environment variable changes.
Because of this, the following doesn't work:
def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
if $arg != "" {
cd $arg
} else {
cd $fall_back_path
}
}
Instead, you have to use cd in the top level scope:
def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
let path = if $arg != "" {
$arg
} else {
$fall_back_path
}
cd $path
}"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -13,7 +13,7 @@ impl Command for Describe {
}
fn usage(&self) -> &str {
"Describe the value(s) piped in."
"Describe the type and structure of the value(s) piped in."
}
fn signature(&self) -> Signature {
@ -55,6 +55,10 @@ impl Command for Describe {
result: Some(Value::test_string("string")),
}]
}
fn search_terms(&self) -> Vec<&str> {
vec!["type", "typeof", "info", "structure"]
}
}
#[cfg(test)]

View File

@ -1,7 +1,9 @@
use nu_engine::{eval_block, CallExt};
use nu_protocol::ast::Call;
use nu_protocol::engine::{CaptureBlock, Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, Signature, SyntaxShape, Value};
use nu_protocol::{
Category, Example, IntoPipelineData, PipelineData, Signature, SyntaxShape, Value,
};
#[derive(Clone)]
pub struct Do;
@ -23,6 +25,11 @@ impl Command for Do {
"ignore errors as the block runs",
Some('i'),
)
.switch(
"capture-errors",
"capture errors as the block runs and return it",
Some('c'),
)
.rest("rest", SyntaxShape::Any, "the parameter(s) for the block")
.category(Category::Core)
}
@ -37,6 +44,7 @@ impl Command for Do {
let block: CaptureBlock = call.req(engine_state, stack, 0)?;
let rest: Vec<Value> = call.rest(engine_state, stack, 1)?;
let ignore_errors = call.has_flag("ignore-errors");
let capture_errors = call.has_flag("capture-errors");
let mut stack = stack.captures_to_stack(&block.captures);
let block = engine_state.get_block(block.block_id);
@ -85,7 +93,7 @@ impl Command for Do {
block,
input,
call.redirect_stdout,
ignore_errors,
ignore_errors || capture_errors,
);
if ignore_errors {
@ -93,6 +101,11 @@ impl Command for Do {
Ok(x) => Ok(x),
Err(_) => Ok(PipelineData::new(call.head)),
}
} else if capture_errors {
match result {
Ok(x) => Ok(x),
Err(err) => Ok((Value::Error { error: err }).into_pipeline_data()),
}
} else {
result
}

View File

@ -23,6 +23,10 @@ impl Command for Echo {
.category(Category::Core)
}
fn extra_usage(&self) -> &str {
"Unlike `print`, this command returns an actual value that will be passed to the next command of the pipeline."
}
fn run(
&self,
engine_state: &EngineState,

View File

@ -2,8 +2,7 @@ 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,
Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Value,
};
#[derive(Clone)]
@ -16,7 +15,12 @@ impl Command for ErrorMake {
fn signature(&self) -> Signature {
Signature::build("error make")
.optional("error_struct", SyntaxShape::Record, "the error to create")
.required("error_struct", SyntaxShape::Record, "the error to create")
.switch(
"unspanned",
"remove the origin label from the error",
Some('u'),
)
.category(Category::Core)
}
@ -24,47 +28,41 @@ impl Command for ErrorMake {
"Create an error."
}
fn search_terms(&self) -> Vec<&str> {
vec!["err", "panic", "crash", "throw"]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head;
let ctrlc = engine_state.ctrlc.clone();
let arg: Option<Value> = call.opt(engine_state, stack, 0)?;
let arg: Value = call.req(engine_state, stack, 0)?;
let unspanned = call.has_flag("unspanned");
if let Some(arg) = arg {
Ok(make_error(&arg)
.map(|err| Value::Error { error: err })
.unwrap_or_else(|| Value::Error {
error: ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
),
})
.into_pipeline_data())
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 {
input.map(
move |value| {
make_error(&value)
.map(|err| Value::Error { error: err })
.unwrap_or_else(|| Value::Error {
error: ShellError::GenericError(
"Creating error value not supported.".into(),
"unsupported error format".into(),
Some(span),
None,
Vec::new(),
),
})
},
ctrlc,
)
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(),
)
}))
}
}
@ -89,7 +87,7 @@ impl Command for ErrorMake {
}
}
fn make_error(value: &Value) -> 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");
@ -117,13 +115,26 @@ fn make_error(value: &Value) -> Option<ShellError> {
None,
Vec::new(),
)),
(
None,
None,
Some(Value::String {
val: label_text, ..
}),
) => Some(ShellError::GenericError(
message,
label_text,
throw_span,
None,
Vec::new(),
)),
_ => None,
}
}
(Some(Value::String { val: message, .. }), None) => Some(ShellError::GenericError(
message,
"".to_string(),
None,
"originates from here".to_string(),
throw_span,
None,
Vec::new(),
)),

View File

@ -22,8 +22,8 @@ impl Command for ExportCommand {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -26,8 +26,8 @@ impl Command for ExportAlias {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -27,8 +27,8 @@ impl Command for ExportDef {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -27,8 +27,34 @@ impl Command for ExportDefEnv {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html
=== EXTRA NOTE ===
All blocks are scoped, including variable definition and environment variable changes.
Because of this, the following doesn't work:
export def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
if $arg != "" {
cd $arg
} else {
cd $fall_back_path
}
}
Instead, you have to use cd in the top level scope:
export def-env cd_with_fallback [arg = ""] {
let fall_back_path = "/tmp"
let path = if $arg != "" {
$arg
} else {
$fall_back_path
}
cd $path
}"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -30,8 +30,8 @@ impl Command for ExportEnv {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -22,8 +22,8 @@ impl Command for ExportExtern {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -22,8 +22,8 @@ impl Command for Extern {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

View File

@ -45,8 +45,8 @@ impl Command for For {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {

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

@ -25,9 +25,8 @@ impl Command for Hide {
fn extra_usage(&self) -> &str {
r#"Symbols are hidden by priority: First aliases, then custom commands, then environment variables.
This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages
"#
This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {
@ -63,23 +62,23 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
return Err(ShellError::NonUtf8(import_pattern.head.span));
};
if let Some(overlay_id) = engine_state.find_overlay(&import_pattern.head.name) {
if let Some(module_id) = engine_state.find_module(&import_pattern.head.name, &[]) {
// The first word is a module
let overlay = engine_state.get_overlay(overlay_id);
let module = engine_state.get_module(module_id);
let env_vars_to_hide = if import_pattern.members.is_empty() {
overlay.env_vars_with_head(&import_pattern.head.name)
module.env_vars_with_head(&import_pattern.head.name)
} else {
match &import_pattern.members[0] {
ImportPatternMember::Glob { .. } => overlay.env_vars(),
ImportPatternMember::Glob { .. } => module.env_vars(),
ImportPatternMember::Name { name, span } => {
let mut output = vec![];
if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name)
module.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !(overlay.has_alias(name) || overlay.has_decl(name)) {
} else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,
@ -93,10 +92,10 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
for (name, span) in names {
if let Some((name, id)) =
overlay.env_var_with_head(name, &import_pattern.head.name)
module.env_var_with_head(name, &import_pattern.head.name)
{
output.push((name, id));
} else if !(overlay.has_alias(name) || overlay.has_decl(name)) {
} else if !(module.has_alias(name) || module.has_decl(name)) {
return Err(ShellError::EnvVarNotFoundAtRuntime(
String::from_utf8_lossy(name).into(),
*span,

View File

@ -1,102 +0,0 @@
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Value,
};
const NEWLINE_ESCAPE_CODE: &str = "<\\n>";
fn decode_newlines(escaped: &str) -> String {
escaped.replace(NEWLINE_ESCAPE_CODE, "\n")
}
#[derive(Clone)]
pub struct History;
impl Command for History {
fn name(&self) -> &str {
"history"
}
fn usage(&self) -> &str {
"Get the command history"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("history")
.switch("clear", "Clears out the history entries", Some('c'))
.category(Category::Core)
}
fn run(
&self,
engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let head = call.head;
if let Some(config_path) = nu_path::config_dir() {
let clear = call.has_flag("clear");
let ctrlc = engine_state.ctrlc.clone();
let mut history_path = config_path;
history_path.push("nushell");
history_path.push("history.txt");
if clear {
let _ = std::fs::remove_file(history_path);
Ok(PipelineData::new(head))
} else {
let contents = std::fs::read_to_string(history_path);
if let Ok(contents) = contents {
Ok(contents
.lines()
.enumerate()
.map(move |(index, command)| Value::Record {
cols: vec!["command".to_string(), "index".to_string()],
vals: vec![
Value::String {
val: decode_newlines(command),
span: head,
},
Value::Int {
val: index as i64,
span: head,
},
],
span: head,
})
.collect::<Vec<_>>()
.into_iter()
.into_pipeline_data(ctrlc))
} else {
Err(ShellError::FileNotFound(head))
}
}
} else {
Err(ShellError::FileNotFound(head))
}
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
example: "history | length",
description: "Get current history length",
result: None,
},
Example {
example: "history | last 5",
description: "Show last 5 commands you have ran",
result: None,
},
Example {
example: "history | wrap cmd | where cmd =~ cargo",
description: "Search all the commands from history that contains 'cargo'",
result: None,
},
]
}
}

View File

@ -34,8 +34,8 @@ impl Command for If {
}
fn extra_usage(&self) -> &str {
r#"This command is a parser keyword. For details, check
https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-different-stages"#
r#"This command is a parser keyword. For details, check:
https://www.nushell.sh/book/thinking_in_nushell.html"#
}
fn is_parser_keyword(&self) -> bool {
@ -92,6 +92,7 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
call.redirect_stdout,
call.redirect_stderr,
)
.map(|res| res.0)
}
} else {
eval_expression_with_input(
@ -102,6 +103,7 @@ https://www.nushell.sh/book/thinking_in_nushell.html#parsing-and-evaluation-are-
call.redirect_stdout,
call.redirect_stderr,
)
.map(|res| res.0)
}
} else {
Ok(PipelineData::new(call.head))

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