Compare commits

...

39 Commits

Author SHA1 Message Date
22ae962b57 Bump to 0.28 (#3149) 2021-03-09 23:40:17 +13:00
864139d67f move bel and backspace to char since they're not ansi (#3144)
* move bel and backspace to char since they're not ansi

* Trigger Build
2021-03-09 22:34:51 +13:00
Tw
1dc7e00d20 Fix trash functionality (#3146)
For now the trash doesn't work because the trash-support flag isn't enabled in nu-engine
crate, so make it work by adding this flag.

Signed-off-by: Tw <wei.tan@intel.com>

Co-authored-by: Tw <wei.tan@intel.com>
2021-03-09 22:34:14 +13:00
49a9107e0f Allow composing help message from two parts (#3124)
* Split help message into brief and full help

Demonstrate on ansi command

Brief help is printed when running `help commands` so it doesn't clutter
the table. Full help is printed when normal help message is requested
(e.g., `help ansi`, `ansi --help`, etc.).

* Split long command descriptions

Some are not split, just edited to be shorter.

* Capitalize the usage of all commands

* Make sure every usage ends with dot

* Fix random typo
2021-03-08 12:57:58 +13:00
7b8c2c232f fix: deadlock when printing errors (#3140)
Co-authored-by: hk <alexhaka10@protonmail.com>
2021-03-08 12:08:37 +13:00
15e1e6376b remove warnings (#3137) 2021-03-06 14:31:22 -06:00
06d9d9ed08 use minus v3.3.0 (#3136) 2021-03-07 06:47:01 +13:00
74e10d6f72 print string returned by draw_table, in autoview when pivot mode is on (#3135) 2021-03-06 10:17:37 -05:00
d43489a6a0 Add exit code argument (#3132) 2021-03-06 18:46:27 +13:00
983de8974b hopefully fixes the coercion error when comparing $nothing to $var (#3133) 2021-03-05 14:07:54 -06:00
c91a1ec08d Table paging release (#3128)
* use the InputHandler functionality from minus

* respond to Q and ESC character to quit

* use arijit79/minus main branch until new release is pushed

* rename NushellMinusInputHandler to MinusInputHandler
2021-03-05 10:32:16 +13:00
507de45d40 Revert "add config: prompt_color_enabled = true (#3115)" (#3127)
This reverts commit fe0fc8d5e1.
2021-03-04 12:22:14 -05:00
fe0fc8d5e1 add config: prompt_color_enabled = true (#3115) 2021-03-04 20:08:26 +13:00
e4a8db56f9 use add_exit_callback, update to rezural/nushell which contains add_exit_callback, and contains updated keybindings (#3121) 2021-03-04 20:06:22 +13:00
1d1ec4727a Refactor arguments of path subcommands & Add path join subcommand (#3123)
* Refactor path subcommand argument handling

DefaultArguments are no longer passed to each subcommand. Instead, each
subcommand has its own Path<xxx>Arguments. This means that it is no
longer necessary to edit every single path subcommand source file when
changing the arguments struct.

* Add new path join subcommand

Makes it easier to create new paths. It's just a wrapper around Rust's
Path.join().
2021-03-04 20:04:56 +13:00
0b71e45072 Preserve order when serializing/deserialize json by default. (#3126) 2021-03-04 01:35:13 -05:00
9c375b33a6 updated fetch to surf2.2 and feature h1-client-rustls (#3120) 2021-03-04 07:18:11 +13:00
28a6a5ea57 Add option to invert match command selection (#3114)
* Add option to invert match command selection

* Fix rustfmt error

* Rename match --exclude to --invert

To be more descriptive and conform to e.g. grep or ripgrep -v flag.
Also simplified the --invert flag description.

* Fix formatting when description got shorter

Co-authored-by: Jakub Žádník <jakub.zadnik@tuni.fi>
2021-03-02 06:48:22 +13:00
f83ff0e47d Use writer from host instead of always std::err (#3112) 2021-03-01 15:00:40 +13:00
079e575cac Table paging (Draft PR) (#3058)
* This adds table paging, relying on minus to perform the paging functionality
This is gated behind the table-pager feature

* fix problem with long running InputStreams blocking table() returning

* some comments regarding Arc clones, and callback from minus
2021-03-01 14:59:33 +13:00
6b2327f231 help generate_docs | flatten crashes nushell (#3099)
* fix case where parent_name was {nu, term} and possibly others in the future by doing an extra test first to see if if the *parent_name key actually exists in cmap

* update with help generate_docs testing
2021-02-27 09:05:22 +13:00
596608aa0c nu_plugin_match: accept -i -m -s flags (#3111) 2021-02-27 07:41:22 +13:00
120e80d1b6 refactor parse_math_expression, reduce indentation (#3093) 2021-02-26 18:11:20 +13:00
aa6c6120f6 Bump to 0.27.2 (#3109)
* Bump to 0.27.2

* Fix clippy and test
2021-02-26 17:55:25 +13:00
19d5f782cc Allow dropping columns. (#3107)
`drop` is used for removing the last row. Passing a number allows dropping N rows.
Here we introduce the same logic for dropping columns instead.

You can certainly remove columns by using `reject`, however, there could be cases
where we are interested in removing columns from tables that contain, say, a big
number of columns. Using `reject` becomes impractical, especially when you don't
care about the column names that could either be known or not known when exploring
tables.

```
> echo [[lib, extension]; [nu-core, rs] [rake, rb]]
─────────┬───────────
   lib   │ extension
─────────┼───────────
 nu-core │ rs
 rake    │ rb
─────────┴───────────
```

```
> echo [[lib, extension]; [nu-core, rs] [rake, rb]] | drop column
─────────
   lib
─────────
 nu-core
 rake
─────────
```
2021-02-25 15:37:21 -05:00
84169a91ff update readme (#3106)
added commit activity
added contributors
removed gitpod
2021-02-25 07:50:42 -06:00
d1c48cdcf9 update azure ci badge from master to main (#3105) 2021-02-25 07:36:09 -06:00
dfe95d3ae6 enabled the easy access use of nu-ansi-term's "Light" colors (#3100) 2021-02-24 15:36:22 -06:00
57ebec385f add ansi strip subcommand (#3095)
* add ansi subcommand

* changed example test, added additional test
2021-02-23 14:16:13 -06:00
7a77910720 Table content rolling. (#3097)
There are many use cases. Here we introduce the following:

- The rows can be rolled `... | roll` (up) or `... | roll down`
- Columns can be rolled too (the default is on the `left`, you can pass `... | roll column --opposite` to roll in the other direction)
- You can `roll` the cells of a table and keeping the header names in the same order (`... | roll column --cells-only`)
- Above examples can also be passed (Ex. `... | roll down 3`) a number to tell how many places to roll.

Basic working example with rolling columns:

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| rename bit1 bit2 bit3 bit4 bit5 bit6 bit7 bit8

───┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────
 # │ bit1 │ bit2 │ bit3 │ bit4 │ bit5 │ bit6 │ bit7 │ bit8
───┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────
 0 │    0 │    0 │    0 │    0 │    0 │    1 │    0 │    0
───┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────
```

We want to "shift" three bits to the left of the bitstring (four in decimal), let's try it:

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| rename bit1 bit2 bit3 bit4 bit5 bit6 bit7 bit8
| roll column 3

───┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────
 # │ bit4 │ bit5 │ bit6 │ bit7 │ bit8 │ bit1 │ bit2 │ bit3
───┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────
 0 │    0 │    0 │    1 │    0 │    0 │    0 │    0 │    0
───┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────
```

The tables was rolled correctly (32 in decimal, for above bitstring). However, the *last three header names* look confusing.
We can roll the cell contents only to fix it.

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| rename bit1 bit2 bit3 bit4 bit5 bit6 bit7 bit8
| roll column 3 --cells-only

───┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────
 # │ bit1 │ bit2 │ bit3 │ bit4 │ bit5 │ bit6 │ bit7 │ bit8
───┼──────┼──────┼──────┼──────┼──────┼──────┼──────┼──────
 0 │    0 │    0 │    1 │    0 │    0 │    0 │    0 │    0
───┴──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────
```

There we go. Let's compute it's decimal value now (should be 32)

```
> echo '00000100'
| split chars
| each { str to-int }
| rotate counter-clockwise _
| reject _
| roll column 3 --cells-only
| pivot bit --ignore-titles
| get bit
| reverse
| each --numbered { = $it.item * (2 ** $it.index) }
| math sum

32
```
2021-02-23 13:29:07 -05:00
23d8dc959c return string from draw_table instead of printing directly (#3088) 2021-02-23 22:25:49 +13:00
7f303a856e Make sure CurDir is filtered out in absolutize. (#3084)
* Make sure `CurDir` is filtered out in absolutize.

Closes #3083

* Add test

* Make sure test works on windows
2021-02-23 22:22:17 +13:00
e834e617f3 Remove parking_lot crate reference from nu-data (#3091)
* remove parking_lot crate from nu-data as it is no longer being used

* remove commented out code from parse.rs

* remove commented out code from scope.rs
2021-02-23 22:21:31 +13:00
2c89a228d5 add nu-ansi-term (#3089) 2021-02-22 12:33:34 -06:00
803826cdcd 90 degree table rotations (clockwise and counter-clockwise) (#3086)
Also for 180 degree is expected. Rotation is not exactly like pivoting (transposing)
for instance, given the following table:

```
> echo [[col1, col2, col3]; [cell1, cell2, cell3] [cell4, cell5, cell6]]
───┬───────┬───────┬───────
 # │ col1  │ col2  │ col3
───┼───────┼───────┼───────
 0 │ cell1 │ cell2 │ cell3
 1 │ cell4 │ cell5 │ cell6
───┴───────┴───────┴───────
```

To rotate it counter clockwise by 90 degrees, we can resort to first transposing (`pivot`)
them adding a new column (preferably integers), sort by that column from highest to lowest,
then remove the column and we have a counter clockwise rotation.

```
> echo [[col1, col2, col3]; [cell1, cell2, cell3] [cell4, cell5, cell6]] | pivot | each --numbered { = $it.item | insert idx $it.index } | sort-by idx | reverse | reject idx
───┬─────────┬─────────┬─────────
 # │ Column0 │ Column1 │ Column2
───┼─────────┼─────────┼─────────
 0 │ col3    │ cell3   │ cell6
 1 │ col2    │ cell2   │ cell5
 2 │ col1    │ cell1   │ cell4
───┴─────────┴─────────┴─────────
```

Which we can get easily, in this case, by doing:

```
> echo [[col1, col2, cel3]; [cell1, cell2, cell3] [cell4, cell5, cell6]] | rotate counter-clockwise
───┬─────────┬─────────┬─────────
 # │ Column0 │ Column1 │ Column2
───┼─────────┼─────────┼─────────
 0 │ col3    │ cell3   │ cell6
 1 │ col2    │ cell2   │ cell5
 2 │ col1    │ cell1   │ cell4
───┴─────────┴─────────┴─────────
```

There are also many powerful use cases with rotation, it makes a breeze creating tables with many columns, say:

```
echo 0..12 | rotate counter-clockwise | reject Column0
───┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬──────────┬──────────┬──────────┬──────────
 # │ Column1 │ Column2 │ Column3 │ Column4 │ Column5 │ Column6 │ Column7 │ Column8 │ Column9 │ Column10 │ Column11 │ Column12 │ Column13
───┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼──────────┼──────────┼──────────┼──────────
 0 │       0 │       1 │       2 │       3 │       4 │       5 │       6 │       7 │       8 │        9 │       10 │       11 │       12
───┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴──────────┴──────────┴──────────┴──────────
```
2021-02-22 06:56:34 -05:00
42d18d2294 add "-0" as short for --headerless in "from" commands (#3042)
* replace --headerless flags with --noheaders / -n

* Update from_csv.rs

Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
2021-02-22 20:25:17 +13:00
b5ae024cc8 add the ability to time commands (#3081) 2021-02-20 07:37:14 -06:00
5968811441 Set skip_welcome_message to false by default and add note (#3069) 2021-02-19 21:41:11 +13:00
fc59c87606 this example now runs out of the box instead of failing with no value or the wrong value (#3067) 2021-02-19 21:40:53 +13:00
297 changed files with 7995 additions and 2274 deletions

774
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@ license = "MIT"
name = "nu"
readme = "README.md"
repository = "https://github.com/nushell/nushell"
version = "0.27.1"
version = "0.28.0"
[workspace]
members = ["crates/*/"]
@ -18,35 +18,35 @@ members = ["crates/*/"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
nu-cli = { version = "0.27.1", path = "./crates/nu-cli", default-features = false }
nu-command = { version = "0.27.1", path = "./crates/nu-command" }
nu-data = { version = "0.27.1", path = "./crates/nu-data" }
nu-engine = { version = "0.27.1", path = "./crates/nu-engine" }
nu-errors = { version = "0.27.1", path = "./crates/nu-errors" }
nu-parser = { version = "0.27.1", path = "./crates/nu-parser" }
nu-plugin = { version = "0.27.1", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.27.1", path = "./crates/nu-protocol" }
nu-source = { version = "0.27.1", path = "./crates/nu-source" }
nu-value-ext = { version = "0.27.1", path = "./crates/nu-value-ext" }
nu-cli = { version = "0.28.0", path = "./crates/nu-cli", default-features = false }
nu-command = { version = "0.28.0", path = "./crates/nu-command" }
nu-data = { version = "0.28.0", path = "./crates/nu-data" }
nu-engine = { version = "0.28.0", path = "./crates/nu-engine" }
nu-errors = { version = "0.28.0", path = "./crates/nu-errors" }
nu-parser = { version = "0.28.0", path = "./crates/nu-parser" }
nu-plugin = { version = "0.28.0", path = "./crates/nu-plugin" }
nu-protocol = { version = "0.28.0", path = "./crates/nu-protocol" }
nu-source = { version = "0.28.0", path = "./crates/nu-source" }
nu-value-ext = { version = "0.28.0", path = "./crates/nu-value-ext" }
nu_plugin_binaryview = { version = "0.27.1", path = "./crates/nu_plugin_binaryview", optional = true }
nu_plugin_chart = { version = "0.27.1", path = "./crates/nu_plugin_chart", optional = true }
nu_plugin_fetch = { version = "0.27.1", path = "./crates/nu_plugin_fetch", optional = true }
nu_plugin_from_bson = { version = "0.27.1", path = "./crates/nu_plugin_from_bson", optional = true }
nu_plugin_from_sqlite = { version = "0.27.1", path = "./crates/nu_plugin_from_sqlite", optional = true }
nu_plugin_inc = { version = "0.27.1", path = "./crates/nu_plugin_inc", optional = true }
nu_plugin_match = { version = "0.27.1", path = "./crates/nu_plugin_match", optional = true }
nu_plugin_post = { version = "0.27.1", path = "./crates/nu_plugin_post", optional = true }
nu_plugin_ps = { version = "0.27.1", path = "./crates/nu_plugin_ps", optional = true }
nu_plugin_s3 = { version = "0.27.1", path = "./crates/nu_plugin_s3", optional = true }
nu_plugin_selector = { version = "0.27.1", path = "./crates/nu_plugin_selector", optional = true }
nu_plugin_start = { version = "0.27.1", path = "./crates/nu_plugin_start", optional = true }
nu_plugin_sys = { version = "0.27.1", path = "./crates/nu_plugin_sys", optional = true }
nu_plugin_textview = { version = "0.27.1", path = "./crates/nu_plugin_textview", optional = true }
nu_plugin_to_bson = { version = "0.27.1", path = "./crates/nu_plugin_to_bson", optional = true }
nu_plugin_to_sqlite = { version = "0.27.1", path = "./crates/nu_plugin_to_sqlite", optional = true }
nu_plugin_tree = { version = "0.27.1", path = "./crates/nu_plugin_tree", optional = true }
nu_plugin_xpath = { version = "0.27.1", path = "./crates/nu_plugin_xpath", optional = true }
nu_plugin_binaryview = { version = "0.28.0", path = "./crates/nu_plugin_binaryview", optional = true }
nu_plugin_chart = { version = "0.28.0", path = "./crates/nu_plugin_chart", optional = true }
nu_plugin_fetch = { version = "0.28.0", path = "./crates/nu_plugin_fetch", optional = true }
nu_plugin_from_bson = { version = "0.28.0", path = "./crates/nu_plugin_from_bson", optional = true }
nu_plugin_from_sqlite = { version = "0.28.0", path = "./crates/nu_plugin_from_sqlite", optional = true }
nu_plugin_inc = { version = "0.28.0", path = "./crates/nu_plugin_inc", optional = true }
nu_plugin_match = { version = "0.28.0", path = "./crates/nu_plugin_match", optional = true }
nu_plugin_post = { version = "0.28.0", path = "./crates/nu_plugin_post", optional = true }
nu_plugin_ps = { version = "0.28.0", path = "./crates/nu_plugin_ps", optional = true }
nu_plugin_s3 = { version = "0.28.0", path = "./crates/nu_plugin_s3", optional = true }
nu_plugin_selector = { version = "0.28.0", path = "./crates/nu_plugin_selector", optional = true }
nu_plugin_start = { version = "0.28.0", path = "./crates/nu_plugin_start", optional = true }
nu_plugin_sys = { version = "0.28.0", path = "./crates/nu_plugin_sys", optional = true }
nu_plugin_textview = { version = "0.28.0", path = "./crates/nu_plugin_textview", optional = true }
nu_plugin_to_bson = { version = "0.28.0", path = "./crates/nu_plugin_to_bson", optional = true }
nu_plugin_to_sqlite = { version = "0.28.0", path = "./crates/nu_plugin_to_sqlite", optional = true }
nu_plugin_tree = { version = "0.28.0", path = "./crates/nu_plugin_tree", optional = true }
nu_plugin_xpath = { version = "0.28.0", path = "./crates/nu_plugin_xpath", optional = true }
# Required to bootstrap the main binary
clap = "2.33.3"
@ -57,7 +57,7 @@ log = "0.4.14"
pretty_env_logger = "0.4.0"
[dev-dependencies]
nu-test-support = { version = "0.27.1", path = "./crates/nu-test-support" }
nu-test-support = { version = "0.28.0", path = "./crates/nu-test-support" }
dunce = "1.0.1"
serial_test = "0.5.1"
@ -144,9 +144,15 @@ s3 = ["nu_plugin_s3"]
selector = ["nu_plugin_selector"]
sqlite = ["nu_plugin_from_sqlite", "nu_plugin_to_sqlite"]
start = ["nu_plugin_start"]
trash-support = ["nu-cli/trash-support", "nu-command/trash-support"]
trash-support = [
"nu-cli/trash-support",
"nu-command/trash-support",
"nu-engine/trash-support",
]
tree = ["nu_plugin_tree"]
xpath = ["nu_plugin_xpath"]
#This is disabled in extra for now
table-pager = ["nu-command/table-pager"]
[profile.release]
#strip = "symbols" #Couldn't get working +nightly

View File

@ -1,11 +1,12 @@
# README
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/nushell/nushell)
[![Crates.io](https://img.shields.io/crates/v/nu.svg)](https://crates.io/crates/nu)
[![Build Status](https://dev.azure.com/nushell/nushell/_apis/build/status/nushell.nushell?branchName=master)](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=master)
[![Build Status](https://dev.azure.com/nushell/nushell/_apis/build/status/nushell.nushell?branchName=main)](https://dev.azure.com/nushell/nushell/_build/latest?definitionId=2&branchName=main)
[![Discord](https://img.shields.io/discord/601130461678272522.svg?logo=discord)](https://discord.gg/NtAbbGn)
[![The Changelog #363](https://img.shields.io/badge/The%20Changelog-%23363-61c192.svg)](https://changelog.com/podcast/363)
[![@nu_shell](https://img.shields.io/badge/twitter-@nu_shell-1DA1F3?style=flat-square)](https://twitter.com/nu_shell)
![GitHub commit activity](https://img.shields.io/github/commit-activity/m/nushell/nushell)
![GitHub contributors](https://img.shields.io/github/contributors/nushell/nushell)
## Nushell
@ -239,7 +240,7 @@ Nu has early support for configuring the shell. You can refer to the book for a
To set one of these variables, you can use `config set`. For example:
```shell
> config set edit_mode "vi"
> config set line_editor.edit_mode "vi"
> config set path $nu.path
```

2
crates/nu-ansi-term/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
Cargo.lock

View File

@ -0,0 +1,35 @@
[package]
authors = [
"ogham@bsago.me",
"Ryan Scheel (Havvy) <ryan.havvy@gmail.com>",
"Josh Triplett <josh@joshtriplett.org>",
"The Nu Project Contributors",
]
description = "Library for ANSI terminal colors and styles (bold, underline)"
edition = "2018"
license = "MIT"
name = "nu-ansi-term"
version = "0.28.0"
[lib]
doctest = false
# name = "nu-ansi-term"
[features]
derive_serde_style = ["serde"]
[dependencies.serde]
version = "1.0.90"
features = ["derive"]
optional = true
[target.'cfg(target_os="windows")'.dependencies.winapi]
version = "0.3.4"
features = ["consoleapi", "errhandlingapi", "fileapi", "handleapi", "processenv"]
[dev-dependencies]
doc-comment = "0.3"
regex = "1.1.9"
[dev-dependencies.serde_json]
version = "1.0.39"

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Benjamin Sago
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,182 @@
# nu-ansi-term
> This is a copy of rust-ansi-term but with Color change to Color and light foreground colors added (90-97) as well as light background colors added (100-107).
This is a library for controlling colors and formatting, such as red bold text or blue underlined text, on ANSI terminals.
### [View the Rustdoc](https://docs.rs/nu_ansi_term/)
# Installation
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
```toml
[dependencies]
nu_ansi_term = "0.13"
```
## Basic usage
There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Color`.
A `Style` holds stylistic information: foreground and background colors, whether the text should be bold, or blinking, or other properties.
The `Color` enum represents the available colors.
And an `ANSIString` is a string paired with a `Style`.
`Color` is also available as an alias to `Color`.
To format a string, call the `paint` method on a `Style` or a `Color`, passing in the string you want to format as the argument.
For example, heres how to get some red text:
```rust
use nu_ansi_term::Color::Red;
println!("This is in red: {}", Red.paint("a red string"));
```
Its important to note that the `paint` method does _not_ actually return a string with the ANSI control characters surrounding it.
Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters.
This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes.
If you _do_ want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value:
```rust
use nu_ansi_term::Color::Red;
let red_string = Red.paint("a red string").to_string();
```
**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first:
```rust,ignore
let enabled = nu_ansi_term::enable_ansi_support();
```
## Bold, underline, background, and other styles
For anything more complex than plain foreground color changes, you need to construct `Style` values themselves, rather than beginning with a `Color`.
You can do this by chaining methods based on a new `Style`, created with `Style::new()`.
Each method creates a new style that has that specific property set.
For example:
```rust
use nu_ansi_term::Style;
println!("How about some {} and {}?",
Style::new().bold().paint("bold"),
Style::new().underline().paint("underline"));
```
For brevity, these methods have also been implemented for `Color` values, so you can give your styles a foreground color without having to begin with an empty `Style` value:
```rust
use nu_ansi_term::Color::{Blue, Yellow};
println!("Demonstrating {} and {}!",
Blue.bold().paint("blue bold"),
Yellow.underline().paint("yellow underline"));
println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
```
The complete list of styles you can use are:
`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colors.
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Color`.
You can do this using the `fg` method:
```rust
use nu_ansi_term::Style;
use nu_ansi_term::Color::{Blue, Cyan, Yellow};
println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
```
You can turn a `Color` into a `Style` with the `normal` method.
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Color` directly, but its useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold” and “red, but not bold” styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with _nothing_ set.
```rust
use nu_ansi_term::Style;
use nu_ansi_term::Color::Red;
Red.normal().paint("yet another red string");
Style::default().paint("a completely regular string");
```
## Extended colors
You can access the extended range of 256 colors by using the `Color::Fixed` variant, which takes an argument of the color number to use.
This can be included wherever you would use a `Color`:
```rust
use nu_ansi_term::Color::Fixed;
Fixed(134).paint("A sort of light purple");
Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
```
The first sixteen of these values are the same as the normal and bold standard color variants.
Theres nothing stopping you from using these as `Fixed` colors instead, but theres nothing to be gained by doing so either.
You can also access full 24-bit color by using the `Color::RGB` variant, which takes separate `u8` arguments for red, green, and blue:
```rust
use nu_ansi_term::Color::RGB;
RGB(70, 130, 180).paint("Steel blue");
```
## Combining successive coloured strings
The benefit of writing ANSI escape codes to the terminal is that they _stack_: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style.
For example, if you want to have some blue text followed by some blue bold text, its possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings.
This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer.
The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine.
The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets:
```rust
use nu_ansi_term::Color::Red;
use nu_ansi_term::{ANSIString, ANSIStrings};
let some_value = format!("{:b}", 42);
let strings: &[ANSIString<'static>] = &[
Red.paint("["),
Red.bold().paint(some_value),
Red.paint("]"),
];
println!("Value: {}", ANSIStrings(strings));
```
There are several things to note here.
Firstly, the `paint` method can take _either_ an owned `String` or a borrowed `&str`.
Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time.
This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices.
Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required.
## Byte strings
This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding.
`Style` and `Color` support painting `[u8]` values, resulting in an `ANSIByteString`.
This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`:
```rust
use nu_ansi_term::Color::Green;
Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
```
Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences:
```rust
use nu_ansi_term::Color::Green;
use nu_ansi_term::ANSIByteStrings;
ANSIByteStrings(&[
Green.paint("user data 1\n".as_bytes()),
Green.bold().paint("user data 2\n".as_bytes()),
]).write_to(&mut std::io::stdout()).unwrap();
```

View File

@ -0,0 +1,72 @@
extern crate nu_ansi_term;
use nu_ansi_term::Color;
// This example prints out the 256 colors.
// They're arranged like this:
//
// - 0 to 8 are the eight standard colors.
// - 9 to 15 are the eight bold colors.
// - 16 to 231 are six blocks of six-by-six color squares.
// - 232 to 255 are shades of grey.
fn main() {
// First two lines
for c in 0..8 {
glow(c, c != 0);
print!(" ");
}
println!();
for c in 8..16 {
glow(c, c != 8);
print!(" ");
}
println!("\n");
// Six lines of the first three squares
for row in 0..6 {
for square in 0..3 {
for column in 0..6 {
glow(16 + square * 36 + row * 6 + column, row >= 3);
print!(" ");
}
print!(" ");
}
println!();
}
println!();
// Six more lines of the other three squares
for row in 0..6 {
for square in 0..3 {
for column in 0..6 {
glow(124 + square * 36 + row * 6 + column, row >= 3);
print!(" ");
}
print!(" ");
}
println!();
}
println!();
// The last greyscale lines
for c in 232..=243 {
glow(c, false);
print!(" ");
}
println!();
for c in 244..=255 {
glow(c, true);
print!(" ");
}
println!();
}
fn glow(c: u8, light_bg: bool) {
let base = if light_bg { Color::Black } else { Color::White };
let style = base.on(Color::Fixed(c));
print!("{}", style.paint(&format!(" {:3} ", c)));
}

View File

@ -0,0 +1,18 @@
extern crate nu_ansi_term;
use nu_ansi_term::{Color::*, Style};
// This example prints out the 16 basic colors.
fn main() {
let normal = Style::default();
println!("{} {}", normal.paint("Normal"), normal.bold().paint("bold"));
println!("{} {}", Black.paint("Black"), Black.bold().paint("bold"));
println!("{} {}", Red.paint("Red"), Red.bold().paint("bold"));
println!("{} {}", Green.paint("Green"), Green.bold().paint("bold"));
println!("{} {}", Yellow.paint("Yellow"), Yellow.bold().paint("bold"));
println!("{} {}", Blue.paint("Blue"), Blue.bold().paint("bold"));
println!("{} {}", Purple.paint("Purple"), Purple.bold().paint("bold"));
println!("{} {}", Cyan.paint("Cyan"), Cyan.bold().paint("bold"));
println!("{} {}", White.paint("White"), White.bold().paint("bold"));
}

View File

@ -0,0 +1,23 @@
extern crate nu_ansi_term;
use nu_ansi_term::{Color, Style};
// This example prints out a color gradient in a grid by calculating each
// characters red, green, and blue components, and using 24-bit color codes
// to display them.
const WIDTH: i32 = 80;
const HEIGHT: i32 = 24;
fn main() {
for row in 0..HEIGHT {
for col in 0..WIDTH {
let r = (row * 255 / HEIGHT) as u8;
let g = (col * 255 / WIDTH) as u8;
let b = 128;
print!("{}", Style::default().on(Color::RGB(r, g, b)).paint(" "));
}
println!();
}
}

View File

@ -0,0 +1,404 @@
use crate::style::{Color, Style};
use crate::write::AnyWrite;
use std::fmt;
impl Style {
/// Write any bytes that go *before* a piece of text to the given writer.
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
// If there are actually no styles here, then dont write *any* codes
// as the prefix. An empty ANSI code may not affect the terminal
// output at all, but a user may just want a code-free string.
if self.is_plain() {
return Ok(());
}
// Write the codes prefix, then write numbers, separated by
// semicolons, for each text style we want to apply.
write!(f, "\x1B[")?;
let mut written_anything = false;
{
let mut write_char = |c| {
if written_anything {
write!(f, ";")?;
}
written_anything = true;
write!(f, "{}", c)?;
Ok(())
};
if self.is_bold {
write_char('1')?
}
if self.is_dimmed {
write_char('2')?
}
if self.is_italic {
write_char('3')?
}
if self.is_underline {
write_char('4')?
}
if self.is_blink {
write_char('5')?
}
if self.is_reverse {
write_char('7')?
}
if self.is_hidden {
write_char('8')?
}
if self.is_strikethrough {
write_char('9')?
}
}
// The foreground and background colors, if specified, need to be
// handled specially because the number codes are more complicated.
// (see `write_background_code` and `write_foreground_code`)
if let Some(bg) = self.background {
if written_anything {
write!(f, ";")?;
}
written_anything = true;
bg.write_background_code(f)?;
}
if let Some(fg) = self.foreground {
if written_anything {
write!(f, ";")?;
}
fg.write_foreground_code(f)?;
}
// All the codes end with an `m`, because reasons.
write!(f, "m")?;
Ok(())
}
/// Write any bytes that go *after* a piece of text to the given writer.
fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
if self.is_plain() {
Ok(())
} else {
write!(f, "{}", RESET)
}
}
}
/// The code to send to reset all styles and return to `Style::default()`.
pub static RESET: &str = "\x1B[0m";
impl Color {
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
match *self {
Color::Black => write!(f, "30"),
Color::Red => write!(f, "31"),
Color::Green => write!(f, "32"),
Color::Yellow => write!(f, "33"),
Color::Blue => write!(f, "34"),
Color::Purple => write!(f, "35"),
Color::Magenta => write!(f, "35"),
Color::Cyan => write!(f, "36"),
Color::White => write!(f, "37"),
Color::Fixed(num) => write!(f, "38;5;{}", &num),
Color::RGB(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
Color::DarkGray => write!(f, "90"),
Color::LightRed => write!(f, "91"),
Color::LightGreen => write!(f, "92"),
Color::LightYellow => write!(f, "93"),
Color::LightBlue => write!(f, "94"),
Color::LightPurple => write!(f, "95"),
Color::LightMagenta => write!(f, "95"),
Color::LightCyan => write!(f, "96"),
Color::LightGray => write!(f, "97"),
}
}
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
match *self {
Color::Black => write!(f, "40"),
Color::Red => write!(f, "41"),
Color::Green => write!(f, "42"),
Color::Yellow => write!(f, "43"),
Color::Blue => write!(f, "44"),
Color::Purple => write!(f, "45"),
Color::Magenta => write!(f, "45"),
Color::Cyan => write!(f, "46"),
Color::White => write!(f, "47"),
Color::Fixed(num) => write!(f, "48;5;{}", &num),
Color::RGB(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
Color::DarkGray => write!(f, "100"),
Color::LightRed => write!(f, "101"),
Color::LightGreen => write!(f, "102"),
Color::LightYellow => write!(f, "103"),
Color::LightBlue => write!(f, "104"),
Color::LightPurple => write!(f, "105"),
Color::LightMagenta => write!(f, "105"),
Color::LightCyan => write!(f, "106"),
Color::LightGray => write!(f, "107"),
}
}
}
/// Like `ANSIString`, but only displays the style prefix.
///
/// This type implements the `Display` trait, meaning it can be written to a
/// `std::fmt` formatting without doing any extra allocation, and written to a
/// string with the `.to_string()` method. For examples, see
/// [`Style::prefix`](struct.Style.html#method.prefix).
#[derive(Clone, Copy, Debug)]
pub struct Prefix(Style);
/// Like `ANSIString`, but only displays the difference between two
/// styles.
///
/// This type implements the `Display` trait, meaning it can be written to a
/// `std::fmt` formatting without doing any extra allocation, and written to a
/// string with the `.to_string()` method. For examples, see
/// [`Style::infix`](struct.Style.html#method.infix).
#[derive(Clone, Copy, Debug)]
pub struct Infix(Style, Style);
/// Like `ANSIString`, but only displays the style suffix.
///
/// This type implements the `Display` trait, meaning it can be written to a
/// `std::fmt` formatting without doing any extra allocation, and written to a
/// string with the `.to_string()` method. For examples, see
/// [`Style::suffix`](struct.Style.html#method.suffix).
#[derive(Clone, Copy, Debug)]
pub struct Suffix(Style);
impl Style {
/// The prefix bytes for this style. These are the bytes that tell the
/// terminal to use a different color or font style.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color::Blue};
///
/// let style = Style::default().bold();
/// assert_eq!("\x1b[1m",
/// style.prefix().to_string());
///
/// let style = Blue.bold();
/// assert_eq!("\x1b[1;34m",
/// style.prefix().to_string());
///
/// let style = Style::default();
/// assert_eq!("",
/// style.prefix().to_string());
/// ```
pub fn prefix(self) -> Prefix {
Prefix(self)
}
/// The infix bytes between this style and `next` style. These are the bytes
/// that tell the terminal to change the style to `next`. These may include
/// a reset followed by the next color and style, depending on the two styles.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color::Green};
///
/// let style = Style::default().bold();
/// assert_eq!("\x1b[32m",
/// style.infix(Green.bold()).to_string());
///
/// let style = Green.normal();
/// assert_eq!("\x1b[1m",
/// style.infix(Green.bold()).to_string());
///
/// let style = Style::default();
/// assert_eq!("",
/// style.infix(style).to_string());
/// ```
pub fn infix(self, next: Style) -> Infix {
Infix(self, next)
}
/// The suffix for this style. These are the bytes that tell the terminal
/// to reset back to its normal color and font style.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color::Green};
///
/// let style = Style::default().bold();
/// assert_eq!("\x1b[0m",
/// style.suffix().to_string());
///
/// let style = Green.normal().bold();
/// assert_eq!("\x1b[0m",
/// style.suffix().to_string());
///
/// let style = Style::default();
/// assert_eq!("",
/// style.suffix().to_string());
/// ```
pub fn suffix(self) -> Suffix {
Suffix(self)
}
}
impl Color {
/// The prefix bytes for this color as a `Style`. These are the bytes
/// that tell the terminal to use a different color or font style.
///
/// See also [`Style::prefix`](struct.Style.html#method.prefix).
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color::Green;
///
/// assert_eq!("\x1b[0m",
/// Green.suffix().to_string());
/// ```
pub fn prefix(self) -> Prefix {
Prefix(self.normal())
}
/// The infix bytes between this color and `next` color. These are the bytes
/// that tell the terminal to use the `next` color, or to do nothing if
/// the two colors are equal.
///
/// See also [`Style::infix`](struct.Style.html#method.infix).
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color::{Red, Yellow};
///
/// assert_eq!("\x1b[33m",
/// Red.infix(Yellow).to_string());
/// ```
pub fn infix(self, next: Color) -> Infix {
Infix(self.normal(), next.normal())
}
/// The suffix for this color as a `Style`. These are the bytes that
/// tell the terminal to reset back to its normal color and font style.
///
/// See also [`Style::suffix`](struct.Style.html#method.suffix).
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color::Purple;
///
/// assert_eq!("\x1b[0m",
/// Purple.suffix().to_string());
/// ```
pub fn suffix(self) -> Suffix {
Suffix(self.normal())
}
}
impl fmt::Display for Prefix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let f: &mut dyn fmt::Write = f;
self.0.write_prefix(f)
}
}
impl fmt::Display for Infix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use crate::difference::Difference;
match Difference::between(&self.0, &self.1) {
Difference::ExtraStyles(style) => {
let f: &mut dyn fmt::Write = f;
style.write_prefix(f)
}
Difference::Reset => {
let f: &mut dyn fmt::Write = f;
write!(f, "{}{}", RESET, self.1.prefix())
}
Difference::NoDifference => {
Ok(()) // nothing to write
}
}
}
}
impl fmt::Display for Suffix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let f: &mut dyn fmt::Write = f;
self.0.write_suffix(f)
}
}
#[cfg(test)]
mod test {
use crate::style::Color::*;
use crate::style::Style;
macro_rules! test {
($name: ident: $style: expr; $input: expr => $result: expr) => {
#[test]
fn $name() {
assert_eq!($style.paint($input).to_string(), $result.to_string());
let mut v = Vec::new();
$style.paint($input.as_bytes()).write_to(&mut v).unwrap();
assert_eq!(v.as_slice(), $result.as_bytes());
}
};
}
test!(plain: Style::default(); "text/plain" => "text/plain");
test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m");
test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m");
test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m");
test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m");
test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m");
test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
test!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m");
test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m");
test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m");
test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m");
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m");
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m");
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m");
test!(rgb: RGB(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m");
test!(rgb_on_blue: RGB(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m");
test!(blue_on_rgb: Blue.on(RGB(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m");
test!(rgb_on_rgb: RGB(70,130,180).on(RGB(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m");
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m");
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m");
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m");
test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m");
test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m");
test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m");
test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m");
test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m");
test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m");
test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m");
#[test]
fn test_infix() {
assert_eq!(
Style::new().dimmed().infix(Style::new()).to_string(),
"\x1B[0m"
);
assert_eq!(
White.dimmed().infix(White.normal()).to_string(),
"\x1B[0m\x1B[37m"
);
assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
}
}

View File

@ -0,0 +1,152 @@
use crate::style::Style;
use std::fmt;
/// Styles have a special `Debug` implementation that only shows the fields that
/// are set. Fields that havent been touched arent included in the output.
///
/// This behaviour gets bypassed when using the alternate formatting mode
/// `format!("{:#?}")`.
///
/// use nu_ansi_term::Color::{Red, Blue};
/// assert_eq!("Style { fg(Red), on(Blue), bold, italic }",
/// format!("{:?}", Red.on(Blue).bold().italic()));
impl fmt::Debug for Style {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
fmt.debug_struct("Style")
.field("foreground", &self.foreground)
.field("background", &self.background)
.field("blink", &self.is_blink)
.field("bold", &self.is_bold)
.field("dimmed", &self.is_dimmed)
.field("hidden", &self.is_hidden)
.field("italic", &self.is_italic)
.field("reverse", &self.is_reverse)
.field("strikethrough", &self.is_strikethrough)
.field("underline", &self.is_underline)
.finish()
} else if self.is_plain() {
fmt.write_str("Style {}")
} else {
fmt.write_str("Style { ")?;
let mut written_anything = false;
if let Some(fg) = self.foreground {
if written_anything {
fmt.write_str(", ")?
}
written_anything = true;
write!(fmt, "fg({:?})", fg)?
}
if let Some(bg) = self.background {
if written_anything {
fmt.write_str(", ")?
}
written_anything = true;
write!(fmt, "on({:?})", bg)?
}
{
let mut write_flag = |name| {
if written_anything {
fmt.write_str(", ")?
}
written_anything = true;
fmt.write_str(name)
};
if self.is_blink {
write_flag("blink")?
}
if self.is_bold {
write_flag("bold")?
}
if self.is_dimmed {
write_flag("dimmed")?
}
if self.is_hidden {
write_flag("hidden")?
}
if self.is_italic {
write_flag("italic")?
}
if self.is_reverse {
write_flag("reverse")?
}
if self.is_strikethrough {
write_flag("strikethrough")?
}
if self.is_underline {
write_flag("underline")?
}
}
write!(fmt, " }}")
}
}
}
#[cfg(test)]
mod test {
use crate::style::Color::*;
use crate::style::Style;
fn style() -> Style {
Style::new()
}
macro_rules! test {
($name: ident: $obj: expr => $result: expr) => {
#[test]
fn $name() {
assert_eq!($result, format!("{:?}", $obj));
}
};
}
test!(empty: style() => "Style {}");
test!(bold: style().bold() => "Style { bold }");
test!(italic: style().italic() => "Style { italic }");
test!(both: style().bold().italic() => "Style { bold, italic }");
test!(red: Red.normal() => "Style { fg(Red) }");
test!(redblue: Red.normal().on(RGB(3, 2, 4)) => "Style { fg(Red), on(RGB(3, 2, 4)) }");
test!(everything:
Red.on(Blue).blink().bold().dimmed().hidden().italic().reverse().strikethrough().underline() =>
"Style { fg(Red), on(Blue), blink, bold, dimmed, hidden, italic, reverse, strikethrough, underline }");
#[test]
fn long_and_detailed() {
extern crate regex;
let expected_debug = "Style { fg(Blue), bold }";
let expected_pretty_repat = r##"(?x)
Style\s+\{\s+
foreground:\s+Some\(\s+
Blue,?\s+
\),\s+
background:\s+None,\s+
blink:\s+false,\s+
bold:\s+true,\s+
dimmed:\s+false,\s+
hidden:\s+false,\s+
italic:\s+false,\s+
reverse:\s+false,\s+
strikethrough:\s+
false,\s+
underline:\s+false,?\s+
\}"##;
let re = regex::Regex::new(expected_pretty_repat).unwrap();
let style = Blue.bold();
let style_fmt_debug = format!("{:?}", style);
let style_fmt_pretty = format!("{:#?}", style);
println!("style_fmt_debug:\n{}", style_fmt_debug);
println!("style_fmt_pretty:\n{}", style_fmt_pretty);
assert_eq!(expected_debug, style_fmt_debug);
assert!(re.is_match(&style_fmt_pretty));
}
}

View File

@ -0,0 +1,174 @@
use super::Style;
/// When printing out one colored string followed by another, use one of
/// these rules to figure out which *extra* control codes need to be sent.
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum Difference {
/// Print out the control codes specified by this style to end up looking
/// like the second string's styles.
ExtraStyles(Style),
/// Converting between these two is impossible, so just send a reset
/// command and then the second string's styles.
Reset,
/// The before style is exactly the same as the after style, so no further
/// control codes need to be printed.
NoDifference,
}
impl Difference {
/// Compute the 'style difference' required to turn an existing style into
/// the given, second style.
///
/// For example, to turn green text into green bold text, it's redundant
/// to write a reset command then a second green+bold command, instead of
/// just writing one bold command. This method should see that both styles
/// use the foreground color green, and reduce it to a single command.
///
/// This method returns an enum value because it's not actually always
/// possible to turn one style into another: for example, text could be
/// made bold and underlined, but you can't remove the bold property
/// without also removing the underline property. So when this has to
/// happen, this function returns None, meaning that the entire set of
/// styles should be reset and begun again.
pub fn between(first: &Style, next: &Style) -> Difference {
use self::Difference::*;
// XXX(Havvy): This algorithm is kind of hard to replicate without
// having the Plain/Foreground enum variants, so I'm just leaving
// it commented out for now, and defaulting to Reset.
if first == next {
return NoDifference;
}
// Cannot un-bold, so must Reset.
if first.is_bold && !next.is_bold {
return Reset;
}
if first.is_dimmed && !next.is_dimmed {
return Reset;
}
if first.is_italic && !next.is_italic {
return Reset;
}
// Cannot un-underline, so must Reset.
if first.is_underline && !next.is_underline {
return Reset;
}
if first.is_blink && !next.is_blink {
return Reset;
}
if first.is_reverse && !next.is_reverse {
return Reset;
}
if first.is_hidden && !next.is_hidden {
return Reset;
}
if first.is_strikethrough && !next.is_strikethrough {
return Reset;
}
// Cannot go from foreground to no foreground, so must Reset.
if first.foreground.is_some() && next.foreground.is_none() {
return Reset;
}
// Cannot go from background to no background, so must Reset.
if first.background.is_some() && next.background.is_none() {
return Reset;
}
let mut extra_styles = Style::default();
if first.is_bold != next.is_bold {
extra_styles.is_bold = true;
}
if first.is_dimmed != next.is_dimmed {
extra_styles.is_dimmed = true;
}
if first.is_italic != next.is_italic {
extra_styles.is_italic = true;
}
if first.is_underline != next.is_underline {
extra_styles.is_underline = true;
}
if first.is_blink != next.is_blink {
extra_styles.is_blink = true;
}
if first.is_reverse != next.is_reverse {
extra_styles.is_reverse = true;
}
if first.is_hidden != next.is_hidden {
extra_styles.is_hidden = true;
}
if first.is_strikethrough != next.is_strikethrough {
extra_styles.is_strikethrough = true;
}
if first.foreground != next.foreground {
extra_styles.foreground = next.foreground;
}
if first.background != next.background {
extra_styles.background = next.background;
}
ExtraStyles(extra_styles)
}
}
#[cfg(test)]
mod test {
use super::Difference::*;
use super::*;
use crate::style::Color::*;
use crate::style::Style;
fn style() -> Style {
Style::new()
}
macro_rules! test {
($name: ident: $first: expr; $next: expr => $result: expr) => {
#[test]
fn $name() {
assert_eq!($result, Difference::between(&$first, &$next));
}
};
}
test!(nothing: Green.normal(); Green.normal() => NoDifference);
test!(uppercase: Green.normal(); Green.bold() => ExtraStyles(style().bold()));
test!(lowercase: Green.bold(); Green.normal() => Reset);
test!(nothing2: Green.bold(); Green.bold() => NoDifference);
test!(color_change: Red.normal(); Blue.normal() => ExtraStyles(Blue.normal()));
test!(addition_of_blink: style(); style().blink() => ExtraStyles(style().blink()));
test!(addition_of_dimmed: style(); style().dimmed() => ExtraStyles(style().dimmed()));
test!(addition_of_hidden: style(); style().hidden() => ExtraStyles(style().hidden()));
test!(addition_of_reverse: style(); style().reverse() => ExtraStyles(style().reverse()));
test!(addition_of_strikethrough: style(); style().strikethrough() => ExtraStyles(style().strikethrough()));
test!(removal_of_strikethrough: style().strikethrough(); style() => Reset);
test!(removal_of_reverse: style().reverse(); style() => Reset);
test!(removal_of_hidden: style().hidden(); style() => Reset);
test!(removal_of_dimmed: style().dimmed(); style() => Reset);
test!(removal_of_blink: style().blink(); style() => Reset);
}

View File

@ -0,0 +1,303 @@
use crate::ansi::RESET;
use crate::difference::Difference;
use crate::style::{Color, Style};
use crate::write::AnyWrite;
use std::borrow::Cow;
use std::fmt;
use std::io;
use std::ops::Deref;
/// An `ANSIGenericString` includes a generic string type and a `Style` to
/// display that string. `ANSIString` and `ANSIByteString` are aliases for
/// this type on `str` and `\[u8]`, respectively.
#[derive(PartialEq, Debug)]
pub struct ANSIGenericString<'a, S: 'a + ToOwned + ?Sized>
where
<S as ToOwned>::Owned: fmt::Debug,
{
style: Style,
string: Cow<'a, S>,
}
/// Cloning an `ANSIGenericString` will clone its underlying string.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::ANSIString;
///
/// let plain_string = ANSIString::from("a plain string");
/// let clone_string = plain_string.clone();
/// assert_eq!(clone_string, plain_string);
/// ```
impl<'a, S: 'a + ToOwned + ?Sized> Clone for ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
{
fn clone(&self) -> ANSIGenericString<'a, S> {
ANSIGenericString {
style: self.style,
string: self.string.clone(),
}
}
}
// You might think that the hand-written Clone impl above is the same as the
// one that gets generated with #[derive]. But its not *quite* the same!
//
// `str` is not Clone, and the derived Clone implementation puts a Clone
// constraint on the S type parameter (generated using --pretty=expanded):
//
// ↓_________________↓
// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone
// for ANSIGenericString<'a, S> where
// <S as ToOwned>::Owned: fmt::Debug { ... }
//
// This resulted in compile errors when you tried to derive Clone on a type
// that used it:
//
// #[derive(PartialEq, Debug, Clone, Default)]
// pub struct TextCellContents(Vec<ANSIString<'static>>);
// ^^^^^^^^^^^^^^^^^^^^^^^^^
// error[E0277]: the trait `std::clone::Clone` is not implemented for `str`
//
// The hand-written impl above can ignore that constraint and still compile.
/// An ANSI String is a string coupled with the `Style` to display it
/// in a terminal.
///
/// Although not technically a string itself, it can be turned into
/// one with the `to_string` method.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::ANSIString;
/// use nu_ansi_term::Color::Red;
///
/// let red_string = Red.paint("a red string");
/// println!("{}", red_string);
/// ```
///
/// ```
/// use nu_ansi_term::ANSIString;
///
/// let plain_string = ANSIString::from("a plain string");
/// assert_eq!(&*plain_string, "a plain string");
/// ```
pub type ANSIString<'a> = ANSIGenericString<'a, str>;
/// An `ANSIByteString` represents a formatted series of bytes. Use
/// `ANSIByteString` when styling text with an unknown encoding.
pub type ANSIByteString<'a> = ANSIGenericString<'a, [u8]>;
impl<'a, I, S: 'a + ToOwned + ?Sized> From<I> for ANSIGenericString<'a, S>
where
I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug,
{
fn from(input: I) -> ANSIGenericString<'a, S> {
ANSIGenericString {
string: input.into(),
style: Style::default(),
}
}
}
impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
{
/// Directly access the style
pub fn style_ref(&self) -> &Style {
&self.style
}
/// Directly access the style mutably
pub fn style_ref_mut(&mut self) -> &mut Style {
&mut self.style
}
}
impl<'a, S: 'a + ToOwned + ?Sized> Deref for ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
{
type Target = S;
fn deref(&self) -> &S {
self.string.deref()
}
}
/// A set of `ANSIGenericString`s collected together, in order to be
/// written with a minimum of control characters.
#[derive(Debug, PartialEq)]
pub struct ANSIGenericStrings<'a, S: 'a + ToOwned + ?Sized>(pub &'a [ANSIGenericString<'a, S>])
where
<S as ToOwned>::Owned: fmt::Debug,
S: PartialEq;
/// A set of `ANSIString`s collected together, in order to be written with a
/// minimum of control characters.
pub type ANSIStrings<'a> = ANSIGenericStrings<'a, str>;
/// A function to construct an `ANSIStrings` instance.
#[allow(non_snake_case)]
pub fn ANSIStrings<'a>(arg: &'a [ANSIString<'a>]) -> ANSIStrings<'a> {
ANSIGenericStrings(arg)
}
/// A set of `ANSIByteString`s collected together, in order to be
/// written with a minimum of control characters.
pub type ANSIByteStrings<'a> = ANSIGenericStrings<'a, [u8]>;
/// A function to construct an `ANSIByteStrings` instance.
#[allow(non_snake_case)]
pub fn ANSIByteStrings<'a>(arg: &'a [ANSIByteString<'a>]) -> ANSIByteStrings<'a> {
ANSIGenericStrings(arg)
}
// ---- paint functions ----
impl Style {
/// Paints the given text with this color, returning an ANSI string.
#[must_use]
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S>
where
I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug,
{
ANSIGenericString {
string: input.into(),
style: self,
}
}
}
impl Color {
/// Paints the given text with this color, returning an ANSI string.
/// This is a short-cut so you dont have to use `Blue.normal()` just
/// to get blue text.
///
/// ```
/// use nu_ansi_term::Color::Blue;
/// println!("{}", Blue.paint("da ba dee"));
/// ```
#[must_use]
pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S>
where
I: Into<Cow<'a, S>>,
<S as ToOwned>::Owned: fmt::Debug,
{
ANSIGenericString {
string: input.into(),
style: self.normal(),
}
}
}
// ---- writers for individual ANSI strings ----
impl<'a> fmt::Display for ANSIString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let w: &mut dyn fmt::Write = f;
self.write_to_any(w)
}
}
impl<'a> ANSIByteString<'a> {
/// Write an `ANSIByteString` to an `io::Write`. This writes the escape
/// sequences for the associated `Style` around the bytes.
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
let w: &mut dyn io::Write = w;
self.write_to_any(w)
}
}
impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
&'a S: AsRef<[u8]>,
{
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
write!(w, "{}", self.style.prefix())?;
w.write_str(self.string.as_ref())?;
write!(w, "{}", self.style.suffix())
}
}
// ---- writers for combined ANSI strings ----
impl<'a> fmt::Display for ANSIStrings<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let f: &mut dyn fmt::Write = f;
self.write_to_any(f)
}
}
impl<'a> ANSIByteStrings<'a> {
/// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal
/// escape sequences for the associated `Style`s around each set of
/// bytes.
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
let w: &mut dyn io::Write = w;
self.write_to_any(w)
}
}
impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> ANSIGenericStrings<'a, S>
where
<S as ToOwned>::Owned: fmt::Debug,
&'a S: AsRef<[u8]>,
{
fn write_to_any<W: AnyWrite<Wstr = S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
use self::Difference::*;
let first = match self.0.first() {
None => return Ok(()),
Some(f) => f,
};
write!(w, "{}", first.style.prefix())?;
w.write_str(first.string.as_ref())?;
for window in self.0.windows(2) {
match Difference::between(&window[0].style, &window[1].style) {
ExtraStyles(style) => write!(w, "{}", style.prefix())?,
Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?,
NoDifference => { /* Do nothing! */ }
}
w.write_str(&window[1].string)?;
}
// Write the final reset string after all of the ANSIStrings have been
// written, *except* if the last one has no styles, because it would
// have already been written by this point.
if let Some(last) = self.0.last() {
if !last.style.is_plain() {
write!(w, "{}", RESET)?;
}
}
Ok(())
}
}
// ---- tests ----
#[cfg(test)]
mod tests {
pub use super::super::ANSIStrings;
pub use crate::style::Color::*;
pub use crate::style::Style;
#[test]
fn no_control_codes_for_plain() {
let one = Style::default().paint("one");
let two = Style::default().paint("two");
let output = format!("{}", ANSIStrings(&[one, two]));
assert_eq!(&*output, "onetwo");
}
}

View File

@ -0,0 +1,267 @@
//! This is a library for controlling colors and formatting, such as
//! red bold text or blue underlined text, on ANSI terminals.
//!
//!
//! ## Basic usage
//!
//! There are three main types in this crate that you need to be
//! concerned with: [`ANSIString`], [`Style`], and [`Color`].
//!
//! A `Style` holds stylistic information: foreground and background colors,
//! whether the text should be bold, or blinking, or other properties. The
//! [`Color`] enum represents the available colors. And an [`ANSIString`] is a
//! string paired with a [`Style`].
//!
//! [`Color`] is also available as an alias to `Color`.
//!
//! To format a string, call the `paint` method on a `Style` or a `Color`,
//! passing in the string you want to format as the argument. For example,
//! heres how to get some red text:
//!
//! ```
//! use nu_ansi_term::Color::Red;
//!
//! println!("This is in red: {}", Red.paint("a red string"));
//! ```
//!
//! Its important to note that the `paint` method does *not* actually return a
//! string with the ANSI control characters surrounding it. Instead, it returns
//! an [`ANSIString`] value that has a [`Display`] implementation that, when
//! formatted, returns the characters. This allows strings to be printed with a
//! minimum of [`String`] allocations being performed behind the scenes.
//!
//! If you *do* want to get at the escape codes, then you can convert the
//! [`ANSIString`] to a string as you would any other `Display` value:
//!
//! ```
//! use nu_ansi_term::Color::Red;
//!
//! let red_string = Red.paint("a red string").to_string();
//! ```
//!
//!
//! ## Bold, underline, background, and other styles
//!
//! For anything more complex than plain foreground color changes, you need to
//! construct `Style` values themselves, rather than beginning with a `Color`.
//! You can do this by chaining methods based on a new `Style`, created with
//! [`Style::new()`]. Each method creates a new style that has that specific
//! property set. For example:
//!
//! ```
//! use nu_ansi_term::Style;
//!
//! println!("How about some {} and {}?",
//! Style::new().bold().paint("bold"),
//! Style::new().underline().paint("underline"));
//! ```
//!
//! For brevity, these methods have also been implemented for `Color` values,
//! so you can give your styles a foreground color without having to begin with
//! an empty `Style` value:
//!
//! ```
//! use nu_ansi_term::Color::{Blue, Yellow};
//!
//! println!("Demonstrating {} and {}!",
//! Blue.bold().paint("blue bold"),
//! Yellow.underline().paint("yellow underline"));
//!
//! println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!"));
//! ```
//!
//! The complete list of styles you can use are: [`bold`], [`dimmed`], [`italic`],
//! [`underline`], [`blink`], [`reverse`], [`hidden`], [`strikethrough`], and [`on`] for
//! background colors.
//!
//! In some cases, you may find it easier to change the foreground on an
//! existing `Style` rather than starting from the appropriate `Color`.
//! You can do this using the [`fg`] method:
//!
//! ```
//! use nu_ansi_term::Style;
//! use nu_ansi_term::Color::{Blue, Cyan, Yellow};
//!
//! println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!"));
//! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!"));
//! ```
//!
//! You can turn a `Color` into a `Style` with the [`normal`] method.
//! This will produce the exact same `ANSIString` as if you just used the
//! `paint` method on the `Color` directly, but its useful in certain cases:
//! for example, you may have a method that returns `Styles`, and need to
//! represent both the “red bold” and “red, but not bold” styles with values of
//! the same type. The `Style` struct also has a [`Default`] implementation if you
//! want to have a style with *nothing* set.
//!
//! ```
//! use nu_ansi_term::Style;
//! use nu_ansi_term::Color::Red;
//!
//! Red.normal().paint("yet another red string");
//! Style::default().paint("a completely regular string");
//! ```
//!
//!
//! ## Extended colors
//!
//! You can access the extended range of 256 colors by using the `Color::Fixed`
//! variant, which takes an argument of the color number to use. This can be
//! included wherever you would use a `Color`:
//!
//! ```
//! use nu_ansi_term::Color::Fixed;
//!
//! Fixed(134).paint("A sort of light purple");
//! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup");
//! ```
//!
//! The first sixteen of these values are the same as the normal and bold
//! standard color variants. Theres nothing stopping you from using these as
//! `Fixed` colors instead, but theres nothing to be gained by doing so
//! either.
//!
//! You can also access full 24-bit color by using the `Color::RGB` variant,
//! which takes separate `u8` arguments for red, green, and blue:
//!
//! ```
//! use nu_ansi_term::Color::RGB;
//!
//! RGB(70, 130, 180).paint("Steel blue");
//! ```
//!
//! ## Combining successive colored strings
//!
//! The benefit of writing ANSI escape codes to the terminal is that they
//! *stack*: you do not need to end every colored string with a reset code if
//! the text that follows it is of a similar style. For example, if you want to
//! have some blue text followed by some blue bold text, its possible to send
//! the ANSI code for blue, followed by the ANSI code for bold, and finishing
//! with a reset code without having to have an extra one between the two
//! strings.
//!
//! This crate can optimise the ANSI codes that get printed in situations like
//! this, making life easier for your terminal renderer. The [`ANSIStrings`]
//! type takes a slice of several [`ANSIString`] values, and will iterate over
//! each of them, printing only the codes for the styles that need to be updated
//! as part of its formatting routine.
//!
//! The following code snippet uses this to enclose a binary number displayed in
//! red bold text inside some red, but not bold, brackets:
//!
//! ```
//! use nu_ansi_term::Color::Red;
//! use nu_ansi_term::{ANSIString, ANSIStrings};
//!
//! let some_value = format!("{:b}", 42);
//! let strings: &[ANSIString<'static>] = &[
//! Red.paint("["),
//! Red.bold().paint(some_value),
//! Red.paint("]"),
//! ];
//!
//! println!("Value: {}", ANSIStrings(strings));
//! ```
//!
//! There are several things to note here. Firstly, the [`paint`] method can take
//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`]
//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and
//! borrowed strings at the same time. This is used here to display a `String`,
//! the result of the `format!` call, using the same mechanism as some
//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value
//! works in the same way as its singular counterpart, with a [`Display`]
//! implementation that only performs the formatting when required.
//!
//! ## Byte strings
//!
//! This library also supports formatting `\[u8]` byte strings; this supports
//! applications working with text in an unknown encoding. [`Style`] and
//! [`Color`] support painting `\[u8]` values, resulting in an [`ANSIByteString`].
//! This type does not implement [`Display`], as it may not contain UTF-8, but
//! it does provide a method [`write_to`] to write the result to any value that
//! implements [`Write`]:
//!
//! ```
//! use nu_ansi_term::Color::Green;
//!
//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap();
//! ```
//!
//! Similarly, the type [`ANSIByteStrings`] supports writing a list of
//! [`ANSIByteString`] values with minimal escape sequences:
//!
//! ```
//! use nu_ansi_term::Color::Green;
//! use nu_ansi_term::ANSIByteStrings;
//!
//! ANSIByteStrings(&[
//! Green.paint("user data 1\n".as_bytes()),
//! Green.bold().paint("user data 2\n".as_bytes()),
//! ]).write_to(&mut std::io::stdout()).unwrap();
//! ```
//!
//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html
//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html
//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html
//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
//! [`Style`]: struct.Style.html
//! [`Style::new()`]: struct.Style.html#method.new
//! [`Color`]: enum.Color.html
//! [`Color`]: enum.Color.html
//! [`ANSIString`]: type.ANSIString.html
//! [`ANSIStrings`]: type.ANSIStrings.html
//! [`ANSIByteString`]: type.ANSIByteString.html
//! [`ANSIByteStrings`]: type.ANSIByteStrings.html
//! [`write_to`]: type.ANSIByteString.html#method.write_to
//! [`paint`]: type.ANSIByteString.html#method.write_to
//! [`normal`]: enum.Color.html#method.normal
//!
//! [`bold`]: struct.Style.html#method.bold
//! [`dimmed`]: struct.Style.html#method.dimmed
//! [`italic`]: struct.Style.html#method.italic
//! [`underline`]: struct.Style.html#method.underline
//! [`blink`]: struct.Style.html#method.blink
//! [`reverse`]: struct.Style.html#method.reverse
//! [`hidden`]: struct.Style.html#method.hidden
//! [`strikethrough`]: struct.Style.html#method.strikethrough
//! [`fg`]: struct.Style.html#method.fg
//! [`on`]: struct.Style.html#method.on
#![crate_name = "nu_ansi_term"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]
#![warn(missing_copy_implementations)]
#![warn(missing_docs)]
#![warn(trivial_casts, trivial_numeric_casts)]
// #![warn(unused_extern_crates, unused_qualifications)]
#[cfg(target_os = "windows")]
extern crate winapi;
#[cfg(test)]
#[macro_use]
extern crate doc_comment;
#[cfg(test)]
doctest!("../README.md");
mod ansi;
pub use ansi::{Infix, Prefix, Suffix};
mod style;
pub use style::{Color, Style};
mod difference;
mod display;
pub use display::*;
mod write;
mod windows;
pub use windows::*;
mod util;
pub use util::*;
mod debug;

View File

@ -0,0 +1,620 @@
/// A style is a collection of properties that can format a string
/// using ANSI escape codes.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color};
///
/// let style = Style::new().bold().on(Color::Black);
/// println!("{}", style.paint("Bold on black"));
/// ```
#[derive(PartialEq, Clone, Copy)]
#[cfg_attr(
feature = "derive_serde_style",
derive(serde::Deserialize, serde::Serialize)
)]
pub struct Style {
/// The style's foreground color, if it has one.
pub foreground: Option<Color>,
/// The style's background color, if it has one.
pub background: Option<Color>,
/// Whether this style is bold.
pub is_bold: bool,
/// Whether this style is dimmed.
pub is_dimmed: bool,
/// Whether this style is italic.
pub is_italic: bool,
/// Whether this style is underlined.
pub is_underline: bool,
/// Whether this style is blinking.
pub is_blink: bool,
/// Whether this style has reverse colors.
pub is_reverse: bool,
/// Whether this style is hidden.
pub is_hidden: bool,
/// Whether this style is struckthrough.
pub is_strikethrough: bool,
}
impl Style {
/// Creates a new Style with no properties set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new();
/// println!("{}", style.paint("hi"));
/// ```
pub fn new() -> Style {
Style::default()
}
/// Returns a `Style` with the bold property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().bold();
/// println!("{}", style.paint("hey"));
/// ```
pub fn bold(&self) -> Style {
Style {
is_bold: true,
..*self
}
}
/// Returns a `Style` with the dimmed property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().dimmed();
/// println!("{}", style.paint("sup"));
/// ```
pub fn dimmed(&self) -> Style {
Style {
is_dimmed: true,
..*self
}
}
/// Returns a `Style` with the italic property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().italic();
/// println!("{}", style.paint("greetings"));
/// ```
pub fn italic(&self) -> Style {
Style {
is_italic: true,
..*self
}
}
/// Returns a `Style` with the underline property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().underline();
/// println!("{}", style.paint("salutations"));
/// ```
pub fn underline(&self) -> Style {
Style {
is_underline: true,
..*self
}
}
/// Returns a `Style` with the blink property set.
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().blink();
/// println!("{}", style.paint("wazzup"));
/// ```
pub fn blink(&self) -> Style {
Style {
is_blink: true,
..*self
}
}
/// Returns a `Style` with the reverse property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().reverse();
/// println!("{}", style.paint("aloha"));
/// ```
pub fn reverse(&self) -> Style {
Style {
is_reverse: true,
..*self
}
}
/// Returns a `Style` with the hidden property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().hidden();
/// println!("{}", style.paint("ahoy"));
/// ```
pub fn hidden(&self) -> Style {
Style {
is_hidden: true,
..*self
}
}
/// Returns a `Style` with the strikethrough property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// let style = Style::new().strikethrough();
/// println!("{}", style.paint("yo"));
/// ```
pub fn strikethrough(&self) -> Style {
Style {
is_strikethrough: true,
..*self
}
}
/// Returns a `Style` with the foreground color property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color};
///
/// let style = Style::new().fg(Color::Yellow);
/// println!("{}", style.paint("hi"));
/// ```
pub fn fg(&self, foreground: Color) -> Style {
Style {
foreground: Some(foreground),
..*self
}
}
/// Returns a `Style` with the background color property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::{Style, Color};
///
/// let style = Style::new().on(Color::Blue);
/// println!("{}", style.paint("eyyyy"));
/// ```
pub fn on(&self, background: Color) -> Style {
Style {
background: Some(background),
..*self
}
}
/// Return true if this `Style` has no actual styles, and can be written
/// without any control characters.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Style;
///
/// assert_eq!(true, Style::default().is_plain());
/// assert_eq!(false, Style::default().bold().is_plain());
/// ```
pub fn is_plain(self) -> bool {
self == Style::default()
}
}
impl Default for Style {
/// Returns a style with *no* properties set. Formatting text using this
/// style returns the exact same text.
///
/// ```
/// use nu_ansi_term::Style;
/// assert_eq!(None, Style::default().foreground);
/// assert_eq!(None, Style::default().background);
/// assert_eq!(false, Style::default().is_bold);
/// assert_eq!("txt", Style::default().paint("txt").to_string());
/// ```
fn default() -> Style {
Style {
foreground: None,
background: None,
is_bold: false,
is_dimmed: false,
is_italic: false,
is_underline: false,
is_blink: false,
is_reverse: false,
is_hidden: false,
is_strikethrough: false,
}
}
}
// ---- colors ----
/// A color is one specific type of ANSI escape code, and can refer
/// to either the foreground or background color.
///
/// These use the standard numeric sequences.
/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
#[derive(PartialEq, Clone, Copy, Debug)]
#[cfg_attr(
feature = "derive_serde_style",
derive(serde::Deserialize, serde::Serialize)
)]
pub enum Color {
/// Color #0 (foreground code `30`, background code `40`).
///
/// This is not necessarily the background color, and using it as one may
/// render the text hard to read on terminals with dark backgrounds.
Black,
/// Color #0 (foreground code `90`, background code `100`).
DarkGray,
/// Color #1 (foreground code `31`, background code `41`).
Red,
/// Color #1 (foreground code `91`, background code `101`).
LightRed,
/// Color #2 (foreground code `32`, background code `42`).
Green,
/// Color #2 (foreground code `92`, background code `102`).
LightGreen,
/// Color #3 (foreground code `33`, background code `43`).
Yellow,
/// Color #3 (foreground code `93`, background code `103`).
LightYellow,
/// Color #4 (foreground code `34`, background code `44`).
Blue,
/// Color #4 (foreground code `94`, background code `104`).
LightBlue,
/// Color #5 (foreground code `35`, background code `45`).
Purple,
/// Color #5 (foreground code `95`, background code `105`).
LightPurple,
/// Color #5 (foreground code `35`, background code `45`).
Magenta,
/// Color #5 (foreground code `95`, background code `105`).
LightMagenta,
/// Color #6 (foreground code `36`, background code `46`).
Cyan,
/// Color #6 (foreground code `96`, background code `106`).
LightCyan,
/// Color #7 (foreground code `37`, background code `47`).
///
/// As above, this is not necessarily the foreground color, and may be
/// hard to read on terminals with light backgrounds.
White,
/// Color #7 (foreground code `97`, background code `107`).
LightGray,
/// A color number from 0 to 255, for use in 256-color terminal
/// environments.
///
/// - colors 0 to 7 are the `Black` to `White` variants respectively.
/// These colors can usually be changed in the terminal emulator.
/// - colors 8 to 15 are brighter versions of the eight colors above.
/// These can also usually be changed in the terminal emulator, or it
/// could be configured to use the original colors and show the text in
/// bold instead. It varies depending on the program.
/// - colors 16 to 231 contain several palettes of bright colors,
/// arranged in six squares measuring six by six each.
/// - colors 232 to 255 are shades of grey from black to white.
///
/// It might make more sense to look at a [color chart][cc].
///
/// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
Fixed(u8),
/// A 24-bit RGB color, as specified by ISO-8613-3.
RGB(u8, u8, u8),
}
impl Color {
/// Returns a `Style` with the foreground color set to this color.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Red.normal();
/// println!("{}", style.paint("hi"));
/// ```
pub fn normal(self) -> Style {
Style {
foreground: Some(self),
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// bold property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Green.bold();
/// println!("{}", style.paint("hey"));
/// ```
pub fn bold(self) -> Style {
Style {
foreground: Some(self),
is_bold: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// dimmed property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Yellow.dimmed();
/// println!("{}", style.paint("sup"));
/// ```
pub fn dimmed(self) -> Style {
Style {
foreground: Some(self),
is_dimmed: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// italic property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Blue.italic();
/// println!("{}", style.paint("greetings"));
/// ```
pub fn italic(self) -> Style {
Style {
foreground: Some(self),
is_italic: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// underline property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Purple.underline();
/// println!("{}", style.paint("salutations"));
/// ```
pub fn underline(self) -> Style {
Style {
foreground: Some(self),
is_underline: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// blink property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Cyan.blink();
/// println!("{}", style.paint("wazzup"));
/// ```
pub fn blink(self) -> Style {
Style {
foreground: Some(self),
is_blink: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// reverse property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Black.reverse();
/// println!("{}", style.paint("aloha"));
/// ```
pub fn reverse(self) -> Style {
Style {
foreground: Some(self),
is_reverse: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// hidden property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::White.hidden();
/// println!("{}", style.paint("ahoy"));
/// ```
pub fn hidden(self) -> Style {
Style {
foreground: Some(self),
is_hidden: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// strikethrough property set.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::Fixed(244).strikethrough();
/// println!("{}", style.paint("yo"));
/// ```
pub fn strikethrough(self) -> Style {
Style {
foreground: Some(self),
is_strikethrough: true,
..Style::default()
}
}
/// Returns a `Style` with the foreground color set to this color and the
/// background color property set to the given color.
///
/// # Examples
///
/// ```
/// use nu_ansi_term::Color;
///
/// let style = Color::RGB(31, 31, 31).on(Color::White);
/// println!("{}", style.paint("eyyyy"));
/// ```
pub fn on(self, background: Color) -> Style {
Style {
foreground: Some(self),
background: Some(background),
..Style::default()
}
}
}
impl From<Color> for Style {
/// You can turn a `Color` into a `Style` with the foreground color set
/// with the `From` trait.
///
/// ```
/// use nu_ansi_term::{Style, Color};
/// let green_foreground = Style::default().fg(Color::Green);
/// assert_eq!(green_foreground, Color::Green.normal());
/// assert_eq!(green_foreground, Color::Green.into());
/// assert_eq!(green_foreground, Style::from(Color::Green));
/// ```
fn from(color: Color) -> Style {
color.normal()
}
}
#[cfg(test)]
#[cfg(feature = "derive_serde_style")]
mod serde_json_tests {
use super::{Color, Style};
#[test]
fn color_serialization() {
let colors = &[
Color::Red,
Color::Blue,
Color::RGB(123, 123, 123),
Color::Fixed(255),
];
assert_eq!(
serde_json::to_string(&colors).unwrap(),
String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]")
);
}
#[test]
fn color_deserialization() {
let colors = &[
Color::Red,
Color::Blue,
Color::RGB(123, 123, 123),
Color::Fixed(255),
];
for color in colors.into_iter() {
let serialized = serde_json::to_string(&color).unwrap();
let deserialized: Color = serde_json::from_str(&serialized).unwrap();
assert_eq!(color, &deserialized);
}
}
#[test]
fn style_serialization() {
let style = Style::default();
assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
}
}

View File

@ -0,0 +1,80 @@
use crate::display::{ANSIString, ANSIStrings};
use std::ops::Deref;
/// Return a substring of the given ANSIStrings sequence, while keeping the formatting.
pub fn sub_string<'a>(
start: usize,
len: usize,
strs: &ANSIStrings<'a>,
) -> Vec<ANSIString<'static>> {
let mut vec = Vec::new();
let mut pos = start;
let mut len_rem = len;
for i in strs.0.iter() {
let fragment = i.deref();
let frag_len = fragment.len();
if pos >= frag_len {
pos -= frag_len;
continue;
}
if len_rem == 0 {
break;
}
let end = pos + len_rem;
let pos_end = if end >= frag_len { frag_len } else { end };
vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end])));
if end <= frag_len {
break;
}
len_rem -= pos_end - pos;
pos = 0;
}
vec
}
/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`.
pub fn unstyle(strs: &ANSIStrings) -> String {
let mut s = String::new();
for i in strs.0.iter() {
s += &i.deref();
}
s
}
/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`.
pub fn unstyled_len(strs: &ANSIStrings) -> usize {
let mut l = 0;
for i in strs.0.iter() {
l += i.deref().len();
}
l
}
#[cfg(test)]
mod test {
use super::*;
use crate::Color::*;
#[test]
fn test() {
let l = [
Black.paint("first"),
Red.paint("-second"),
White.paint("-third"),
];
let a = ANSIStrings(&l);
assert_eq!(unstyle(&a), "first-second-third");
assert_eq!(unstyled_len(&a), 18);
let l2 = [Black.paint("st"), Red.paint("-second"), White.paint("-t")];
assert_eq!(sub_string(3, 11, &a).as_slice(), &l2);
}
}

View File

@ -0,0 +1,62 @@
/// Enables ANSI code support on Windows 10.
///
/// This uses Windows API calls to alter the properties of the console that
/// the program is running in.
///
/// https://msdn.microsoft.com/en-us/library/windows/desktop/mt638032(v=vs.85).aspx
///
/// Returns a `Result` with the Windows error code if unsuccessful.
#[cfg(windows)]
pub fn enable_ansi_support() -> Result<(), u32> {
// ref: https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING @@ https://archive.is/L7wRJ#76%
use std::ffi::OsStr;
use std::iter::once;
use std::os::windows::ffi::OsStrExt;
use std::ptr::null_mut;
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
use winapi::um::errhandlingapi::GetLastError;
use winapi::um::fileapi::{CreateFileW, OPEN_EXISTING};
use winapi::um::handleapi::INVALID_HANDLE_VALUE;
use winapi::um::winnt::{FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE};
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
unsafe {
// ref: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
// Using `CreateFileW("CONOUT$", ...)` to retrieve the console handle works correctly even if STDOUT and/or STDERR are redirected
let console_out_name: Vec<u16> =
OsStr::new("CONOUT$").encode_wide().chain(once(0)).collect();
let console_handle = CreateFileW(
console_out_name.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_WRITE,
null_mut(),
OPEN_EXISTING,
0,
null_mut(),
);
if console_handle == INVALID_HANDLE_VALUE {
return Err(GetLastError());
}
// ref: https://docs.microsoft.com/en-us/windows/console/getconsolemode
let mut console_mode: u32 = 0;
if 0 == GetConsoleMode(console_handle, &mut console_mode) {
return Err(GetLastError());
}
// VT processing not already enabled?
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
if 0 == SetConsoleMode(
console_handle,
console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING,
) {
return Err(GetLastError());
}
}
}
Ok(())
}

View File

@ -0,0 +1,37 @@
use std::fmt;
use std::io;
pub trait AnyWrite {
type Wstr: ?Sized;
type Error;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>;
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error>;
}
impl<'a> AnyWrite for dyn fmt::Write + 'a {
type Wstr = str;
type Error = fmt::Error;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
fmt::Write::write_fmt(self, fmt)
}
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
fmt::Write::write_str(self, s)
}
}
impl<'a> AnyWrite for dyn io::Write + 'a {
type Wstr = [u8];
type Error = io::Error;
fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> {
io::Write::write_fmt(self, fmt)
}
fn write_str(&mut self, s: &Self::Wstr) -> Result<(), Self::Error> {
io::Write::write_all(self, s)
}
}

View File

@ -5,28 +5,28 @@ description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-cli"
version = "0.27.1"
version = "0.28.0"
[lib]
doctest = false
[dependencies]
nu-command = { version = "0.27.1", path = "../nu-command" }
nu-data = { version = "0.27.1", path = "../nu-data" }
nu-engine = { version = "0.27.1", path = "../nu-engine" }
nu-errors = { version = "0.27.1", path = "../nu-errors" }
nu-json = { version = "0.27.1", path = "../nu-json" }
nu-parser = { version = "0.27.1", path = "../nu-parser" }
nu-plugin = { version = "0.27.1", path = "../nu-plugin" }
nu-protocol = { version = "0.27.1", path = "../nu-protocol" }
nu-source = { version = "0.27.1", path = "../nu-source" }
nu-stream = { version = "0.27.1", path = "../nu-stream" }
nu-table = { version = "0.27.1", path = "../nu-table" }
nu-test-support = { version = "0.27.1", path = "../nu-test-support" }
nu-value-ext = { version = "0.27.1", path = "../nu-value-ext" }
nu-command = { version = "0.28.0", path = "../nu-command" }
nu-data = { version = "0.28.0", path = "../nu-data" }
nu-engine = { version = "0.28.0", path = "../nu-engine" }
nu-errors = { version = "0.28.0", path = "../nu-errors" }
nu-json = { version = "0.28.0", path = "../nu-json" }
nu-parser = { version = "0.28.0", path = "../nu-parser" }
nu-plugin = { version = "0.28.0", path = "../nu-plugin" }
nu-protocol = { version = "0.28.0", path = "../nu-protocol" }
nu-source = { version = "0.28.0", path = "../nu-source" }
nu-stream = { version = "0.28.0", path = "../nu-stream" }
nu-table = { version = "0.28.0", path = "../nu-table" }
nu-test-support = { version = "0.28.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.28.0", path = "../nu-value-ext" }
nu-ansi-term = { version = "0.28.0", path = "../nu-ansi-term" }
Inflector = "0.11"
ansi_term = "0.12.1"
arboard = { version = "1.1.0", optional = true }
async-recursion = "0.3.2"
async-trait = "0.1.42"

View File

@ -31,6 +31,7 @@ use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::{hir::ExternalRedirection, UntaggedValue, Value};
use log::trace;
use std::error::Error;
use std::iter::Iterator;
use std::path::PathBuf;
@ -78,7 +79,7 @@ pub async fn run_script_file(
syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
print_err(reason, &Text::from(""), ctx);
}
let _ = register_plugins(ctx);
@ -106,7 +107,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
print_err(reason, &Text::from(""), ctx);
}
let _ = configure_ctrl_c(ctx);
@ -116,7 +117,19 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
rl.set_helper(helper);
});
// start time for command duration
let startup_commands_start_time = std::time::Instant::now();
// run the startup commands
let _ = run_startup_commands(&mut context, &configuration).await;
// Store cmd duration in an env var
context.scope.add_env_var(
"CMD_DURATION",
format!("{:?}", startup_commands_start_time.elapsed()),
);
trace!(
"startup commands took {:?}",
startup_commands_start_time.elapsed()
);
// Give ourselves a scope to work in
context.scope.enter_scope();
@ -140,7 +153,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
#[cfg(windows)]
{
let _ = ansi_term::enable_ansi_support();
let _ = nu_ansi_term::enable_ansi_support();
}
let mut ctrlcbreak = false;
@ -186,14 +199,14 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
}
}
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
crate::cli::print_err(e, &Text::from(prompt_line), &context);
context.clear_errors();
"> ".to_string()
}
},
Err(e) => {
crate::cli::print_err(e, &Text::from(prompt_line));
crate::cli::print_err(e, &Text::from(prompt_line), &context);
context.clear_errors();
"> ".to_string()
@ -227,6 +240,9 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
session_text.push('\n');
}
// start time for command duration
let cmd_start_time = std::time::Instant::now();
let line = match convert_rustyline_result_to_string(readline) {
LineResult::Success(_) => {
process_script(
@ -241,6 +257,11 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
x => x,
};
// Store cmd duration in an env var
context
.scope
.add_env_var("CMD_DURATION", format!("{:?}", cmd_start_time.elapsed()));
// Check the config to see if we need to update the path
// TODO: make sure config is cached so we don't path this load every call
// FIXME: we probably want to be a bit more graceful if we can't set the environment
@ -253,7 +274,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
}
if let Err(reason) = syncer.autoenv(ctx) {
print_err(reason, &Text::from(""));
print_err(reason, &Text::from(""), ctx);
}
let _ = configure_rustyline_editor(&mut rl, config);
@ -275,9 +296,7 @@ pub async fn cli(mut context: EvaluationContext) -> Result<(), Box<dyn Error>> {
rl.add_history_entry(&line);
let _ = rl.save_history(&history_path);
context.with_host(|_host| {
print_err(err, &Text::from(session_text.clone()));
});
print_err(err, &Text::from(session_text.clone()), &context);
maybe_print_errors(&context, Text::from(session_text.clone()));
}

View File

@ -5,27 +5,27 @@ description = "CLI for nushell"
edition = "2018"
license = "MIT"
name = "nu-command"
version = "0.27.1"
version = "0.28.0"
[lib]
doctest = false
[dependencies]
nu-data = { version = "0.27.1", path = "../nu-data" }
nu-engine = { version = "0.27.1", path = "../nu-engine" }
nu-errors = { version = "0.27.1", path = "../nu-errors" }
nu-json = { version = "0.27.1", path = "../nu-json" }
nu-parser = { version = "0.27.1", path = "../nu-parser" }
nu-plugin = { version = "0.27.1", path = "../nu-plugin" }
nu-protocol = { version = "0.27.1", path = "../nu-protocol" }
nu-source = { version = "0.27.1", path = "../nu-source" }
nu-stream = { version = "0.27.1", path = "../nu-stream" }
nu-table = { version = "0.27.1", path = "../nu-table" }
nu-test-support = { version = "0.27.1", path = "../nu-test-support" }
nu-value-ext = { version = "0.27.1", path = "../nu-value-ext" }
nu-data = { version = "0.28.0", path = "../nu-data" }
nu-engine = { version = "0.28.0", path = "../nu-engine" }
nu-errors = { version = "0.28.0", path = "../nu-errors" }
nu-json = { version = "0.28.0", path = "../nu-json" }
nu-parser = { version = "0.28.0", path = "../nu-parser" }
nu-plugin = { version = "0.28.0", path = "../nu-plugin" }
nu-protocol = { version = "0.28.0", path = "../nu-protocol" }
nu-source = { version = "0.28.0", path = "../nu-source" }
nu-stream = { version = "0.28.0", path = "../nu-stream" }
nu-table = { version = "0.28.0", path = "../nu-table" }
nu-test-support = { version = "0.28.0", path = "../nu-test-support" }
nu-value-ext = { version = "0.28.0", path = "../nu-value-ext" }
nu-ansi-term = { version = "0.28.0", path = "../nu-ansi-term" }
Inflector = "0.11"
ansi_term = "0.12.1"
arboard = { version = "1.1.0", optional = true }
async-recursion = "0.3.2"
async-trait = "0.1.42"
@ -38,6 +38,7 @@ chrono = { version = "0.4.19", features = ["serde"] }
chrono-tz = "0.5.3"
clap = "2.33.3"
codespan-reporting = "0.11.0"
crossterm = { version = "0.19.0", optional = true }
csv = "1.1.3"
ctrlc = { version = "3.1.7", optional = true }
derive-new = "0.5.8"
@ -62,6 +63,7 @@ itertools = "0.10.0"
lazy_static = "1.*"
log = "0.4.14"
meval = "0.2.0"
minus = { version = "3.3.0", optional = true, features = ["async_std_lib", "search"] }
num-bigint = { version = "0.3.1", features = ["serde"] }
num-format = { version = "0.4.0", features = ["with-num-bigint"] }
num-traits = "0.2.14"
@ -130,3 +132,4 @@ stable = []
trash-support = ["trash"]
directories = ["directories-next"]
dirs = ["dirs-next"]
table-pager = ["minus", "crossterm"]

View File

@ -98,6 +98,8 @@ pub(crate) mod reject;
pub(crate) mod rename;
pub(crate) mod reverse;
pub(crate) mod rm;
pub(crate) mod roll;
pub(crate) mod rotate;
pub(crate) mod run_external;
pub(crate) mod save;
pub(crate) mod select;
@ -139,6 +141,7 @@ pub(crate) use autoview::Autoview;
pub(crate) use cd::Cd;
pub(crate) use ansi::Ansi;
pub(crate) use ansi::AnsiStrip;
pub(crate) use append::Command as Append;
pub(crate) use autoenv::Autoenv;
pub(crate) use autoenv_trust::AutoenvTrust;
@ -160,7 +163,7 @@ pub(crate) use def::Def;
pub(crate) use default::Default;
pub(crate) use describe::Describe;
pub(crate) use do_::Do;
pub(crate) use drop::Drop;
pub(crate) use drop::{Drop, DropColumn};
pub(crate) use du::Du;
pub(crate) use each::Each;
pub(crate) use each::EachGroup;
@ -227,7 +230,7 @@ pub(crate) use open::Open;
pub(crate) use parse::Parse;
pub(crate) use path::{
PathBasename, PathCommand, PathDirname, PathExists, PathExpand, PathExtension, PathFilestem,
PathType,
PathJoin, PathType,
};
pub(crate) use pivot::Pivot;
pub(crate) use prepend::Prepend;
@ -244,6 +247,8 @@ pub(crate) use reject::Reject;
pub(crate) use rename::Rename;
pub(crate) use reverse::Reverse;
pub(crate) use rm::Remove;
pub(crate) use roll::{Roll, RollColumn, RollUp};
pub(crate) use rotate::{Rotate, RotateCounterClockwise};
pub(crate) use run_external::RunExternalCommand;
pub(crate) use save::Save;
pub(crate) use select::Command as Select;

View File

@ -1,11 +1,11 @@
use crate::prelude::*;
use ansi_term::Color;
use nu_ansi_term::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
pub struct Ansi;
pub struct Command;
#[derive(Deserialize)]
struct AnsiArgs {
@ -15,7 +15,7 @@ struct AnsiArgs {
}
#[async_trait]
impl WholeStreamCommand for Ansi {
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"ansi"
}
@ -42,9 +42,11 @@ impl WholeStreamCommand for Ansi {
}
fn usage(&self) -> &str {
r#"Output ANSI codes to change color
"Output ANSI codes to change color."
}
For escape sequences:
fn extra_usage(&self) -> &str {
r#"For escape sequences:
Escape: '\x1b[' is not required for --escape parameter
Format: #(;#)m
Example: 1;31m for bold red or 2;37;41m for dimmed white fg with red bg
@ -179,48 +181,116 @@ pub fn str_to_ansi(s: String) -> Option<String> {
"gi" | "green_italic" => Some(Color::Green.italic().prefix().to_string()),
"gd" | "green_dimmed" => Some(Color::Green.dimmed().prefix().to_string()),
"gr" | "green_reverse" => Some(Color::Green.reverse().prefix().to_string()),
"lg" | "light_green" => Some(Color::LightGreen.prefix().to_string()),
"lgb" | "light_green_bold" => Some(Color::LightGreen.bold().prefix().to_string()),
"lgu" | "light_green_underline" => Some(Color::LightGreen.underline().prefix().to_string()),
"lgi" | "light_green_italic" => Some(Color::LightGreen.italic().prefix().to_string()),
"lgd" | "light_green_dimmed" => Some(Color::LightGreen.dimmed().prefix().to_string()),
"lgr" | "light_green_reverse" => Some(Color::LightGreen.reverse().prefix().to_string()),
"r" | "red" => Some(Color::Red.prefix().to_string()),
"rb" | "red_bold" => Some(Color::Red.bold().prefix().to_string()),
"ru" | "red_underline" => Some(Color::Red.underline().prefix().to_string()),
"ri" | "red_italic" => Some(Color::Red.italic().prefix().to_string()),
"rd" | "red_dimmed" => Some(Color::Red.dimmed().prefix().to_string()),
"rr" | "red_reverse" => Some(Color::Red.reverse().prefix().to_string()),
"lr" | "light_red" => Some(Color::LightRed.prefix().to_string()),
"lrb" | "light_red_bold" => Some(Color::LightRed.bold().prefix().to_string()),
"lru" | "light_red_underline" => Some(Color::LightRed.underline().prefix().to_string()),
"lri" | "light_red_italic" => Some(Color::LightRed.italic().prefix().to_string()),
"lrd" | "light_red_dimmed" => Some(Color::LightRed.dimmed().prefix().to_string()),
"lrr" | "light_red_reverse" => Some(Color::LightRed.reverse().prefix().to_string()),
"u" | "blue" => Some(Color::Blue.prefix().to_string()),
"ub" | "blue_bold" => Some(Color::Blue.bold().prefix().to_string()),
"uu" | "blue_underline" => Some(Color::Blue.underline().prefix().to_string()),
"ui" | "blue_italic" => Some(Color::Blue.italic().prefix().to_string()),
"ud" | "blue_dimmed" => Some(Color::Blue.dimmed().prefix().to_string()),
"ur" | "blue_reverse" => Some(Color::Blue.reverse().prefix().to_string()),
"lu" | "light_blue" => Some(Color::LightBlue.prefix().to_string()),
"lub" | "light_blue_bold" => Some(Color::LightBlue.bold().prefix().to_string()),
"luu" | "light_blue_underline" => Some(Color::LightBlue.underline().prefix().to_string()),
"lui" | "light_blue_italic" => Some(Color::LightBlue.italic().prefix().to_string()),
"lud" | "light_blue_dimmed" => Some(Color::LightBlue.dimmed().prefix().to_string()),
"lur" | "light_blue_reverse" => Some(Color::LightBlue.reverse().prefix().to_string()),
"b" | "black" => Some(Color::Black.prefix().to_string()),
"bb" | "black_bold" => Some(Color::Black.bold().prefix().to_string()),
"bu" | "black_underline" => Some(Color::Black.underline().prefix().to_string()),
"bi" | "black_italic" => Some(Color::Black.italic().prefix().to_string()),
"bd" | "black_dimmed" => Some(Color::Black.dimmed().prefix().to_string()),
"br" | "black_reverse" => Some(Color::Black.reverse().prefix().to_string()),
"ligr" | "light_gray" => Some(Color::LightGray.prefix().to_string()),
"ligrb" | "light_gray_bold" => Some(Color::LightGray.bold().prefix().to_string()),
"ligru" | "light_gray_underline" => Some(Color::LightGray.underline().prefix().to_string()),
"ligri" | "light_gray_italic" => Some(Color::LightGray.italic().prefix().to_string()),
"ligrd" | "light_gray_dimmed" => Some(Color::LightGray.dimmed().prefix().to_string()),
"ligrr" | "light_gray_reverse" => Some(Color::LightGray.reverse().prefix().to_string()),
"y" | "yellow" => Some(Color::Yellow.prefix().to_string()),
"yb" | "yellow_bold" => Some(Color::Yellow.bold().prefix().to_string()),
"yu" | "yellow_underline" => Some(Color::Yellow.underline().prefix().to_string()),
"yi" | "yellow_italic" => Some(Color::Yellow.italic().prefix().to_string()),
"yd" | "yellow_dimmed" => Some(Color::Yellow.dimmed().prefix().to_string()),
"yr" | "yellow_reverse" => Some(Color::Yellow.reverse().prefix().to_string()),
"ly" | "light_yellow" => Some(Color::LightYellow.prefix().to_string()),
"lyb" | "light_yellow_bold" => Some(Color::LightYellow.bold().prefix().to_string()),
"lyu" | "light_yellow_underline" => {
Some(Color::LightYellow.underline().prefix().to_string())
}
"lyi" | "light_yellow_italic" => Some(Color::LightYellow.italic().prefix().to_string()),
"lyd" | "light_yellow_dimmed" => Some(Color::LightYellow.dimmed().prefix().to_string()),
"lyr" | "light_yellow_reverse" => Some(Color::LightYellow.reverse().prefix().to_string()),
"p" | "purple" => Some(Color::Purple.prefix().to_string()),
"pb" | "purple_bold" => Some(Color::Purple.bold().prefix().to_string()),
"pu" | "purple_underline" => Some(Color::Purple.underline().prefix().to_string()),
"pi" | "purple_italic" => Some(Color::Purple.italic().prefix().to_string()),
"pd" | "purple_dimmed" => Some(Color::Purple.dimmed().prefix().to_string()),
"pr" | "purple_reverse" => Some(Color::Purple.reverse().prefix().to_string()),
"lp" | "light_purple" => Some(Color::LightPurple.prefix().to_string()),
"lpb" | "light_purple_bold" => Some(Color::LightPurple.bold().prefix().to_string()),
"lpu" | "light_purple_underline" => {
Some(Color::LightPurple.underline().prefix().to_string())
}
"lpi" | "light_purple_italic" => Some(Color::LightPurple.italic().prefix().to_string()),
"lpd" | "light_purple_dimmed" => Some(Color::LightPurple.dimmed().prefix().to_string()),
"lpr" | "light_purple_reverse" => Some(Color::LightPurple.reverse().prefix().to_string()),
"c" | "cyan" => Some(Color::Cyan.prefix().to_string()),
"cb" | "cyan_bold" => Some(Color::Cyan.bold().prefix().to_string()),
"cu" | "cyan_underline" => Some(Color::Cyan.underline().prefix().to_string()),
"ci" | "cyan_italic" => Some(Color::Cyan.italic().prefix().to_string()),
"cd" | "cyan_dimmed" => Some(Color::Cyan.dimmed().prefix().to_string()),
"cr" | "cyan_reverse" => Some(Color::Cyan.reverse().prefix().to_string()),
"lc" | "light_cyan" => Some(Color::LightCyan.prefix().to_string()),
"lcb" | "light_cyan_bold" => Some(Color::LightCyan.bold().prefix().to_string()),
"lcu" | "light_cyan_underline" => Some(Color::LightCyan.underline().prefix().to_string()),
"lci" | "light_cyan_italic" => Some(Color::LightCyan.italic().prefix().to_string()),
"lcd" | "light_cyan_dimmed" => Some(Color::LightCyan.dimmed().prefix().to_string()),
"lcr" | "light_cyan_reverse" => Some(Color::LightCyan.reverse().prefix().to_string()),
"w" | "white" => Some(Color::White.prefix().to_string()),
"wb" | "white_bold" => Some(Color::White.bold().prefix().to_string()),
"wu" | "white_underline" => Some(Color::White.underline().prefix().to_string()),
"wi" | "white_italic" => Some(Color::White.italic().prefix().to_string()),
"wd" | "white_dimmed" => Some(Color::White.dimmed().prefix().to_string()),
"wr" | "white_reverse" => Some(Color::White.reverse().prefix().to_string()),
"dgr" | "dark_gray" => Some(Color::DarkGray.prefix().to_string()),
"dgrb" | "dark_gray_bold" => Some(Color::DarkGray.bold().prefix().to_string()),
"dgru" | "dark_gray_underline" => Some(Color::DarkGray.underline().prefix().to_string()),
"dgri" | "dark_gray_italic" => Some(Color::DarkGray.italic().prefix().to_string()),
"dgrd" | "dark_gray_dimmed" => Some(Color::DarkGray.dimmed().prefix().to_string()),
"dgrr" | "dark_gray_reverse" => Some(Color::DarkGray.reverse().prefix().to_string()),
"reset" => Some("\x1b[0m".to_owned()),
// Reference for ansi codes https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
@ -228,8 +298,6 @@ pub fn str_to_ansi(s: String) -> Option<String> {
// For setting title like `echo [$(char title) $(pwd) $(char bel)] | str collect`
"title" => Some("\x1b]2;".to_string()), // ESC]2; xterm sets window title using OSC syntax escapes
"bel" => Some('\x07'.to_string()), // Terminal Bell
"backspace" => Some('\x08'.to_string()), // Backspace
// Ansi Erase Sequences
"clear_screen" => Some("\x1b[J".to_string()), // clears the screen
@ -279,13 +347,13 @@ pub fn str_to_ansi(s: String) -> Option<String> {
#[cfg(test)]
mod tests {
use super::Ansi;
use super::Command;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(Ansi {})
test_examples(Command {})
}
}

View File

@ -0,0 +1,5 @@
mod command;
mod strip;
pub use command::Command as Ansi;
pub use strip::SubCommand as AnsiStrip;

View File

@ -0,0 +1,121 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tag;
use strip_ansi_escapes::strip;
pub struct SubCommand;
#[derive(Deserialize)]
struct Arguments {
rest: Vec<ColumnPath>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"ansi strip"
}
fn signature(&self) -> Signature {
Signature::build("ansi strip").rest(
SyntaxShape::ColumnPath,
"optionally, remove ansi sequences by column paths",
)
}
fn usage(&self) -> &str {
"strip ansi escape sequences from string"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate(args).await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "strip ansi escape sequences from string",
example: "echo [$(ansi gb) 'hello' $(ansi reset)] | str collect | ansi strip",
result: None,
}]
}
}
async fn operate(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (Arguments { rest }, input) = args.process().await?;
let column_paths: Vec<_> = rest;
Ok(input
.map(move |v| {
if column_paths.is_empty() {
ReturnSuccess::value(action(&v, v.tag())?)
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action(old, old.tag())),
)?;
}
ReturnSuccess::value(ret)
}
})
.to_output_stream())
}
fn action(input: &Value, tag: impl Into<Tag>) -> Result<Value, ShellError> {
match &input.value {
UntaggedValue::Primitive(Primitive::String(astring)) => {
let stripped_string = {
if let Ok(bytes) = strip(&astring) {
String::from_utf8_lossy(&bytes).to_string()
} else {
astring.to_string()
}
};
Ok(UntaggedValue::string(stripped_string).into_value(tag))
}
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not string",
got,
tag.into().span,
))
}
}
}
#[cfg(test)]
mod tests {
use super::ShellError;
use super::{action, SubCommand};
use nu_protocol::UntaggedValue;
use nu_source::Tag;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
#[test]
fn test_stripping() {
let input_string =
UntaggedValue::string("\u{1b}[3;93;41mHello\u{1b}[0m \u{1b}[1;32mNu \u{1b}[1;35mWorld")
.into_untagged_value();
let expected = UntaggedValue::string("Hello Nu World").into_untagged_value();
let actual = action(&input_string, Tag::unknown()).unwrap();
assert_eq!(actual, expected);
}
}

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"Append a row to the table"
"Append a row to the table."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -51,8 +51,12 @@ impl WholeStreamCommand for Autoenv {
"autoenv"
}
fn usage(&self) -> &str {
"Manage directory specific environment variables and scripts."
}
fn extra_usage(&self) -> &str {
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
r#"Manage directory specific environment variables and scripts. Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
r#"Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
The file can contain several optional sections:
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
@ -64,7 +68,7 @@ The file can contain several optional sections:
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&Autoenv, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Autoenv, &args.scope)).into_value(Tag::unknown()),
)))
}

View File

@ -240,7 +240,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
key.to_string(),
TextStyle::new()
.alignment(nu_table::Alignment::Left)
.fg(ansi_term::Color::Green)
.fg(nu_ansi_term::Color::Green)
.bold(Some(true)),
),
nu_table::StyledString::new(
@ -253,7 +253,7 @@ pub async fn autoview(context: RunnableContext) -> Result<OutputStream, ShellErr
let table =
nu_table::Table::new(vec![], entries, nu_table::Theme::compact());
nu_table::draw_table(&table, term_width, &color_hm);
println!("{}", nu_table::draw_table(&table, term_width, &color_hm));
}
Value {
value: UntaggedValue::Primitive(Primitive::Nothing),

View File

@ -45,7 +45,7 @@ impl WholeStreamCommand for Benchmark {
}
fn usage(&self) -> &str {
"Runs a block and returns the time it took to execute it"
"Runs a block and returns the time it took to execute it."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -24,7 +24,7 @@ impl WholeStreamCommand for BuildString {
}
fn usage(&self) -> &str {
"Builds a string from the arguments"
"Builds a string from the arguments."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -29,7 +29,7 @@ impl WholeStreamCommand for Char {
}
fn usage(&self) -> &str {
"Output special characters (eg. 'newline')"
"Output special characters (eg. 'newline')."
}
fn examples(&self) -> Vec<Example> {
@ -130,6 +130,9 @@ fn str_to_character(s: &str) -> Option<String> {
"snowy" | "snow" => Some("❄️".to_string()),
"thunderstorm" | "thunder" => Some("🌩️".to_string()),
"bel" => Some('\x07'.to_string()), // Terminal Bell
"backspace" => Some('\x08'.to_string()), // Backspace
_ => None,
}
}

View File

@ -28,7 +28,7 @@ impl WholeStreamCommand for Chart {
}
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&Chart, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Chart, &args.scope)).into_value(Tag::unknown()),
))))
}
}

View File

@ -17,7 +17,7 @@ impl WholeStreamCommand for Clear {
}
fn usage(&self) -> &str {
"Clears the terminal"
"Clears the terminal."
}
async fn run(&self, _: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -19,7 +19,7 @@ impl WholeStreamCommand for Clip {
}
fn usage(&self) -> &str {
"Copy the contents of the pipeline to the copy/paste buffer"
"Copy the contents of the pipeline to the copy/paste buffer."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -24,7 +24,7 @@ impl WholeStreamCommand for Compact {
}
fn usage(&self) -> &str {
"Creates a table with non-empty rows"
"Creates a table with non-empty rows."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -16,12 +16,12 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"Apply date function"
"Apply date function."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&Command, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
)))
}
}

View File

@ -21,7 +21,7 @@ impl WholeStreamCommand for Debug {
}
fn usage(&self) -> &str {
"Print the Rust debug representation of the values"
"Print the Rust debug representation of the values."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -111,8 +111,10 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(StrScreamingSnakeCase),
whole_stream_command(BuildString),
whole_stream_command(Ansi),
whole_stream_command(AnsiStrip),
whole_stream_command(Char),
// Column manipulation
whole_stream_command(DropColumn),
whole_stream_command(Move),
whole_stream_command(Reject),
whole_stream_command(Select),
@ -161,6 +163,11 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(Pivot),
whole_stream_command(Headers),
whole_stream_command(Reduce),
whole_stream_command(Roll),
whole_stream_command(RollColumn),
whole_stream_command(RollUp),
whole_stream_command(Rotate),
whole_stream_command(RotateCounterClockwise),
// Data processing
whole_stream_command(Histogram),
whole_stream_command(Autoenv),
@ -228,6 +235,7 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
whole_stream_command(PathExpand),
whole_stream_command(PathExtension),
whole_stream_command(PathFilestem),
whole_stream_command(PathJoin),
whole_stream_command(PathType),
// Url
whole_stream_command(UrlCommand),

View File

@ -29,7 +29,7 @@ impl WholeStreamCommand for Do {
}
fn usage(&self) -> &str {
"Runs a block, optionally ignoring errors"
"Runs a block, optionally ignoring errors."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -0,0 +1,86 @@
use crate::prelude::*;
use nu_data::base::select_fields;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape};
use nu_source::Tagged;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments {
columns: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"drop column"
}
fn signature(&self) -> Signature {
Signature::build("drop column").optional(
"columns",
SyntaxShape::Number,
"starting from the end, the number of columns to remove",
)
}
fn usage(&self) -> &str {
"Remove the last number of columns. If you want to remove columns by name, try 'reject'."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
drop(args).await
}
fn examples(&self) -> Vec<Example> {
use nu_protocol::{row, Value};
vec![Example {
description: "Remove the last column of a table",
example: "echo [[lib, extension]; [nu-lib, rs] [nu-core, rb]] | drop column",
result: Some(vec![
row! { "lib".into() => Value::from("nu-lib") },
row! { "lib".into() => Value::from("nu-core") },
]),
}]
}
}
async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (Arguments { columns }, input) = args.process().await?;
let to_drop = if let Some(quantity) = columns {
*quantity as usize
} else {
1
};
Ok(input
.map(move |item| {
let headers = item.data_descriptors();
let descs = match headers.len() {
0 => &headers[..],
n if to_drop > n => &[],
n => &headers[..n - to_drop],
};
select_fields(&item, descs, item.tag())
})
.map(ReturnSuccess::value)
.to_output_stream())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -4,15 +4,15 @@ use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_source::Tagged;
pub struct Drop;
pub struct Command;
#[derive(Deserialize)]
pub struct DropArgs {
pub struct Arguments {
rows: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for Drop {
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"drop"
}
@ -26,7 +26,7 @@ impl WholeStreamCommand for Drop {
}
fn usage(&self) -> &str {
"Remove the last number of rows. If you want to remove columns, try 'reject'."
"Remove the last number of rows or columns."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
@ -53,7 +53,7 @@ impl WholeStreamCommand for Drop {
}
async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (DropArgs { rows }, input) = args.process().await?;
let (Arguments { rows }, input) = args.process().await?;
let v: Vec<_> = input.into_vec().await;
let rows_to_drop = if let Some(quantity) = rows {
@ -76,16 +76,3 @@ async fn drop(args: CommandArgs) -> Result<OutputStream, ShellError> {
futures::stream::iter(iter).to_output_stream()
})
}
#[cfg(test)]
mod tests {
use super::Drop;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(Drop {})
}
}

View File

@ -0,0 +1,5 @@
mod column;
mod command;
pub use column::SubCommand as DropColumn;
pub use command::Command as Drop;

View File

@ -68,7 +68,7 @@ impl WholeStreamCommand for Du {
}
fn usage(&self) -> &str {
"Find disk usage sizes of specified items"
"Find disk usage sizes of specified items."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -32,7 +32,7 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"Check for empty values"
"Check for empty values."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -39,14 +39,16 @@ impl WholeStreamCommand for Enter {
}
fn usage(&self) -> &str {
r#"Create a new shell and begin at this path.
Multiple encodings are supported for reading text files by using
"Create a new shell and begin at this path."
}
fn extra_usage(&self) -> &str {
r#"Multiple encodings are supported for reading text files by using
the '--encoding <encoding>' parameter. Here is an example of a few:
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
For a more complete list of encodings please refer to the encoding_rs
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -29,7 +29,7 @@ impl WholeStreamCommand for Exec {
}
fn usage(&self) -> &str {
"Execute command"
"Execute command."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -1,6 +1,6 @@
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{CommandAction, ReturnSuccess, Signature};
use nu_protocol::{CommandAction, ReturnSuccess, Signature, SyntaxShape};
pub struct Exit;
@ -11,11 +11,17 @@ impl WholeStreamCommand for Exit {
}
fn signature(&self) -> Signature {
Signature::build("exit").switch("now", "exit out of the shell immediately", Some('n'))
Signature::build("exit")
.optional(
"code",
SyntaxShape::Number,
"Status code to return if this was the last shell or --now was specified",
)
.switch("now", "Exit out of the shell immediately", Some('n'))
}
fn usage(&self) -> &str {
"Exit the current shell (or all shells)"
"Exit the current shell (or all shells)."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
@ -41,10 +47,16 @@ impl WholeStreamCommand for Exit {
pub async fn exit(args: CommandArgs) -> Result<OutputStream, ShellError> {
let args = args.evaluate_once().await?;
let command_action = if args.call_info.args.has("now") {
CommandAction::Exit
let code = if let Some(value) = args.call_info.args.nth(0) {
value.as_i32()?
} else {
CommandAction::LeaveShell
0
};
let command_action = if args.call_info.args.has("now") {
CommandAction::Exit(code)
} else {
CommandAction::LeaveShell(code)
};
Ok(OutputStream::one(ReturnSuccess::action(command_action)))

View File

@ -16,12 +16,12 @@ impl WholeStreamCommand for From {
}
fn usage(&self) -> &str {
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)"
"Parse content (string or binary) as a table (input format based on subcommand, like csv, ini, json, toml)."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&From, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&From, &args.scope)).into_value(Tag::unknown()),
)))
}
}

View File

@ -8,7 +8,7 @@ pub struct FromCSV;
#[derive(Deserialize)]
pub struct FromCSVArgs {
headerless: bool,
noheaders: bool,
separator: Option<Value>,
}
@ -27,9 +27,9 @@ impl WholeStreamCommand for FromCSV {
Some('s'),
)
.switch(
"headerless",
"noheaders",
"don't treat the first row as column names",
None,
Some('n'),
)
}
@ -50,7 +50,12 @@ impl WholeStreamCommand for FromCSV {
},
Example {
description: "Convert comma-separated data to a table, ignoring headers",
example: "open data.txt | from csv --headerless",
example: "open data.txt | from csv --noheaders",
result: None,
},
Example {
description: "Convert comma-separated data to a table, ignoring headers",
example: "open data.txt | from csv -n",
result: None,
},
Example {
@ -67,7 +72,7 @@ async fn from_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (
FromCSVArgs {
headerless,
noheaders,
separator,
},
input,
@ -95,7 +100,7 @@ async fn from_csv(args: CommandArgs) -> Result<OutputStream, ShellError> {
_ => ',',
};
from_delimited_data(headerless, sep, "CSV", input, name).await
from_delimited_data(noheaders, sep, "CSV", input, name).await
}
#[cfg(test)]

View File

@ -5,18 +5,18 @@ use nu_protocol::{TaggedDictBuilder, UntaggedValue, Value};
fn from_delimited_string_to_value(
s: String,
headerless: bool,
noheaders: bool,
separator: char,
tag: impl Into<Tag>,
) -> Result<Value, csv::Error> {
let mut reader = ReaderBuilder::new()
.has_headers(!headerless)
.has_headers(!noheaders)
.delimiter(separator as u8)
.from_reader(s.as_bytes());
let tag = tag.into();
let span = tag.span;
let headers = if headerless {
let headers = if noheaders {
(1..=reader.headers()?.len())
.map(|i| format!("Column{}", i))
.collect::<Vec<String>>()
@ -46,7 +46,7 @@ fn from_delimited_string_to_value(
}
pub async fn from_delimited_data(
headerless: bool,
noheaders: bool,
sep: char,
format_name: &'static str,
input: InputStream,
@ -56,7 +56,7 @@ pub async fn from_delimited_data(
let concat_string = input.collect_string(name_tag.clone()).await?;
let sample_lines = concat_string.item.lines().take(3).collect_vec().join("\n");
match from_delimited_string_to_value(concat_string.item, headerless, sep, name_tag.clone()) {
match from_delimited_string_to_value(concat_string.item, noheaders, sep, name_tag.clone()) {
Ok(x) => match x {
Value {
value: UntaggedValue::Table(list),

View File

@ -10,7 +10,7 @@ pub struct FromODS;
#[derive(Deserialize)]
pub struct FromODSArgs {
headerless: bool,
noheaders: bool,
}
#[async_trait]
@ -21,9 +21,9 @@ impl WholeStreamCommand for FromODS {
fn signature(&self) -> Signature {
Signature::build("from ods").switch(
"headerless",
"noheaders",
"don't treat the first row as column names",
None,
Some('n'),
)
}
@ -42,7 +42,7 @@ async fn from_ods(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (
FromODSArgs {
headerless: _headerless,
noheaders: _noheaders,
},
input,
) = args.process().await?;

View File

@ -10,7 +10,7 @@ pub struct FromSSV;
#[derive(Deserialize)]
pub struct FromSSVArgs {
headerless: bool,
noheaders: bool,
#[serde(rename(deserialize = "aligned-columns"))]
aligned_columns: bool,
#[serde(rename(deserialize = "minimum-spaces"))]
@ -29,9 +29,9 @@ impl WholeStreamCommand for FromSSV {
fn signature(&self) -> Signature {
Signature::build(STRING_REPRESENTATION)
.switch(
"headerless",
"noheaders",
"don't treat the first row as column names",
None,
Some('n'),
)
.switch("aligned-columns", "assume columns are aligned", Some('a'))
.named(
@ -196,14 +196,14 @@ fn parse_separated_columns<'a>(
fn string_to_table(
s: &str,
headerless: bool,
noheaders: bool,
aligned_columns: bool,
split_at: usize,
) -> Vec<Vec<(String, String)>> {
let mut lines = s.lines().filter(|l| !l.trim().is_empty());
let separator = " ".repeat(std::cmp::max(split_at, 1));
let (ls, header_options) = if headerless {
let (ls, header_options) = if noheaders {
(lines, HeaderOptions::WithoutHeaders)
} else {
match lines.next() {
@ -223,13 +223,13 @@ fn string_to_table(
fn from_ssv_string_to_value(
s: &str,
headerless: bool,
noheaders: bool,
aligned_columns: bool,
split_at: usize,
tag: impl Into<Tag>,
) -> Value {
let tag = tag.into();
let rows = string_to_table(s, headerless, aligned_columns, split_at)
let rows = string_to_table(s, noheaders, aligned_columns, split_at)
.iter()
.map(|row| {
let mut tagged_dict = TaggedDictBuilder::new(&tag);
@ -251,7 +251,7 @@ async fn from_ssv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let (
FromSSVArgs {
headerless,
noheaders,
aligned_columns,
minimum_spaces,
},
@ -266,7 +266,7 @@ async fn from_ssv(args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(
match from_ssv_string_to_value(
&concat_string.item,
headerless,
noheaders,
aligned_columns,
split_at,
name.clone(),
@ -323,7 +323,7 @@ mod tests {
}
#[test]
fn it_uses_first_row_as_data_when_headerless() {
fn it_uses_first_row_as_data_when_noheaders() {
let input = r#"
a b
1 2
@ -435,7 +435,7 @@ mod tests {
}
#[test]
fn it_handles_empty_values_when_headerless_and_aligned_columns() {
fn it_handles_empty_values_when_noheaders_and_aligned_columns() {
let input = r#"
a multi-word value b d
1 3-3 4
@ -479,11 +479,11 @@ mod tests {
kubernetes-ro component=apiserver,provider=kubernetes <none> 172.30.0.1 80/TCP
"#;
let aligned_columns_headerless = string_to_table(input, true, true, 2);
let separator_headerless = string_to_table(input, true, false, 2);
let aligned_columns_noheaders = string_to_table(input, true, true, 2);
let separator_noheaders = string_to_table(input, true, false, 2);
let aligned_columns_with_headers = string_to_table(input, false, true, 2);
let separator_with_headers = string_to_table(input, false, false, 2);
assert_eq!(aligned_columns_headerless, separator_headerless);
assert_eq!(aligned_columns_noheaders, separator_noheaders);
assert_eq!(aligned_columns_with_headers, separator_with_headers);
}

View File

@ -8,7 +8,7 @@ pub struct FromTSV;
#[derive(Deserialize)]
pub struct FromTSVArgs {
headerless: bool,
noheaders: bool,
}
#[async_trait]
@ -19,9 +19,9 @@ impl WholeStreamCommand for FromTSV {
fn signature(&self) -> Signature {
Signature::build("from tsv").switch(
"headerless",
"noheaders",
"don't treat the first row as column names",
None,
Some('n'),
)
}
@ -36,9 +36,9 @@ impl WholeStreamCommand for FromTSV {
async fn from_tsv(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let (FromTSVArgs { headerless }, input) = args.process().await?;
let (FromTSVArgs { noheaders }, input) = args.process().await?;
from_delimited_data(headerless, '\t', "TSV", input, name).await
from_delimited_data(noheaders, '\t', "TSV", input, name).await
}
#[cfg(test)]

View File

@ -10,7 +10,7 @@ pub struct FromXLSX;
#[derive(Deserialize)]
pub struct FromXLSXArgs {
headerless: bool,
noheaders: bool,
}
#[async_trait]
@ -21,9 +21,9 @@ impl WholeStreamCommand for FromXLSX {
fn signature(&self) -> Signature {
Signature::build("from xlsx").switch(
"headerless",
"noheaders",
"don't treat the first row as column names",
None,
Some('n'),
)
}
@ -41,7 +41,7 @@ async fn from_xlsx(args: CommandArgs) -> Result<OutputStream, ShellError> {
let span = tag.span;
let (
FromXLSXArgs {
headerless: _headerless,
noheaders: _noheaders,
},
input,
) = args.process().await?;

View File

@ -24,7 +24,7 @@ impl WholeStreamCommand for Command {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&Command, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
)))
}
}

View File

@ -19,7 +19,7 @@ impl WholeStreamCommand for Headers {
}
fn usage(&self) -> &str {
"Use the first row of the table as column names"
"Use the first row of the table as column names."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -161,7 +161,7 @@ async fn help(args: CommandArgs) -> Result<OutputStream, ShellError> {
let command_name = format!("{} {}", rest[0].item, rest[1].item);
if let Some(command) = scope.get_command(&command_name) {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &scope))
UntaggedValue::string(get_full_help(command.stream_command(), &scope))
.into_value(Tag::unknown()),
)))
} else {
@ -169,7 +169,7 @@ async fn help(args: CommandArgs) -> Result<OutputStream, ShellError> {
}
} else if let Some(command) = scope.get_command(&rest[0].item) {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(command.stream_command(), &scope))
UntaggedValue::string(get_full_help(command.stream_command(), &scope))
.into_value(Tag::unknown()),
)))
} else {

View File

@ -23,7 +23,7 @@ impl WholeStreamCommand for IntoInt {
}
fn usage(&self) -> &str {
"Convert value to integer"
"Convert value to integer."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -26,7 +26,7 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"Keep the number of rows only"
"Keep the number of rows only."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -16,12 +16,12 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"Use mathematical functions as aggregate functions on a list of numbers or tables"
"Use mathematical functions as aggregate functions on a list of numbers or tables."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&Command, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
))))
}
}
@ -154,7 +154,7 @@ mod tests {
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
]),
Ok(row![
"col1".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string")),
"col1".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string")),
"col2".to_owned() => decimal(BigDecimal::from_str("1.118033988749894848204586834365638117720309179805762862135448622705260462818902449707207204189391137").expect("Could not convert to decimal from string"))
]),
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),

View File

@ -31,7 +31,7 @@ impl WholeStreamCommand for Nth {
}
fn usage(&self) -> &str {
"Return or skip only the selected rows"
"Return or skip only the selected rows."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -101,7 +101,7 @@ impl WholeStreamCommand for SubCommand {
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&SubCommand, &scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&SubCommand, &scope)).into_value(Tag::unknown()),
)))
}
}

View File

@ -46,14 +46,16 @@ impl WholeStreamCommand for Open {
}
fn usage(&self) -> &str {
r#"Load a file into a cell, convert to table if possible (avoid by appending '--raw').
Multiple encodings are supported for reading text files by using
"Load a file into a cell, convert to table if possible (avoid by appending '--raw')."
}
fn extra_usage(&self) -> &str {
r#"Multiple encodings are supported for reading text files by using
the '--encoding <encoding>' parameter. Here is an example of a few:
big5, euc-jp, euc-kr, gbk, iso-8859-1, utf-16, cp1252, latin5
For a more complete list of encodings please refer to the encoding_rs
documentation link at https://docs.rs/encoding_rs/0.8.23/encoding_rs/#statics"#
documentation link at https://docs.rs/encoding_rs/0.8.28/encoding_rs/#statics"#
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -1,4 +1,4 @@
use super::{operate, DefaultArguments};
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
@ -14,6 +14,12 @@ struct PathBasenameArguments {
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathBasenameArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathBasename {
fn name(&self) -> &str {
@ -38,13 +44,7 @@ impl WholeStreamCommand for PathBasename {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (PathBasenameArguments { replace, rest }, input) = args.process().await?;
let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
let args = Arc::new(PathBasenameArguments { replace, rest });
operate(input, &action, tag.span, args).await
}
@ -85,9 +85,9 @@ impl WholeStreamCommand for PathBasename {
}
}
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
fn action(path: &Path, args: &PathBasenameArguments) -> UntaggedValue {
match args.replace {
Some(ref basename) => UntaggedValue::filepath(path.with_file_name(basename)),
Some(ref basename) => UntaggedValue::filepath(path.with_file_name(&basename.item)),
None => UntaggedValue::string(match path.file_name() {
Some(filename) => filename.to_string_lossy(),
None => "".into(),

View File

@ -16,12 +16,12 @@ impl WholeStreamCommand for Path {
}
fn usage(&self) -> &str {
"Explore and manipulate paths"
"Explore and manipulate paths."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&Path, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Path, &args.scope)).into_value(Tag::unknown()),
)))
}
}

View File

@ -1,4 +1,4 @@
use super::{operate, DefaultArguments};
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
@ -16,6 +16,12 @@ struct PathDirnameArguments {
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathDirnameArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathDirname {
fn name(&self) -> &str {
@ -53,12 +59,10 @@ impl WholeStreamCommand for PathDirname {
},
input,
) = args.process().await?;
let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: None,
suffix: None,
num_levels: num_levels.map(|v| v.item),
paths: rest,
let args = Arc::new(PathDirnameArguments {
replace,
num_levels,
rest,
});
operate(input, &action, tag.span, args).await
}
@ -113,8 +117,8 @@ impl WholeStreamCommand for PathDirname {
}
}
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
let num_levels = args.num_levels.unwrap_or(1);
fn action(path: &Path, args: &PathDirnameArguments) -> UntaggedValue {
let num_levels = args.num_levels.as_ref().map_or(1, |tagged| tagged.item);
let mut dirname = path;
let mut reached_top = false; // end early if somebody passes -n 99999999
@ -132,9 +136,9 @@ fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
Some(ref newdir) => {
let remainder = path.strip_prefix(dirname).unwrap_or(dirname);
if !remainder.as_os_str().is_empty() {
UntaggedValue::filepath(Path::new(newdir).join(remainder))
UntaggedValue::filepath(Path::new(&newdir.item).join(remainder))
} else {
UntaggedValue::filepath(Path::new(newdir))
UntaggedValue::filepath(Path::new(&newdir.item))
}
}
None => UntaggedValue::filepath(dirname),

View File

@ -1,4 +1,4 @@
use super::{operate, DefaultArguments};
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
@ -12,6 +12,12 @@ struct PathExistsArguments {
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathExistsArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathExists {
fn name(&self) -> &str {
@ -30,13 +36,7 @@ impl WholeStreamCommand for PathExists {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (PathExistsArguments { rest }, input) = args.process().await?;
let args = Arc::new(DefaultArguments {
replace: None,
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
let args = Arc::new(PathExistsArguments { rest });
operate(input, &action, tag.span, args).await
}
@ -59,7 +59,7 @@ impl WholeStreamCommand for PathExists {
}
}
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
fn action(path: &Path, _args: &PathExistsArguments) -> UntaggedValue {
UntaggedValue::boolean(path.exists())
}

View File

@ -1,4 +1,4 @@
use super::{operate, DefaultArguments};
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
@ -12,6 +12,12 @@ struct PathExpandArguments {
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathExpandArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathExpand {
fn name(&self) -> &str {
@ -30,13 +36,7 @@ impl WholeStreamCommand for PathExpand {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (PathExpandArguments { rest }, input) = args.process().await?;
let args = Arc::new(DefaultArguments {
replace: None,
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
let args = Arc::new(PathExpandArguments { rest });
operate(input, &action, tag.span, args).await
}
@ -61,7 +61,7 @@ impl WholeStreamCommand for PathExpand {
}
}
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
fn action(path: &Path, _args: &PathExpandArguments) -> UntaggedValue {
let ps = path.to_string_lossy();
let expanded = shellexpand::tilde(&ps);
let path: &Path = expanded.as_ref().as_ref();

View File

@ -1,4 +1,4 @@
use super::{operate, DefaultArguments};
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
@ -14,6 +14,12 @@ struct PathExtensionArguments {
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathExtensionArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathExtension {
fn name(&self) -> &str {
@ -38,13 +44,7 @@ impl WholeStreamCommand for PathExtension {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (PathExtensionArguments { replace, rest }, input) = args.process().await?;
let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
let args = Arc::new(PathExtensionArguments { replace, rest });
operate(input, &action, tag.span, args).await
}
@ -74,9 +74,9 @@ impl WholeStreamCommand for PathExtension {
}
}
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
fn action(path: &Path, args: &PathExtensionArguments) -> UntaggedValue {
match args.replace {
Some(ref extension) => UntaggedValue::filepath(path.with_extension(extension)),
Some(ref extension) => UntaggedValue::filepath(path.with_extension(&extension.item)),
None => UntaggedValue::string(match path.extension() {
Some(extension) => extension.to_string_lossy(),
None => "".into(),

View File

@ -1,4 +1,4 @@
use super::{operate, DefaultArguments};
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
@ -16,6 +16,12 @@ struct PathFilestemArguments {
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathFilestemArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathFilestem {
fn name(&self) -> &str {
@ -53,19 +59,18 @@ impl WholeStreamCommand for PathFilestem {
let tag = args.call_info.name_tag.clone();
let (
PathFilestemArguments {
replace,
prefix,
suffix,
replace,
rest,
},
input,
) = args.process().await?;
let args = Arc::new(DefaultArguments {
replace: replace.map(|v| v.item),
prefix: prefix.map(|v| v.item),
suffix: suffix.map(|v| v.item),
num_levels: None,
paths: rest,
let args = Arc::new(PathFilestemArguments {
prefix,
suffix,
replace,
rest,
});
operate(input, &action, tag.span, args).await
}
@ -113,14 +118,14 @@ impl WholeStreamCommand for PathFilestem {
}
}
fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
fn action(path: &Path, args: &PathFilestemArguments) -> UntaggedValue {
let basename = match path.file_name() {
Some(name) => name.to_string_lossy().to_string(),
None => "".to_string(),
};
let suffix = match args.suffix {
Some(ref suf) => match basename.rmatch_indices(suf).next() {
Some(ref suf) => match basename.rmatch_indices(&suf.item).next() {
Some((i, _)) => basename.split_at(i).1.to_string(),
None => "".to_string(),
},
@ -132,7 +137,7 @@ fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
};
let prefix = match args.prefix {
Some(ref pre) => match basename.matches(pre).next() {
Some(ref pre) => match basename.matches(&pre.item).next() {
Some(m) => basename.split_at(m.len()).0.to_string(),
None => "".to_string(),
},
@ -151,7 +156,7 @@ fn action(path: &Path, args: Arc<DefaultArguments>) -> UntaggedValue {
match args.replace {
Some(ref replace) => {
let new_name = prefix + replace + &suffix;
let new_name = prefix + &replace.item + &suffix;
UntaggedValue::filepath(path.with_file_name(&new_name))
}
None => UntaggedValue::string(stem),

View File

@ -0,0 +1,84 @@
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ColumnPath, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use std::path::Path;
pub struct PathJoin;
#[derive(Deserialize)]
struct PathJoinArguments {
path: Tagged<String>,
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathJoinArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathJoin {
fn name(&self) -> &str {
"path join"
}
fn signature(&self) -> Signature {
Signature::build("path join")
.required("path", SyntaxShape::String, "Path to join the input path")
.rest(SyntaxShape::ColumnPath, "Optionally operate by column path")
}
fn usage(&self) -> &str {
"Joins an input path with another path"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (PathJoinArguments { path, rest }, input) = args.process().await?;
let args = Arc::new(PathJoinArguments { path, rest });
operate(input, &action, tag.span, args).await
}
#[cfg(windows)]
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Append a filename to a path",
example: "echo 'C:\\Users\\viking' | path join spam.txt",
result: Some(vec![Value::from(UntaggedValue::filepath(
"C:\\Users\\viking\\spam.txt",
))]),
}]
}
#[cfg(not(windows))]
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Append a filename to a path",
example: "echo '/home/viking' | path join spam.txt",
result: Some(vec![Value::from(UntaggedValue::filepath(
"/home/viking/spam.txt",
))]),
}]
}
}
fn action(path: &Path, args: &PathJoinArguments) -> UntaggedValue {
UntaggedValue::filepath(path.join(&args.path.item))
}
#[cfg(test)]
mod tests {
use super::PathJoin;
use super::ShellError;
#[test]
fn examples_work_as_expected() -> Result<(), ShellError> {
use crate::examples::test as test_examples;
test_examples(PathJoin {})
}
}

View File

@ -5,6 +5,7 @@ mod exists;
mod expand;
mod extension;
mod filestem;
mod join;
mod r#type;
use crate::prelude::*;
@ -21,34 +22,24 @@ pub use exists::PathExists;
pub use expand::PathExpand;
pub use extension::PathExtension;
pub use filestem::PathFilestem;
pub use join::PathJoin;
pub use r#type::PathType;
#[derive(Deserialize)]
struct DefaultArguments {
// used by basename, dirname, extension and filestem
replace: Option<String>,
// used by filestem
prefix: Option<String>,
suffix: Option<String>,
// used by dirname
num_levels: Option<u32>,
// used by all
paths: Vec<ColumnPath>,
trait PathSubcommandArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath>;
}
fn handle_value<F>(
action: &F,
v: &Value,
span: Span,
args: Arc<DefaultArguments>,
) -> Result<Value, ShellError>
fn handle_value<F, T>(action: &F, v: &Value, span: Span, args: Arc<T>) -> Result<Value, ShellError>
where
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + 'static,
T: PathSubcommandArguments + Send + 'static,
F: Fn(&Path, &T) -> UntaggedValue + Send + 'static,
{
let v = match &v.value {
UntaggedValue::Primitive(Primitive::FilePath(buf)) => action(buf, args).into_value(v.tag()),
UntaggedValue::Primitive(Primitive::FilePath(buf)) => {
action(buf, &args).into_value(v.tag())
}
UntaggedValue::Primitive(Primitive::String(s)) => {
action(s.as_ref(), args).into_value(v.tag())
action(s.as_ref(), &args).into_value(v.tag())
}
other => {
let got = format!("got {}", other.type_name());
@ -64,23 +55,24 @@ where
Ok(v)
}
async fn operate<F>(
async fn operate<F, T>(
input: crate::InputStream,
action: &'static F,
span: Span,
args: Arc<DefaultArguments>,
args: Arc<T>,
) -> Result<OutputStream, ShellError>
where
F: Fn(&Path, Arc<DefaultArguments>) -> UntaggedValue + Send + Sync + 'static,
T: PathSubcommandArguments + Send + Sync + 'static,
F: Fn(&Path, &T) -> UntaggedValue + Send + Sync + 'static,
{
Ok(input
.map(move |v| {
if args.paths.is_empty() {
if args.get_column_paths().is_empty() {
ReturnSuccess::value(handle_value(&action, &v, span, Arc::clone(&args))?)
} else {
let mut ret = v;
for path in &args.paths {
for path in args.get_column_paths() {
let cloned_args = Arc::clone(&args);
ret = ret.swap_data_by_column_path(
path,

View File

@ -1,4 +1,4 @@
use super::{operate, DefaultArguments};
use super::{operate, PathSubcommandArguments};
use crate::prelude::*;
use nu_engine::filesystem::filesystem_shell::get_file_type;
use nu_engine::WholeStreamCommand;
@ -13,6 +13,12 @@ struct PathTypeArguments {
rest: Vec<ColumnPath>,
}
impl PathSubcommandArguments for PathTypeArguments {
fn get_column_paths(&self) -> &Vec<ColumnPath> {
&self.rest
}
}
#[async_trait]
impl WholeStreamCommand for PathType {
fn name(&self) -> &str {
@ -31,13 +37,7 @@ impl WholeStreamCommand for PathType {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let tag = args.call_info.name_tag.clone();
let (PathTypeArguments { rest }, input) = args.process().await?;
let args = Arc::new(DefaultArguments {
replace: None,
prefix: None,
suffix: None,
num_levels: None,
paths: rest,
});
let args = Arc::new(PathTypeArguments { rest });
operate(input, &action, tag.span, args).await
}
@ -50,7 +50,7 @@ impl WholeStreamCommand for PathType {
}
}
fn action(path: &Path, _args: Arc<DefaultArguments>) -> UntaggedValue {
fn action(path: &Path, _args: &PathTypeArguments) -> UntaggedValue {
let meta = std::fs::symlink_metadata(path);
UntaggedValue::string(match &meta {
Ok(md) => get_file_type(md),

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for Prepend {
}
fn usage(&self) -> &str {
"Prepend the given row to the front of the table"
"Prepend the given row to the front of the table."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -16,12 +16,12 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"Generate random values"
"Generate random values."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&Command, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
))))
}
}

View File

@ -27,7 +27,7 @@ impl WholeStreamCommand for Range {
}
fn usage(&self) -> &str {
"Return only the selected rows"
"Return only the selected rows."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -42,8 +42,11 @@ impl WholeStreamCommand for Reduce {
}
fn usage(&self) -> &str {
"Aggregate a list table to a single value using an accumulator block. Block must be
(A, A) -> A unless --fold is selected, in which case it may be A, B -> A."
"Aggregate a list table to a single value using an accumulator block."
}
fn extra_usage(&self) -> &str {
"Block must be (A, A) -> A unless --fold is selected, in which case it may be A, B -> A."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -30,7 +30,7 @@ impl WholeStreamCommand for Remove {
}
fn usage(&self) -> &str {
"Remove file(s)"
"Remove file(s)."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -0,0 +1,117 @@
use crate::prelude::*;
use nu_data::base::select_fields;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tagged;
use super::support::{rotate, Direction};
pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments {
by: Option<Tagged<u64>>,
opposite: bool,
#[serde(rename(deserialize = "cells-only"))]
cells_only: bool,
}
impl Arguments {
fn direction(&self) -> Direction {
if self.opposite {
return Direction::Left;
}
Direction::Right
}
fn move_headers(&self) -> bool {
!self.cells_only
}
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"roll column"
}
fn signature(&self) -> Signature {
Signature::build("roll column")
.optional("by", SyntaxShape::Int, "the number of times to roll")
.switch("opposite", "roll in the opposite direction", Some('o'))
.switch("cells-only", "only roll the cells", Some('c'))
}
fn usage(&self) -> &str {
"Rolls the table columns"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
roll(args).await
}
}
pub async fn roll(args: CommandArgs) -> Result<OutputStream, ShellError> {
let (args, input) = args.process().await?;
Ok(input
.map(move |value| {
futures::stream::iter({
let tag = value.tag();
roll_by(value, &args)
.unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(tag)])
.into_iter()
.map(ReturnSuccess::value)
})
})
.flatten()
.to_output_stream())
}
fn roll_by(value: Value, options: &Arguments) -> Option<Vec<Value>> {
let tag = value.tag();
let direction = options.direction();
if value.is_row() {
if options.move_headers() {
let columns = value.data_descriptors();
if let Some(fields) = rotate(columns, &options.by, direction) {
return Some(vec![select_fields(&value, &fields, &tag)]);
}
} else {
let columns = value.data_descriptors();
let values_rotated = rotate(
value
.row_entries()
.map(|(_, value)| value)
.map(Clone::clone)
.collect::<Vec<_>>(),
&options.by,
direction,
);
if let Some(ref values) = values_rotated {
let mut out = TaggedDictBuilder::new(&tag);
for (k, v) in columns.iter().zip(values.iter()) {
out.insert_value(k, v.clone());
}
return Some(vec![out.into_value()]);
}
}
None
} else if value.is_table() {
rotate(
value.table_entries().map(Clone::clone).collect(),
&options.by,
direction,
)
} else {
Some(vec![value])
}
}

View File

@ -0,0 +1,52 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use super::support::{rotate, Direction};
pub struct Command;
#[derive(Deserialize)]
pub struct Arguments {
by: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"roll"
}
fn signature(&self) -> Signature {
Signature::build("roll").optional("by", SyntaxShape::Int, "the number of times to roll")
}
fn usage(&self) -> &str {
"Rolls the table rows."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
roll(args).await
}
}
pub async fn roll(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let (args, mut input) = args.process().await?;
let values = input.drain_vec().await;
Ok(futures::stream::iter(
roll_down(values, &args)
.unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(&name)])
.into_iter()
.map(ReturnSuccess::value),
)
.to_output_stream())
}
fn roll_down(values: Vec<Value>, Arguments { by: ref n }: &Arguments) -> Option<Vec<Value>> {
rotate(values, n, Direction::Down)
}

View File

@ -0,0 +1,42 @@
mod column;
mod command;
mod up;
pub use column::SubCommand as RollColumn;
pub use command::Command as Roll;
pub use up::SubCommand as RollUp;
mod support {
pub enum Direction {
Left,
Right,
Down,
Up,
}
pub fn rotate<T: Clone>(
mut collection: Vec<T>,
n: &Option<nu_source::Tagged<u64>>,
direction: Direction,
) -> Option<Vec<T>> {
if collection.is_empty() {
return None;
}
let values = collection.as_mut_slice();
let rotations = if let Some(n) = n {
n.item as usize % values.len()
} else {
1
};
match direction {
Direction::Up | Direction::Right => values.rotate_left(rotations),
Direction::Down | Direction::Left => values.rotate_right(rotations),
}
Some(values.to_vec())
}
}

View File

@ -0,0 +1,52 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_source::Tagged;
use super::support::{rotate, Direction};
pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments {
by: Option<Tagged<u64>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"roll up"
}
fn signature(&self) -> Signature {
Signature::build("roll up").optional("by", SyntaxShape::Int, "the number of times to roll")
}
fn usage(&self) -> &str {
"Rolls the table rows"
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
roll(args).await
}
}
pub async fn roll(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let (args, mut input) = args.process().await?;
let values = input.drain_vec().await;
Ok(futures::stream::iter(
roll_up(values, &args)
.unwrap_or_else(|| vec![UntaggedValue::nothing().into_value(&name)])
.into_iter()
.map(ReturnSuccess::value),
)
.to_output_stream())
}
fn roll_up(values: Vec<Value>, Arguments { by: ref n }: &Arguments) -> Option<Vec<Value>> {
rotate(values, n, Direction::Up)
}

View File

@ -0,0 +1,121 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
merge_descriptors, ColumnPath, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
UntaggedValue,
};
use nu_source::{SpannedItem, Tagged};
use nu_value_ext::ValueExt;
pub struct Command;
#[derive(Deserialize)]
pub struct Arguments {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for Command {
fn name(&self) -> &str {
"rotate"
}
fn signature(&self) -> Signature {
Signature::build("rotate").rest(
SyntaxShape::String,
"the names to give columns once rotated",
)
}
fn usage(&self) -> &str {
"Rotates the table by 90 degrees clockwise."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
rotate(args).await
}
}
pub async fn rotate(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let (Arguments { rest }, input) = args.process().await?;
let input = input.into_vec().await;
let total_rows = input.len();
let descs = merge_descriptors(&input);
let total_descriptors = descs.len();
let descs = descs.into_iter().rev().collect::<Vec<_>>();
if total_rows == 0 {
return Ok(OutputStream::empty());
}
let mut headers: Vec<String> = vec![];
for i in 0..=total_rows {
headers.push(format!("Column{}", i));
}
let first = input[0].clone();
let name = if first.tag.anchor().is_some() {
first.tag
} else {
name
};
let values =
UntaggedValue::table(&input.into_iter().rev().collect::<Vec<_>>()).into_value(&name);
let values = nu_data::utils::group(
&values,
&Some(Box::new(move |row_number: usize, _| {
Ok(match headers.get(row_number) {
Some(name) => name.clone(),
None => String::new(),
})
})),
&name,
)?;
Ok(futures::stream::iter(
(0..total_descriptors)
.map(move |row_number| {
let mut row = TaggedDictBuilder::new(&name);
for (current_numbered_column, (column_name, _)) in values.row_entries().enumerate()
{
let raw_column_path =
format!("{}.0.{}", column_name, descs[row_number]).spanned_unknown();
let path = ColumnPath::build(&raw_column_path);
match &values.get_data_by_column_path(&path, Box::new(move |_, _, error| error))
{
Ok(x) => {
row.insert_value(
rest.get(current_numbered_column)
.map(|c| c.item.clone())
.unwrap_or_else(|| column_name.to_string()),
x.clone(),
);
}
Err(_) => {}
}
}
row.insert_value(
rest.get(total_rows)
.map(|c| c.item.clone())
.unwrap_or_else(|| format!("Column{}", total_rows)),
UntaggedValue::string(&descs[row_number]).into_untagged_value(),
);
ReturnSuccess::value(row.into_value())
})
.rev()
.collect::<Vec<_>>(),
)
.to_output_stream())
}

View File

@ -0,0 +1,117 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
merge_descriptors, ColumnPath, ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder,
UntaggedValue,
};
use nu_source::{SpannedItem, Tagged};
use nu_value_ext::ValueExt;
pub struct SubCommand;
#[derive(Deserialize)]
pub struct Arguments {
rest: Vec<Tagged<String>>,
}
#[async_trait]
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"rotate counter-clockwise"
}
fn signature(&self) -> Signature {
Signature::build("rotate counter-clockwise").rest(
SyntaxShape::String,
"the names to give columns once rotated",
)
}
fn usage(&self) -> &str {
"Rotates the table by 90 degrees counter clockwise."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
rotate(args).await
}
}
pub async fn rotate(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let (Arguments { rest }, input) = args.process().await?;
let input = input.into_vec().await;
let descs = merge_descriptors(&input);
let total_rows = input.len();
if total_rows == 0 {
return Ok(OutputStream::empty());
}
let mut headers: Vec<String> = vec![];
for i in 0..=total_rows {
headers.push(format!("Column{}", i + 1));
}
let first = input[0].clone();
let name = if first.tag.anchor().is_some() {
first.tag
} else {
name
};
let values = UntaggedValue::table(&input).into_value(&name);
let values = nu_data::utils::group(
&values,
&Some(Box::new(move |row_number: usize, _| {
Ok(match headers.get(row_number) {
Some(name) => name.clone(),
None => String::new(),
})
})),
&name,
)?;
Ok(futures::stream::iter(
(0..descs.len())
.rev()
.map(move |row_number| {
let mut row = TaggedDictBuilder::new(&name);
row.insert_value(
rest.get(0)
.map(|c| c.item.clone())
.unwrap_or_else(|| String::from("Column0")),
UntaggedValue::string(descs.get(row_number).unwrap_or(&String::new()))
.into_untagged_value(),
);
for (current_numbered_column, (column_name, _)) in values.row_entries().enumerate()
{
let raw_column_path =
format!("{}.0.{}", column_name, &descs[row_number]).spanned_unknown();
let path = ColumnPath::build(&raw_column_path);
match &values.get_data_by_column_path(&path, Box::new(move |_, _, error| error))
{
Ok(x) => {
row.insert_value(
rest.get(current_numbered_column + 1)
.map(|c| c.item.clone())
.unwrap_or_else(|| column_name.to_string()),
x.clone(),
);
}
Err(_) => {}
}
}
ReturnSuccess::value(row.into_value())
})
.collect::<Vec<_>>(),
)
.to_output_stream())
}

View File

@ -0,0 +1,5 @@
mod command;
mod counter_clockwise;
pub use command::Command as Rotate;
pub use counter_clockwise::SubCommand as RotateCounterClockwise;

View File

@ -45,7 +45,7 @@ impl WholeStreamCommand for Seq {
}
fn usage(&self) -> &str {
"print sequences of numbers"
"Print sequences of numbers."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -36,7 +36,7 @@ impl WholeStreamCommand for Sleep {
}
fn usage(&self) -> &str {
"Delay for a specified amount of time"
"Delay for a specified amount of time."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {

View File

@ -17,12 +17,12 @@ impl WholeStreamCommand for Command {
}
fn usage(&self) -> &str {
"split contents across desired subcommand (like row, column) via the separator."
"Split contents across desired subcommand (like row, column) via the separator."
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(Ok(ReturnSuccess::Value(
UntaggedValue::string(get_help(&Command, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
))))
}
}

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &to_camel_case).await
operate(args, &to_camel_case).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &to_kebab_case).await
operate(args, &to_kebab_case).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &to_pascal_case).await
operate(args, &to_pascal_case).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &to_screaming_snake_case).await
operate(args, &to_screaming_snake_case).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -25,7 +25,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &to_snake_case).await
operate(args, &to_snake_case).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -24,7 +24,7 @@ impl WholeStreamCommand for Command {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::string(get_help(&Command, &args.scope)).into_value(Tag::unknown()),
UntaggedValue::string(get_full_help(&Command, &args.scope)).into_value(Tag::unknown()),
)))
}
}

View File

@ -31,7 +31,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &trim).await
operate(args, &trim).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -31,7 +31,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &trim_left).await
operate(args, &trim_left).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -31,7 +31,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
operate((args), &trim_right).await
operate(args, &trim_right).await
}
fn examples(&self) -> Vec<Example> {

View File

@ -8,6 +8,12 @@ use nu_protocol::{Primitive, Signature, SyntaxShape, UntaggedValue, Value};
use nu_table::{draw_table, Alignment, StyledString, TextStyle};
use std::collections::HashMap;
use std::time::Instant;
#[cfg(feature = "table-pager")]
use {
futures::future::join,
minus::{ExitStrategy, Pager},
std::fmt::Write,
};
const STREAM_PAGE_SIZE: usize = 1000;
const STREAM_TIMEOUT_CHECK_INTERVAL: usize = 100;
@ -34,7 +40,7 @@ impl WholeStreamCommand for Command {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
table(TableConfiguration::new(), (args)).await
table(TableConfiguration::new(), args).await
}
}
@ -42,7 +48,7 @@ pub fn from_list(
values: &[Value],
configuration: &TableConfiguration,
starting_idx: usize,
color_hm: &HashMap<String, ansi_term::Style>,
color_hm: &HashMap<String, nu_ansi_term::Style>,
) -> nu_table::Table {
let header_style = configuration.header_style();
let mut headers: Vec<StyledString> = nu_protocol::merge_descriptors(values)
@ -62,7 +68,7 @@ fn values_to_entries(
headers: &mut Vec<StyledString>,
configuration: &TableConfiguration,
starting_idx: usize,
color_hm: &HashMap<String, ansi_term::Style>,
color_hm: &HashMap<String, nu_ansi_term::Style>,
) -> Vec<Vec<StyledString>> {
let disable_indexes = configuration.disabled_indexes();
let mut entries = vec![];
@ -122,9 +128,9 @@ fn values_to_entries(
color_hm
.get("index_color")
.unwrap_or(
&ansi_term::Style::default()
&nu_ansi_term::Style::default()
.bold()
.fg(ansi_term::Color::Green),
.fg(nu_ansi_term::Color::Green),
)
.to_owned(),
),
@ -142,7 +148,7 @@ fn values_to_entries(
"#".to_owned(),
TextStyle::new()
.alignment(Alignment::Center)
.fg(ansi_term::Color::Green)
.fg(nu_ansi_term::Color::Green)
.bold(Some(true)),
),
);
@ -156,7 +162,7 @@ async fn table(
args: CommandArgs,
) -> Result<OutputStream, ShellError> {
let mut args = args.evaluate_once().await?;
let mut finished = false;
// Ideally, get_color_config would get all the colors configured in the config.toml
// and create a style based on those settings. However, there are few places where
// this just won't work right now, like header styling, because a style needs to know
@ -187,66 +193,239 @@ async fn table(
let term_width = args.host.lock().width();
while !finished {
let mut new_input: VecDeque<Value> = VecDeque::new();
#[cfg(feature = "table-pager")]
let pager = Pager::new()
.set_exit_strategy(ExitStrategy::PagerQuit)
.set_searchable(true)
.set_page_if_havent_overflowed(false)
.set_input_handler(Box::new(input_handling::MinusInputHandler {}))
.finish();
let start_time = Instant::now();
for idx in 0..STREAM_PAGE_SIZE {
if let Some(val) = delay_slot {
new_input.push_back(val);
delay_slot = None;
} else {
match args.input.next().await {
Some(a) => {
if !new_input.is_empty() {
if let Some(descs) = new_input.get(0) {
let descs = descs.data_descriptors();
let compare = a.data_descriptors();
if descs != compare {
delay_slot = Some(a);
break;
let stream_data = async {
let finished = Arc::new(AtomicBool::new(false));
// we are required to clone finished, for use within the callback, otherwise we get borrow errors
#[cfg(feature = "table-pager")]
let finished_within_callback = finished.clone();
#[cfg(feature = "table-pager")]
{
// This is called when the pager finishes, to indicate to the
// while loop below to finish, in case of long running InputStream consumer
// that doesnt finish by the time the user quits out of the pager
pager.lock().await.add_exit_callback(move || {
finished_within_callback.store(true, Ordering::Relaxed);
});
}
while !finished.clone().load(Ordering::Relaxed) {
let mut new_input: VecDeque<Value> = VecDeque::new();
let start_time = Instant::now();
for idx in 0..STREAM_PAGE_SIZE {
if let Some(val) = delay_slot {
new_input.push_back(val);
delay_slot = None;
} else {
match args.input.next().await {
Some(a) => {
if !new_input.is_empty() {
if let Some(descs) = new_input.get(0) {
let descs = descs.data_descriptors();
let compare = a.data_descriptors();
if descs != compare {
delay_slot = Some(a);
break;
} else {
new_input.push_back(a);
}
} else {
new_input.push_back(a);
}
} else {
new_input.push_back(a);
}
} else {
new_input.push_back(a);
}
_ => {
finished.store(true, Ordering::Relaxed);
break;
}
}
_ => {
finished = true;
break;
}
}
// Check if we've gone over our buffering threshold
if (idx + 1) % STREAM_TIMEOUT_CHECK_INTERVAL == 0 {
let end_time = Instant::now();
// Check if we've gone over our buffering threshold
if (idx + 1) % STREAM_TIMEOUT_CHECK_INTERVAL == 0 {
let end_time = Instant::now();
// If we've been buffering over a second, go ahead and send out what we have so far
if (end_time - start_time).as_secs() >= 1 {
break;
// If we've been buffering over a second, go ahead and send out what we have so far
if (end_time - start_time).as_secs() >= 1 {
break;
}
if finished.load(Ordering::Relaxed) {
break;
}
}
}
}
let input: Vec<Value> = new_input.into();
if !input.is_empty() {
let t = from_list(&input, &configuration, start_number, &color_hm);
let output = draw_table(&t, term_width, &color_hm);
#[cfg(feature = "table-pager")]
{
let mut pager = pager.lock().await;
writeln!(pager.lines, "{}", output).map_err(|_| {
ShellError::untagged_runtime_error("Error writing to pager")
})?;
}
#[cfg(not(feature = "table-pager"))]
println!("{}", output);
}
start_number += input.len();
}
let input: Vec<Value> = new_input.into();
if !input.is_empty() {
let t = from_list(&input, &configuration, start_number, &color_hm);
draw_table(&t, term_width, &color_hm);
#[cfg(feature = "table-pager")]
{
let mut pager_lock = pager.lock().await;
pager_lock.data_finished();
}
start_number += input.len();
Result::<_, ShellError>::Ok(())
};
#[cfg(feature = "table-pager")]
{
let (minus_result, streaming_result) =
join(minus::async_std_updating(pager.clone()), stream_data).await;
minus_result.map_err(|_| ShellError::untagged_runtime_error("Error paging data"))?;
streaming_result?;
}
#[cfg(not(feature = "table-pager"))]
stream_data
.await
.map_err(|_| ShellError::untagged_runtime_error("Error streaming data"))?;
Ok(OutputStream::empty())
}
#[cfg(feature = "table-pager")]
mod input_handling {
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
use minus::{InputEvent, InputHandler, LineNumbers, SearchMode};
pub struct MinusInputHandler;
impl InputHandler for MinusInputHandler {
fn handle_input(
&self,
ev: Event,
upper_mark: usize,
search_mode: SearchMode,
ln: LineNumbers,
rows: usize,
) -> Option<InputEvent> {
match ev {
// Scroll up by one.
Event::Key(KeyEvent {
code: KeyCode::Up,
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(upper_mark.saturating_sub(1))),
// Scroll down by one.
Event::Key(KeyEvent {
code: KeyCode::Down,
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(upper_mark.saturating_add(1))),
// Mouse scroll up/down
Event::Mouse(MouseEvent {
kind: MouseEventKind::ScrollUp,
..
}) => Some(InputEvent::UpdateUpperMark(upper_mark.saturating_sub(5))),
Event::Mouse(MouseEvent {
kind: MouseEventKind::ScrollDown,
..
}) => Some(InputEvent::UpdateUpperMark(upper_mark.saturating_add(5))),
// Go to top.
Event::Key(KeyEvent {
code: KeyCode::Home,
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(0)),
// Go to bottom.
Event::Key(KeyEvent {
code: KeyCode::End,
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(usize::MAX)),
// Page Up/Down
Event::Key(KeyEvent {
code: KeyCode::PageUp,
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(
upper_mark.saturating_sub(rows - 1),
)),
Event::Key(KeyEvent {
code: KeyCode::PageDown,
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::UpdateUpperMark(
upper_mark.saturating_add(rows - 1),
)),
// Resize event from the terminal.
Event::Resize(_, height) => Some(InputEvent::UpdateRows(height as usize)),
// Switch line number display.
Event::Key(KeyEvent {
code: KeyCode::Char('l'),
modifiers: KeyModifiers::CONTROL,
}) => Some(InputEvent::UpdateLineNumber(!ln)),
// Quit.
Event::Key(KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
})
| Event::Key(KeyEvent {
code: KeyCode::Char('Q'),
modifiers: KeyModifiers::SHIFT,
})
| Event::Key(KeyEvent {
code: KeyCode::Esc,
modifiers: KeyModifiers::NONE,
})
| Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
}) => Some(InputEvent::Exit),
Event::Key(KeyEvent {
code: KeyCode::Char('/'),
modifiers: KeyModifiers::NONE,
}) => Some(InputEvent::Search(SearchMode::Unknown)),
Event::Key(KeyEvent {
code: KeyCode::Down,
modifiers: KeyModifiers::CONTROL,
}) => {
if search_mode == SearchMode::Unknown {
Some(InputEvent::NextMatch)
} else {
None
}
}
Event::Key(KeyEvent {
code: KeyCode::Up,
modifiers: KeyModifiers::CONTROL,
}) => {
if search_mode == SearchMode::Unknown {
Some(InputEvent::PrevMatch)
} else {
None
}
}
_ => None,
}
}
}
}
#[cfg(test)]
mod tests {
use super::Command;

View File

@ -80,7 +80,7 @@ impl ConfigExtensions for NuConfig {
Some(s) => {
lookup_ansi_color_style(s.as_string().unwrap_or_else(|_| "green".to_string()))
}
None => ansi_term::Color::Green.normal(),
None => nu_ansi_term::Color::Green.normal(),
};
let head_bold = get_color_from_key_and_subkey(self, "color_config", "header_bold");
let head_bold_bool = match head_bold {
@ -98,7 +98,7 @@ impl ConfigExtensions for NuConfig {
.bold(Some(head_bold_bool))
.fg(head_color_style
.foreground
.unwrap_or(ansi_term::Color::Green))
.unwrap_or(nu_ansi_term::Color::Green))
}
fn table_mode(&self) -> nu_table::Theme {

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