зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1441204 - Upgrade clap from 2.29.0 to 2.31.2. r=maja_zf
MozReview-Commit-ID: 97HZvS13yg --HG-- rename : third_party/rust/strsim/.cargo-checksum.json => third_party/rust/strsim-0.6.0/.cargo-checksum.json rename : third_party/rust/strsim/CHANGELOG.md => third_party/rust/strsim-0.6.0/CHANGELOG.md rename : third_party/rust/strsim/Cargo.toml => third_party/rust/strsim-0.6.0/Cargo.toml rename : third_party/rust/strsim/LICENSE => third_party/rust/strsim-0.6.0/LICENSE rename : third_party/rust/strsim/README.md => third_party/rust/strsim-0.6.0/README.md rename : third_party/rust/strsim/dev => third_party/rust/strsim-0.6.0/dev rename : third_party/rust/strsim/src/lib.rs => third_party/rust/strsim-0.6.0/src/lib.rs rename : third_party/rust/strsim/tests/lib.rs => third_party/rust/strsim-0.6.0/tests/lib.rs extra : rebase_source : bc29633dad9e1436f93eb464504d9114a19a0812
This commit is contained in:
Родитель
53d50e1b88
Коммит
393c2a2412
|
@ -22,8 +22,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.10.2"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "app_units"
|
||||
|
@ -159,7 +162,7 @@ dependencies = [
|
|||
"cexpr 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clang-sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -183,7 +186,7 @@ name = "binsource"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"binjs_meta 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -300,13 +303,13 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.29.0"
|
||||
version = "2.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -780,7 +783,7 @@ name = "geckodriver"
|
|||
version = "0.20.1"
|
||||
dependencies = [
|
||||
"chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.10.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1914,6 +1917,11 @@ name = "strsim"
|
|||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "style"
|
||||
version = "0.0.1"
|
||||
|
@ -2509,7 +2517,7 @@ dependencies = [
|
|||
"checksum Inflector 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1b33cd9b653730fc539c53c7b3c672d2f47108fa20c6df571fa5817178f5a14c"
|
||||
"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45"
|
||||
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
|
||||
"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"
|
||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
"checksum app_units 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "29069a9b483f7780aebb55dafb360c6225eefdc1f98c8d336a65148fd10c37b1"
|
||||
"checksum arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2f0ef4a9820019a0c91d918918c93dc71d469f581a49b47ddc1d285d4270bbe2"
|
||||
"checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2"
|
||||
|
@ -2537,7 +2545,7 @@ dependencies = [
|
|||
"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
|
||||
"checksum chrono 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9213f7cd7c27e95c2b57c49f0e69b1ea65b27138da84a170133fd21b07659c00"
|
||||
"checksum clang-sys 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "939a1a34310b120d26eba35c29475933128b0ec58e24b43327f8dbe6036fc538"
|
||||
"checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
|
||||
"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536"
|
||||
"checksum cmake 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "56d741ea7a69e577f6d06b36b7dff4738f680593dc27a701ffa8506b73ce28bb"
|
||||
"checksum cookie 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "746858cae4eae40fff37e1998320068df317bc247dc91a67c6cfa053afdc2abb"
|
||||
"checksum core-foundation 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "286e0b41c3a20da26536c6000a280585d519fd07b3956b43aed8a79e9edce980"
|
||||
|
@ -2697,6 +2705,7 @@ dependencies = [
|
|||
"checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7"
|
||||
"checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
|
||||
"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
|
||||
"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
|
||||
"checksum syn 0.12.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9e1c669ed757c0ebd04337f6a5bb972d05e0c08fe2540dd3ee3dd9e4daf1604c"
|
||||
"checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
|
||||
"checksum synstructure 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "010366096045d8250555904c58da03377289e7f4b2ce7a5b1027e2b532f41000"
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"files":{".travis.yml":"eb7113e5f5e36c2e00ae8e88a84dd5273505036520f2787133ba575d5fdd93c1","Cargo.toml":"7ee95481bc957d9c98cdb9e3ae6c9d982dd385a8276544445554b8321e604c97","LICENCE":"2762990c7fbba9d550802a2593c1d857dcd52596bb0f9f192a97e9a7ac5f4f9e","README.md":"8a00b2016ab0b1c41b110b7f6561d7c2d675fd2442ef10a1cce36b8b8f68235a","src/ansi.rs":"a404efab736839d65c49e147c1d037fb3f852341d445ad41cc43181462310a65","src/debug.rs":"c40ea0796167161dda30bfb4b8b2596101da42e98a81b1c7d0e04fe53befb1c3","src/difference.rs":"9b4b8f91c72932bfda262abdceff0ec124a5a8dd27d07bd4d2e5e7889135c6c9","src/display.rs":"507d120de80411c9b8b2a05ff68f2336137ef226de4b8c537c790d7ccdacc3e1","src/lib.rs":"4847d21681c323236068bbcbf5523e69cf69e79470a9fec0927f520bd0670967","src/style.rs":"003b9832c4381c455b6ddf2c37b7150b48c9a5649c0c14da923a80e9fd6d898f","src/windows.rs":"0f806e0d70d8b15fdb158cc5f9bbb88ffba2ea9d64b0d69b5adfb95a2e2cdbef","src/write.rs":"c9ec03764ad1ecea8b680243c9cafc5e70919fcea7500cc18246ffd8f6bb4b33"},"package":"6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455"}
|
||||
{"files":{".travis.yml":"03d80927d30c71a0183fc0cd2a6b3bb40b2474d4f83b8d02e90d950457b7c590","Cargo.toml":"48df4570f3bbfba5b3c19731a54abf70ac8608e4dab6f4dae6a694b18d6ad102","LICENCE":"a24742368cf773bbb8b6f0fcbe86ca4b802c2b7c081bc8bebf14ac38618e7c63","README.md":"779b02ebacd6f4d08e01ef289bd7976a4467054f40355593817fd6df7e8c9dd4","examples/colours.rs":"e4870671adb9574607e37a0e4145643f9047c881c310113de114ec20d76aaf4b","src/ansi.rs":"b8f5de966e7ec2fba7a4d5a373d0aceafe19ea6e20a3f4daaf448e119c989ae7","src/debug.rs":"0ab28b65c39538825707d8b7e81c6f91c78310856c936bba0ee609e06d138543","src/difference.rs":"da68156310cbaf57a3619160d0fb966f496f970c32a2e57601127cc8f54a2fbf","src/display.rs":"a43f19b7cf4d95e90e4f3954399405d8350523d423f0beed9f01399e17527b17","src/lib.rs":"b6df00ab61ca0d82c9f7b3798d516384dd2617fe73e8981f37125ecccc970dd7","src/style.rs":"7c5c2524428f0dfbe3b8d5876ddb81c47d8635704ee31ef63ddeb2429a67f457","src/windows.rs":"3b52469eed89fdc258139e4fd978f0e30c72168863966247df781da8715f0841","src/write.rs":"247c518f8b0c103c970bbe7bc70caba3ee961ab0d37095e2da5c69db98d2fc24"},"package":"ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"}
|
|
@ -1,33 +1,33 @@
|
|||
language: rust
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
|
||||
rust:
|
||||
- nightly
|
||||
- beta
|
||||
- stable
|
||||
|
||||
# load travis-cargo
|
||||
before_script:
|
||||
- |
|
||||
pip install 'travis-cargo<0.2' --user &&
|
||||
export PATH=$HOME/.local/bin:$PATH
|
||||
|
||||
script:
|
||||
- |
|
||||
travis-cargo build &&
|
||||
travis-cargo test
|
||||
travis-cargo --only stable doc
|
||||
after_success:
|
||||
# measure code coverage and upload to coveralls.io
|
||||
- travis-cargo coveralls --no-sudo
|
||||
|
||||
env:
|
||||
global:
|
||||
# override the default `--features unstable` used for the nightly branch (optional)
|
||||
language: rust
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
|
||||
rust:
|
||||
- nightly
|
||||
- beta
|
||||
- stable
|
||||
|
||||
# load travis-cargo
|
||||
before_script:
|
||||
- |
|
||||
pip install 'travis-cargo<0.2' --user &&
|
||||
export PATH=$HOME/.local/bin:$PATH
|
||||
|
||||
script:
|
||||
- |
|
||||
travis-cargo build &&
|
||||
travis-cargo test
|
||||
travis-cargo --only stable doc
|
||||
after_success:
|
||||
# measure code coverage and upload to coveralls.io
|
||||
- travis-cargo coveralls --no-sudo
|
||||
|
||||
env:
|
||||
global:
|
||||
# override the default `--features unstable` used for the nightly branch (optional)
|
||||
- TRAVIS_CARGO_NIGHTLY_FEATURE=nightly
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
[package]
|
||||
name = "ansi_term"
|
||||
version = "0.10.2"
|
||||
version = "0.11.0"
|
||||
authors = ["ogham@bsago.me", "Ryan Scheel (Havvy) <ryan.havvy@gmail.com>", "Josh Triplett <josh@joshtriplett.org>"]
|
||||
description = "Library for ANSI terminal colours and styles (bold, underline)"
|
||||
homepage = "https://github.com/ogham/rust-ansi-term"
|
||||
|
@ -22,3 +22,6 @@ license = "MIT"
|
|||
|
||||
[lib]
|
||||
name = "ansi_term"
|
||||
[target."cfg(target_os=\"windows\")".dependencies.winapi]
|
||||
version = "0.3.4"
|
||||
features = ["errhandlingapi", "consoleapi", "processenv"]
|
||||
|
|
|
@ -1,21 +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.
|
||||
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.
|
||||
|
|
|
@ -1,174 +1,174 @@
|
|||
# rust-ansi-term [![ansi-term on crates.io](http://meritbadge.herokuapp.com/ansi-term)](https://crates.io/crates/ansi_term) [![Build status](https://travis-ci.org/ogham/rust-ansi-term.svg?branch=master)](https://travis-ci.org/ogham/rust-ansi-term) [![Coverage status](https://coveralls.io/repos/ogham/rust-ansi-term/badge.svg?branch=master&service=github)](https://coveralls.io/github/ogham/rust-ansi-term?branch=master)
|
||||
|
||||
This is a library for controlling colours and formatting, such as red bold text or blue underlined text, on ANSI terminals.
|
||||
|
||||
### [View the Rustdoc](https://docs.rs/ansi_term/0.9.0/ansi_term/)
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
ansi_term = "0.9"
|
||||
```
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
There are two main data structures in this crate that you need to be concerned with: `ANSIString` and `Style`.
|
||||
A `Style` holds stylistic information: colours, whether the text should be bold, or blinking, or whatever.
|
||||
There are also `Colour` variants that represent simple foreground colour styles.
|
||||
An `ANSIString` is a string paired with a `Style`.
|
||||
|
||||
(Yes, it’s British English, but you won’t have to write “colour” very often. `Style` is used the majority of the time.)
|
||||
|
||||
To format a string, call the `paint` method on a `Style` or a `Colour`, passing in the string you want to format as the argument.
|
||||
For example, here’s how to get some red text:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::Red;
|
||||
println!("This is in red: {}", Red.paint("a red string"));
|
||||
```
|
||||
|
||||
It’s 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 ansi_term::Colour::Red;
|
||||
use std::string::ToString;
|
||||
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
|
||||
let enabled = ansi_term::enable_ansi_support();
|
||||
```
|
||||
|
||||
## Bold, underline, background, and other styles
|
||||
|
||||
For anything more complex than plain foreground colour changes, you need to construct `Style` objects themselves, rather than beginning with a `Colour`.
|
||||
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 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 `Colour` values, so you can give your styles a foreground colour without having to begin with an empty `Style` value:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::{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 colours.
|
||||
|
||||
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Colour`.
|
||||
You can do this using the `fg` method:
|
||||
|
||||
```rust
|
||||
use ansi_term::Style;
|
||||
use ansi_term::Colour::{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!"));
|
||||
```
|
||||
|
||||
Finally, you can turn a `Colour` into a `Style` with the `normal` method.
|
||||
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Colour` directly, but it’s 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 ansi_term::Style;
|
||||
use ansi_term::Colour::Red;
|
||||
Red.normal().paint("yet another red string");
|
||||
Style::default().paint("a completely regular string");
|
||||
```
|
||||
|
||||
|
||||
## Extended colours
|
||||
|
||||
You can access the extended range of 256 colours by using the `Fixed` colour variant, which takes an argument of the colour number to use.
|
||||
This can be included wherever you would use a `Colour`:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::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 colour variants.
|
||||
There’s nothing stopping you from using these as `Fixed` colours instead, but there’s nothing to be gained by doing so either.
|
||||
|
||||
You can also access full 24-bit color by using the `RGB` colour variant, which takes separate `u8` arguments for red, green, and blue:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::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, it’s 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 ansi_term::Colour::Red;
|
||||
use 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
|
||||
`io::Write`:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::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 ansi_term::Colour::Green;
|
||||
use 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();
|
||||
```
|
||||
# rust-ansi-term [![ansi-term on crates.io](http://meritbadge.herokuapp.com/ansi-term)](https://crates.io/crates/ansi_term) [![Build status](https://travis-ci.org/ogham/rust-ansi-term.svg?branch=master)](https://travis-ci.org/ogham/rust-ansi-term) [![Coverage status](https://coveralls.io/repos/ogham/rust-ansi-term/badge.svg?branch=master&service=github)](https://coveralls.io/github/ogham/rust-ansi-term?branch=master)
|
||||
|
||||
This is a library for controlling colours and formatting, such as red bold text or blue underlined text, on ANSI terminals.
|
||||
|
||||
### [View the Rustdoc](https://docs.rs/ansi_term/0.9.0/ansi_term/)
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
This crate works with [Cargo](http://crates.io). Add the following to your `Cargo.toml` dependencies section:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
ansi_term = "0.9"
|
||||
```
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
There are two main data structures in this crate that you need to be concerned with: `ANSIString` and `Style`.
|
||||
A `Style` holds stylistic information: colours, whether the text should be bold, or blinking, or whatever.
|
||||
There are also `Colour` variants that represent simple foreground colour styles.
|
||||
An `ANSIString` is a string paired with a `Style`.
|
||||
|
||||
(Yes, it’s British English, but you won’t have to write “colour” very often. `Style` is used the majority of the time.)
|
||||
|
||||
To format a string, call the `paint` method on a `Style` or a `Colour`, passing in the string you want to format as the argument.
|
||||
For example, here’s how to get some red text:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::Red;
|
||||
println!("This is in red: {}", Red.paint("a red string"));
|
||||
```
|
||||
|
||||
It’s 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 ansi_term::Colour::Red;
|
||||
use std::string::ToString;
|
||||
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
|
||||
let enabled = ansi_term::enable_ansi_support();
|
||||
```
|
||||
|
||||
## Bold, underline, background, and other styles
|
||||
|
||||
For anything more complex than plain foreground colour changes, you need to construct `Style` objects themselves, rather than beginning with a `Colour`.
|
||||
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 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 `Colour` values, so you can give your styles a foreground colour without having to begin with an empty `Style` value:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::{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 colours.
|
||||
|
||||
In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Colour`.
|
||||
You can do this using the `fg` method:
|
||||
|
||||
```rust
|
||||
use ansi_term::Style;
|
||||
use ansi_term::Colour::{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!"));
|
||||
```
|
||||
|
||||
Finally, you can turn a `Colour` into a `Style` with the `normal` method.
|
||||
This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Colour` directly, but it’s 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 ansi_term::Style;
|
||||
use ansi_term::Colour::Red;
|
||||
Red.normal().paint("yet another red string");
|
||||
Style::default().paint("a completely regular string");
|
||||
```
|
||||
|
||||
|
||||
## Extended colours
|
||||
|
||||
You can access the extended range of 256 colours by using the `Fixed` colour variant, which takes an argument of the colour number to use.
|
||||
This can be included wherever you would use a `Colour`:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::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 colour variants.
|
||||
There’s nothing stopping you from using these as `Fixed` colours instead, but there’s nothing to be gained by doing so either.
|
||||
|
||||
You can also access full 24-bit color by using the `RGB` colour variant, which takes separate `u8` arguments for red, green, and blue:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::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, it’s 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 ansi_term::Colour::Red;
|
||||
use 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
|
||||
`io::Write`:
|
||||
|
||||
```rust
|
||||
use ansi_term::Colour::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 ansi_term::Colour::Green;
|
||||
use 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();
|
||||
```
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
extern crate ansi_term;
|
||||
use ansi_term::Colour::*;
|
||||
|
||||
fn main() {
|
||||
println!("{}", Black.paint("Black"));
|
||||
println!("{}", Red.paint("Red"));
|
||||
println!("{}", Green.paint("Green"));
|
||||
println!("{}", Yellow.paint("Yellow"));
|
||||
println!("{}", Blue.paint("Blue"));
|
||||
println!("{}", Purple.paint("Purple"));
|
||||
println!("{}", Cyan.paint("Cyan"));
|
||||
println!("{}", White.paint("White"));
|
||||
}
|
|
@ -1,258 +1,258 @@
|
|||
use style::{Colour, Style};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use write::AnyWrite;
|
||||
|
||||
|
||||
// ---- generating ANSI codes ----
|
||||
|
||||
impl Style {
|
||||
|
||||
/// Write any ANSI codes that go *before* a piece of text. These should be
|
||||
/// the codes to set the terminal to a different colour or font style.
|
||||
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
|
||||
// If there are actually no styles here, then don’t 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 colours, 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 ANSI codes that go *after* a piece of text. These should be
|
||||
/// the codes to *reset* the terminal back to its normal colour and style.
|
||||
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 Colour {
|
||||
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Colour::Black => write!(f, "30"),
|
||||
Colour::Red => write!(f, "31"),
|
||||
Colour::Green => write!(f, "32"),
|
||||
Colour::Yellow => write!(f, "33"),
|
||||
Colour::Blue => write!(f, "34"),
|
||||
Colour::Purple => write!(f, "35"),
|
||||
Colour::Cyan => write!(f, "36"),
|
||||
Colour::White => write!(f, "37"),
|
||||
Colour::Fixed(num) => write!(f, "38;5;{}", &num),
|
||||
Colour::RGB(r,g,b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Colour::Black => write!(f, "40"),
|
||||
Colour::Red => write!(f, "41"),
|
||||
Colour::Green => write!(f, "42"),
|
||||
Colour::Yellow => write!(f, "43"),
|
||||
Colour::Blue => write!(f, "44"),
|
||||
Colour::Purple => write!(f, "45"),
|
||||
Colour::Cyan => write!(f, "46"),
|
||||
Colour::White => write!(f, "47"),
|
||||
Colour::Fixed(num) => write!(f, "48;5;{}", &num),
|
||||
Colour::RGB(r,g,b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Like `ANSIString`, but only displays the style prefix.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Prefix(Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the difference between two
|
||||
/// styles.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Infix(Style, Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the style suffix.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Suffix(Style);
|
||||
|
||||
|
||||
impl Style {
|
||||
|
||||
/// The prefix for this style.
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self)
|
||||
}
|
||||
|
||||
/// The infix between this style and another.
|
||||
pub fn infix(self, other: Style) -> Infix {
|
||||
Infix(self, other)
|
||||
}
|
||||
|
||||
/// The suffix for this style.
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Colour {
|
||||
|
||||
/// The prefix for this colour.
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self.normal())
|
||||
}
|
||||
|
||||
/// The infix between this colour and another.
|
||||
pub fn infix(self, other: Colour) -> Infix {
|
||||
Infix(self.normal(), other.normal())
|
||||
}
|
||||
|
||||
/// The suffix for this colour.
|
||||
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 fmt::Write = f;
|
||||
self.0.write_prefix(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Display for Infix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use difference::Difference;
|
||||
|
||||
match Difference::between(&self.0, &self.1) {
|
||||
Difference::ExtraStyles(style) => {
|
||||
let f: &mut fmt::Write = f;
|
||||
style.write_prefix(f)
|
||||
},
|
||||
Difference::Reset => {
|
||||
let f: &mut fmt::Write = f;
|
||||
write!(f, "{}{}", RESET, self.0.prefix())
|
||||
},
|
||||
Difference::NoDifference => {
|
||||
Ok(()) // nothing to write
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Display for Suffix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut fmt::Write = f;
|
||||
self.0.write_suffix(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use style::Style;
|
||||
use style::Colour::*;
|
||||
|
||||
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!(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");
|
||||
|
||||
}
|
||||
use style::{Colour, Style};
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use write::AnyWrite;
|
||||
|
||||
|
||||
// ---- generating ANSI codes ----
|
||||
|
||||
impl Style {
|
||||
|
||||
/// Write any ANSI codes that go *before* a piece of text. These should be
|
||||
/// the codes to set the terminal to a different colour or font style.
|
||||
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
|
||||
// If there are actually no styles here, then don’t 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 colours, 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 ANSI codes that go *after* a piece of text. These should be
|
||||
/// the codes to *reset* the terminal back to its normal colour and style.
|
||||
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 Colour {
|
||||
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Colour::Black => write!(f, "30"),
|
||||
Colour::Red => write!(f, "31"),
|
||||
Colour::Green => write!(f, "32"),
|
||||
Colour::Yellow => write!(f, "33"),
|
||||
Colour::Blue => write!(f, "34"),
|
||||
Colour::Purple => write!(f, "35"),
|
||||
Colour::Cyan => write!(f, "36"),
|
||||
Colour::White => write!(f, "37"),
|
||||
Colour::Fixed(num) => write!(f, "38;5;{}", &num),
|
||||
Colour::RGB(r,g,b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
||||
match *self {
|
||||
Colour::Black => write!(f, "40"),
|
||||
Colour::Red => write!(f, "41"),
|
||||
Colour::Green => write!(f, "42"),
|
||||
Colour::Yellow => write!(f, "43"),
|
||||
Colour::Blue => write!(f, "44"),
|
||||
Colour::Purple => write!(f, "45"),
|
||||
Colour::Cyan => write!(f, "46"),
|
||||
Colour::White => write!(f, "47"),
|
||||
Colour::Fixed(num) => write!(f, "48;5;{}", &num),
|
||||
Colour::RGB(r,g,b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Like `ANSIString`, but only displays the style prefix.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Prefix(Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the difference between two
|
||||
/// styles.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Infix(Style, Style);
|
||||
|
||||
/// Like `ANSIString`, but only displays the style suffix.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Suffix(Style);
|
||||
|
||||
|
||||
impl Style {
|
||||
|
||||
/// The prefix for this style.
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self)
|
||||
}
|
||||
|
||||
/// The infix between this style and another.
|
||||
pub fn infix(self, other: Style) -> Infix {
|
||||
Infix(self, other)
|
||||
}
|
||||
|
||||
/// The suffix for this style.
|
||||
pub fn suffix(self) -> Suffix {
|
||||
Suffix(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Colour {
|
||||
|
||||
/// The prefix for this colour.
|
||||
pub fn prefix(self) -> Prefix {
|
||||
Prefix(self.normal())
|
||||
}
|
||||
|
||||
/// The infix between this colour and another.
|
||||
pub fn infix(self, other: Colour) -> Infix {
|
||||
Infix(self.normal(), other.normal())
|
||||
}
|
||||
|
||||
/// The suffix for this colour.
|
||||
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 fmt::Write = f;
|
||||
self.0.write_prefix(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Display for Infix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use difference::Difference;
|
||||
|
||||
match Difference::between(&self.0, &self.1) {
|
||||
Difference::ExtraStyles(style) => {
|
||||
let f: &mut fmt::Write = f;
|
||||
style.write_prefix(f)
|
||||
},
|
||||
Difference::Reset => {
|
||||
let f: &mut fmt::Write = f;
|
||||
write!(f, "{}{}", RESET, self.0.prefix())
|
||||
},
|
||||
Difference::NoDifference => {
|
||||
Ok(()) // nothing to write
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl fmt::Display for Suffix {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let f: &mut fmt::Write = f;
|
||||
self.0.write_suffix(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use style::Style;
|
||||
use style::Colour::*;
|
||||
|
||||
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!(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");
|
||||
|
||||
}
|
||||
|
|
|
@ -1,122 +1,122 @@
|
|||
use std::fmt;
|
||||
|
||||
use style::Style;
|
||||
|
||||
|
||||
/// Styles have a special `Debug` implementation that only shows the fields that
|
||||
/// are set. Fields that haven’t been touched aren’t included in the output.
|
||||
///
|
||||
/// This behaviour gets bypassed when using the alternate formatting mode
|
||||
/// `format!("{:#?}")`.
|
||||
///
|
||||
/// use ansi_term::Colour::{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 style::Colour::*;
|
||||
use 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() {
|
||||
let debug = r##"Style {
|
||||
foreground: Some(
|
||||
Blue
|
||||
),
|
||||
background: None,
|
||||
blink: false,
|
||||
bold: true,
|
||||
dimmed: false,
|
||||
hidden: false,
|
||||
italic: false,
|
||||
reverse: false,
|
||||
strikethrough: false,
|
||||
underline: false
|
||||
}"##;
|
||||
assert_eq!(debug, format!("{:#?}", Blue.bold()));
|
||||
}
|
||||
}
|
||||
use std::fmt;
|
||||
|
||||
use style::Style;
|
||||
|
||||
|
||||
/// Styles have a special `Debug` implementation that only shows the fields that
|
||||
/// are set. Fields that haven’t been touched aren’t included in the output.
|
||||
///
|
||||
/// This behaviour gets bypassed when using the alternate formatting mode
|
||||
/// `format!("{:#?}")`.
|
||||
///
|
||||
/// use ansi_term::Colour::{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 style::Colour::*;
|
||||
use 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() {
|
||||
let debug = r##"Style {
|
||||
foreground: Some(
|
||||
Blue
|
||||
),
|
||||
background: None,
|
||||
blink: false,
|
||||
bold: true,
|
||||
dimmed: false,
|
||||
hidden: false,
|
||||
italic: false,
|
||||
reverse: false,
|
||||
strikethrough: false,
|
||||
underline: false
|
||||
}"##;
|
||||
assert_eq!(debug, format!("{:#?}", Blue.bold()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,179 +1,179 @@
|
|||
use super::Style;
|
||||
|
||||
|
||||
/// When printing out one coloured 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 colour 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::*;
|
||||
use super::Difference::*;
|
||||
use style::Colour::*;
|
||||
use 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!(colour_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);
|
||||
}
|
||||
use super::Style;
|
||||
|
||||
|
||||
/// When printing out one coloured 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 colour 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::*;
|
||||
use super::Difference::*;
|
||||
use style::Colour::*;
|
||||
use 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!(colour_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);
|
||||
}
|
||||
|
|
|
@ -1,279 +1,279 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
|
||||
use ansi::RESET;
|
||||
use difference::Difference;
|
||||
use style::{Style, Colour};
|
||||
use write::AnyWrite;
|
||||
|
||||
|
||||
/// 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 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.clone(),
|
||||
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 it’s 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
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ansi_term::ANSIString;
|
||||
/// use ansi_term::Colour::Red;
|
||||
///
|
||||
/// let red_string = Red.paint("a red string");
|
||||
/// println!("{}", red_string);
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use 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> 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.
|
||||
pub struct ANSIGenericStrings<'a, S: 'a + ToOwned + ?Sized>
|
||||
(pub &'a [ANSIGenericString<'a, S>])
|
||||
where <S as ToOwned>::Owned: fmt::Debug;
|
||||
|
||||
/// 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 colour, returning an ANSI string.
|
||||
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 Colour {
|
||||
|
||||
/// Paints the given text with this colour, returning an ANSI string.
|
||||
/// This is a short-cut so you don’t have to use `Blue.normal()` just
|
||||
/// to get blue text.
|
||||
///
|
||||
/// ```
|
||||
/// use ansi_term::Colour::Blue;
|
||||
/// println!("{}", Blue.paint("da ba dee"));
|
||||
/// ```
|
||||
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 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 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 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 Styles around each set of
|
||||
/// bytes.
|
||||
pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> {
|
||||
let w: &mut io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> 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 style::Style;
|
||||
pub use style::Colour::*;
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::ops::Deref;
|
||||
|
||||
use ansi::RESET;
|
||||
use difference::Difference;
|
||||
use style::{Style, Colour};
|
||||
use write::AnyWrite;
|
||||
|
||||
|
||||
/// 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 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 it’s 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
|
||||
///
|
||||
/// ```no_run
|
||||
/// use ansi_term::ANSIString;
|
||||
/// use ansi_term::Colour::Red;
|
||||
///
|
||||
/// let red_string = Red.paint("a red string");
|
||||
/// println!("{}", red_string);
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// use 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> 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.
|
||||
pub struct ANSIGenericStrings<'a, S: 'a + ToOwned + ?Sized>
|
||||
(pub &'a [ANSIGenericString<'a, S>])
|
||||
where <S as ToOwned>::Owned: fmt::Debug;
|
||||
|
||||
/// 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 colour, returning an ANSI string.
|
||||
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 Colour {
|
||||
|
||||
/// Paints the given text with this colour, returning an ANSI string.
|
||||
/// This is a short-cut so you don’t have to use `Blue.normal()` just
|
||||
/// to get blue text.
|
||||
///
|
||||
/// ```
|
||||
/// use ansi_term::Colour::Blue;
|
||||
/// println!("{}", Blue.paint("da ba dee"));
|
||||
/// ```
|
||||
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 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 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 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 io::Write = w;
|
||||
self.write_to_any(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: 'a + ToOwned + ?Sized> 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 style::Style;
|
||||
pub use style::Colour::*;
|
||||
|
||||
#[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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,202 +1,205 @@
|
|||
//! This is a library for controlling colours and formatting, such as
|
||||
//! red bold text or blue underlined text, on ANSI terminals.
|
||||
//!
|
||||
//!
|
||||
//! ## Basic usage
|
||||
//!
|
||||
//! There are two main data structures in this crate that you need to be
|
||||
//! concerned with: `ANSIString` and `Style`. A `Style` holds stylistic
|
||||
//! information: colours, whether the text should be bold, or blinking, or
|
||||
//! whatever. There are also `Colour` variants that represent simple foreground
|
||||
//! colour styles. An `ANSIString` is a string paired with a `Style`.
|
||||
//!
|
||||
//! (Yes, it’s British English, but you won’t have to write “colour” very often.
|
||||
//! `Style` is used the majority of the time.)
|
||||
//!
|
||||
//! To format a string, call the `paint` method on a `Style` or a `Colour`,
|
||||
//! passing in the string you want to format as the argument. For example,
|
||||
//! here’s how to get some red text:
|
||||
//!
|
||||
//! use ansi_term::Colour::Red;
|
||||
//! println!("This is in red: {}", Red.paint("a red string"));
|
||||
//!
|
||||
//! It’s 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 ansi_term::Colour::Red;
|
||||
//! use std::string::ToString;
|
||||
//! let red_string = Red.paint("a red string").to_string();
|
||||
//!
|
||||
//!
|
||||
//! ## Bold, underline, background, and other styles
|
||||
//!
|
||||
//! For anything more complex than plain foreground colour changes, you need to
|
||||
//! construct `Style` objects themselves, rather than beginning with a `Colour`.
|
||||
//! 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 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 `Colour` values,
|
||||
//! so you can give your styles a foreground colour without having to begin with
|
||||
//! an empty `Style` value:
|
||||
//!
|
||||
//! use ansi_term::Colour::{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 colours.
|
||||
//!
|
||||
//! In some cases, you may find it easier to change the foreground on an
|
||||
//! existing `Style` rather than starting from the appropriate `Colour`.
|
||||
//! You can do this using the `fg` method:
|
||||
//!
|
||||
//! use ansi_term::Style;
|
||||
//! use ansi_term::Colour::{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!"));
|
||||
//!
|
||||
//! Finally, you can turn a `Colour` into a `Style` with the `normal` method.
|
||||
//! This will produce the exact same `ANSIString` as if you just used the
|
||||
//! `paint` method on the `Colour` directly, but it’s 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 ansi_term::Style;
|
||||
//! use ansi_term::Colour::Red;
|
||||
//! Red.normal().paint("yet another red string");
|
||||
//! Style::default().paint("a completely regular string");
|
||||
//!
|
||||
//!
|
||||
//! ## Extended colours
|
||||
//!
|
||||
//! You can access the extended range of 256 colours by using the `Fixed` colour
|
||||
//! variant, which takes an argument of the colour number to use. This can be
|
||||
//! included wherever you would use a `Colour`:
|
||||
//!
|
||||
//! use ansi_term::Colour::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 colour variants. There’s nothing stopping you from using these as
|
||||
//! `Fixed` colours instead, but there’s nothing to be gained by doing so
|
||||
//! either.
|
||||
//!
|
||||
//! You can also access full 24-bit color by using the `RGB` colour variant,
|
||||
//! which takes separate `u8` arguments for red, green, and blue:
|
||||
//!
|
||||
//! use ansi_term::Colour::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, it’s 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:
|
||||
//!
|
||||
//! use ansi_term::Colour::Red;
|
||||
//! use 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
|
||||
//! `io::Write`:
|
||||
//!
|
||||
//! use ansi_term::Colour::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 ansi_term::Colour::Green;
|
||||
//! use 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();
|
||||
|
||||
|
||||
#![crate_name = "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)]
|
||||
|
||||
mod ansi;
|
||||
pub use ansi::{Prefix, Infix, Suffix};
|
||||
|
||||
mod style;
|
||||
pub use style::{Colour, Style};
|
||||
|
||||
/// Color is a type alias for Colour for those who can't be bothered.
|
||||
pub use Colour as Color;
|
||||
|
||||
// I'm not beyond calling Colour Colour, rather than Color, but I did
|
||||
// purposefully name this crate 'ansi-term' so people wouldn't get
|
||||
// confused when they tried to install it.
|
||||
//
|
||||
// Only *after* they'd installed it.
|
||||
|
||||
mod difference;
|
||||
mod display;
|
||||
pub use display::*;
|
||||
|
||||
mod write;
|
||||
|
||||
mod windows;
|
||||
pub use windows::*;
|
||||
|
||||
mod debug;
|
||||
//! This is a library for controlling colours and formatting, such as
|
||||
//! red bold text or blue underlined text, on ANSI terminals.
|
||||
//!
|
||||
//!
|
||||
//! ## Basic usage
|
||||
//!
|
||||
//! There are two main data structures in this crate that you need to be
|
||||
//! concerned with: `ANSIString` and `Style`. A `Style` holds stylistic
|
||||
//! information: colours, whether the text should be bold, or blinking, or
|
||||
//! whatever. There are also `Colour` variants that represent simple foreground
|
||||
//! colour styles. An `ANSIString` is a string paired with a `Style`.
|
||||
//!
|
||||
//! (Yes, it’s British English, but you won’t have to write “colour” very often.
|
||||
//! `Style` is used the majority of the time.)
|
||||
//!
|
||||
//! To format a string, call the `paint` method on a `Style` or a `Colour`,
|
||||
//! passing in the string you want to format as the argument. For example,
|
||||
//! here’s how to get some red text:
|
||||
//!
|
||||
//! use ansi_term::Colour::Red;
|
||||
//! println!("This is in red: {}", Red.paint("a red string"));
|
||||
//!
|
||||
//! It’s 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 ansi_term::Colour::Red;
|
||||
//! use std::string::ToString;
|
||||
//! let red_string = Red.paint("a red string").to_string();
|
||||
//!
|
||||
//!
|
||||
//! ## Bold, underline, background, and other styles
|
||||
//!
|
||||
//! For anything more complex than plain foreground colour changes, you need to
|
||||
//! construct `Style` objects themselves, rather than beginning with a `Colour`.
|
||||
//! 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 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 `Colour` values,
|
||||
//! so you can give your styles a foreground colour without having to begin with
|
||||
//! an empty `Style` value:
|
||||
//!
|
||||
//! use ansi_term::Colour::{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 colours.
|
||||
//!
|
||||
//! In some cases, you may find it easier to change the foreground on an
|
||||
//! existing `Style` rather than starting from the appropriate `Colour`.
|
||||
//! You can do this using the `fg` method:
|
||||
//!
|
||||
//! use ansi_term::Style;
|
||||
//! use ansi_term::Colour::{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!"));
|
||||
//!
|
||||
//! Finally, you can turn a `Colour` into a `Style` with the `normal` method.
|
||||
//! This will produce the exact same `ANSIString` as if you just used the
|
||||
//! `paint` method on the `Colour` directly, but it’s 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 ansi_term::Style;
|
||||
//! use ansi_term::Colour::Red;
|
||||
//! Red.normal().paint("yet another red string");
|
||||
//! Style::default().paint("a completely regular string");
|
||||
//!
|
||||
//!
|
||||
//! ## Extended colours
|
||||
//!
|
||||
//! You can access the extended range of 256 colours by using the `Fixed` colour
|
||||
//! variant, which takes an argument of the colour number to use. This can be
|
||||
//! included wherever you would use a `Colour`:
|
||||
//!
|
||||
//! use ansi_term::Colour::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 colour variants. There’s nothing stopping you from using these as
|
||||
//! `Fixed` colours instead, but there’s nothing to be gained by doing so
|
||||
//! either.
|
||||
//!
|
||||
//! You can also access full 24-bit color by using the `RGB` colour variant,
|
||||
//! which takes separate `u8` arguments for red, green, and blue:
|
||||
//!
|
||||
//! use ansi_term::Colour::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, it’s 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:
|
||||
//!
|
||||
//! use ansi_term::Colour::Red;
|
||||
//! use 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
|
||||
//! `io::Write`:
|
||||
//!
|
||||
//! use ansi_term::Colour::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 ansi_term::Colour::Green;
|
||||
//! use 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();
|
||||
|
||||
|
||||
#![crate_name = "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;
|
||||
|
||||
mod ansi;
|
||||
pub use ansi::{Prefix, Infix, Suffix};
|
||||
|
||||
mod style;
|
||||
pub use style::{Colour, Style};
|
||||
|
||||
/// Color is a type alias for Colour for those who can't be bothered.
|
||||
pub use Colour as Color;
|
||||
|
||||
// I'm not beyond calling Colour Colour, rather than Color, but I did
|
||||
// purposefully name this crate 'ansi-term' so people wouldn't get
|
||||
// confused when they tried to install it.
|
||||
//
|
||||
// Only *after* they'd installed it.
|
||||
|
||||
mod difference;
|
||||
mod display;
|
||||
pub use display::*;
|
||||
|
||||
mod write;
|
||||
|
||||
mod windows;
|
||||
pub use windows::*;
|
||||
|
||||
mod debug;
|
||||
|
|
|
@ -1,258 +1,259 @@
|
|||
/// A style is a collection of properties that can format a string
|
||||
/// using ANSI escape codes.
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub struct Style {
|
||||
|
||||
/// The style's foreground colour, if it has one.
|
||||
pub foreground: Option<Colour>,
|
||||
|
||||
/// The style's background colour, if it has one.
|
||||
pub background: Option<Colour>,
|
||||
|
||||
/// 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 colours.
|
||||
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 differences.
|
||||
pub fn new() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
/// Returns a Style with the bold property set.
|
||||
pub fn bold(&self) -> Style {
|
||||
Style { is_bold: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the dimmed property set.
|
||||
pub fn dimmed(&self) -> Style {
|
||||
Style { is_dimmed: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the italic property set.
|
||||
pub fn italic(&self) -> Style {
|
||||
Style { is_italic: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the underline property set.
|
||||
pub fn underline(&self) -> Style {
|
||||
Style { is_underline: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the blink property set.
|
||||
pub fn blink(&self) -> Style {
|
||||
Style { is_blink: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the reverse property set.
|
||||
pub fn reverse(&self) -> Style {
|
||||
Style { is_reverse: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the hidden property set.
|
||||
pub fn hidden(&self) -> Style {
|
||||
Style { is_hidden: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the hidden property set.
|
||||
pub fn strikethrough(&self) -> Style {
|
||||
Style { is_strikethrough: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the foreground colour property set.
|
||||
pub fn fg(&self, foreground: Colour) -> Style {
|
||||
Style { foreground: Some(foreground), .. *self }
|
||||
}
|
||||
|
||||
/// Returns a Style with the background colour property set.
|
||||
pub fn on(&self, background: Colour) -> Style {
|
||||
Style { background: Some(background), .. *self }
|
||||
}
|
||||
|
||||
/// Return true if this `Style` has no actual styles, and can be written
|
||||
/// without any control characters.
|
||||
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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- colours ----
|
||||
|
||||
/// A colour is one specific type of ANSI escape code, and can refer
|
||||
/// to either the foreground or background colour.
|
||||
///
|
||||
/// These use the standard numeric sequences.
|
||||
/// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Colour {
|
||||
|
||||
/// Colour #0 (foreground code `30`, background code `40`).
|
||||
///
|
||||
/// This is not necessarily the background colour, and using it as one may
|
||||
/// render the text hard to read on terminals with dark backgrounds.
|
||||
Black,
|
||||
|
||||
/// Colour #1 (foreground code `31`, background code `41`).
|
||||
Red,
|
||||
|
||||
/// Colour #2 (foreground code `32`, background code `42`).
|
||||
Green,
|
||||
|
||||
/// Colour #3 (foreground code `33`, background code `43`).
|
||||
Yellow,
|
||||
|
||||
/// Colour #4 (foreground code `34`, background code `44`).
|
||||
Blue,
|
||||
|
||||
/// Colour #5 (foreground code `35`, background code `45`).
|
||||
Purple,
|
||||
|
||||
/// Colour #6 (foreground code `36`, background code `46`).
|
||||
Cyan,
|
||||
|
||||
/// Colour #7 (foreground code `37`, background code `47`).
|
||||
///
|
||||
/// As above, this is not necessarily the foreground colour, and may be
|
||||
/// hard to read on terminals with light backgrounds.
|
||||
White,
|
||||
|
||||
/// A colour number from 0 to 255, for use in 256-colour terminal
|
||||
/// environments.
|
||||
///
|
||||
/// - Colours 0 to 7 are the `Black` to `White` variants respectively.
|
||||
/// These colours can usually be changed in the terminal emulator.
|
||||
/// - Colours 8 to 15 are brighter versions of the eight colours above.
|
||||
/// These can also usually be changed in the terminal emulator, or it
|
||||
/// could be configured to use the original colours and show the text in
|
||||
/// bold instead. It varies depending on the program.
|
||||
/// - Colours 16 to 231 contain several palettes of bright colours,
|
||||
/// arranged in six squares measuring six by six each.
|
||||
/// - Colours 232 to 255 are shades of grey from black to white.
|
||||
///
|
||||
/// It might make more sense to look at a [colour chart][cc].
|
||||
/// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
|
||||
Fixed(u8),
|
||||
|
||||
/// A 24-bit RGB color, as specified by ISO-8613-3.
|
||||
RGB(u8, u8, u8),
|
||||
}
|
||||
|
||||
|
||||
impl Colour {
|
||||
/// Return a Style with the foreground colour set to this colour.
|
||||
pub fn normal(self) -> Style {
|
||||
Style { foreground: Some(self), .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the bold property set.
|
||||
pub fn bold(self) -> Style {
|
||||
Style { foreground: Some(self), is_bold: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the dimmed property set.
|
||||
pub fn dimmed(self) -> Style {
|
||||
Style { foreground: Some(self), is_dimmed: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the italic property set.
|
||||
pub fn italic(self) -> Style {
|
||||
Style { foreground: Some(self), is_italic: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the underline property set.
|
||||
pub fn underline(self) -> Style {
|
||||
Style { foreground: Some(self), is_underline: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the blink property set.
|
||||
pub fn blink(self) -> Style {
|
||||
Style { foreground: Some(self), is_blink: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the reverse property set.
|
||||
pub fn reverse(self) -> Style {
|
||||
Style { foreground: Some(self), is_reverse: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the hidden property set.
|
||||
pub fn hidden(self) -> Style {
|
||||
Style { foreground: Some(self), is_hidden: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the strikethrough property set.
|
||||
pub fn strikethrough(self) -> Style {
|
||||
Style { foreground: Some(self), is_strikethrough: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a Style with the background colour property set.
|
||||
pub fn on(self, background: Colour) -> Style {
|
||||
Style { foreground: Some(self), background: Some(background), .. Style::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Colour> for Style {
|
||||
|
||||
/// You can turn a `Colour` into a `Style` with the foreground colour set
|
||||
/// with the `From` trait.
|
||||
///
|
||||
/// ```
|
||||
/// use ansi_term::{Style, Colour};
|
||||
/// let green_foreground = Style::default().fg(Colour::Green);
|
||||
/// assert_eq!(green_foreground, Colour::Green.normal());
|
||||
/// assert_eq!(green_foreground, Colour::Green.into());
|
||||
/// assert_eq!(green_foreground, Style::from(Colour::Green));
|
||||
/// ```
|
||||
fn from(colour: Colour) -> Style {
|
||||
colour.normal()
|
||||
}
|
||||
}
|
||||
/// A style is a collection of properties that can format a string
|
||||
/// using ANSI escape codes.
|
||||
#[derive(PartialEq, Clone, Copy)]
|
||||
pub struct Style {
|
||||
|
||||
/// The style's foreground colour, if it has one.
|
||||
pub foreground: Option<Colour>,
|
||||
|
||||
/// The style's background colour, if it has one.
|
||||
pub background: Option<Colour>,
|
||||
|
||||
/// 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 colours.
|
||||
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 differences.
|
||||
pub fn new() -> Style {
|
||||
Style::default()
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the bold property set.
|
||||
pub fn bold(&self) -> Style {
|
||||
Style { is_bold: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the dimmed property set.
|
||||
pub fn dimmed(&self) -> Style {
|
||||
Style { is_dimmed: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the italic property set.
|
||||
pub fn italic(&self) -> Style {
|
||||
Style { is_italic: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the underline property set.
|
||||
pub fn underline(&self) -> Style {
|
||||
Style { is_underline: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the blink property set.
|
||||
pub fn blink(&self) -> Style {
|
||||
Style { is_blink: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the reverse property set.
|
||||
pub fn reverse(&self) -> Style {
|
||||
Style { is_reverse: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the hidden property set.
|
||||
pub fn hidden(&self) -> Style {
|
||||
Style { is_hidden: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the hidden property set.
|
||||
pub fn strikethrough(&self) -> Style {
|
||||
Style { is_strikethrough: true, .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the foreground colour property set.
|
||||
pub fn fg(&self, foreground: Colour) -> Style {
|
||||
Style { foreground: Some(foreground), .. *self }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the background colour property set.
|
||||
pub fn on(&self, background: Colour) -> Style {
|
||||
Style { background: Some(background), .. *self }
|
||||
}
|
||||
|
||||
/// Return true if this `Style` has no actual styles, and can be written
|
||||
/// without any control characters.
|
||||
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 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- colours ----
|
||||
|
||||
/// A colour is one specific type of ANSI escape code, and can refer
|
||||
/// to either the foreground or background colour.
|
||||
///
|
||||
/// These use the standard numeric sequences.
|
||||
/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Colour {
|
||||
|
||||
/// Colour #0 (foreground code `30`, background code `40`).
|
||||
///
|
||||
/// This is not necessarily the background colour, and using it as one may
|
||||
/// render the text hard to read on terminals with dark backgrounds.
|
||||
Black,
|
||||
|
||||
/// Colour #1 (foreground code `31`, background code `41`).
|
||||
Red,
|
||||
|
||||
/// Colour #2 (foreground code `32`, background code `42`).
|
||||
Green,
|
||||
|
||||
/// Colour #3 (foreground code `33`, background code `43`).
|
||||
Yellow,
|
||||
|
||||
/// Colour #4 (foreground code `34`, background code `44`).
|
||||
Blue,
|
||||
|
||||
/// Colour #5 (foreground code `35`, background code `45`).
|
||||
Purple,
|
||||
|
||||
/// Colour #6 (foreground code `36`, background code `46`).
|
||||
Cyan,
|
||||
|
||||
/// Colour #7 (foreground code `37`, background code `47`).
|
||||
///
|
||||
/// As above, this is not necessarily the foreground colour, and may be
|
||||
/// hard to read on terminals with light backgrounds.
|
||||
White,
|
||||
|
||||
/// A colour number from 0 to 255, for use in 256-colour terminal
|
||||
/// environments.
|
||||
///
|
||||
/// - Colours 0 to 7 are the `Black` to `White` variants respectively.
|
||||
/// These colours can usually be changed in the terminal emulator.
|
||||
/// - Colours 8 to 15 are brighter versions of the eight colours above.
|
||||
/// These can also usually be changed in the terminal emulator, or it
|
||||
/// could be configured to use the original colours and show the text in
|
||||
/// bold instead. It varies depending on the program.
|
||||
/// - Colours 16 to 231 contain several palettes of bright colours,
|
||||
/// arranged in six squares measuring six by six each.
|
||||
/// - Colours 232 to 255 are shades of grey from black to white.
|
||||
///
|
||||
/// It might make more sense to look at a [colour chart][cc].
|
||||
///
|
||||
/// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
|
||||
Fixed(u8),
|
||||
|
||||
/// A 24-bit RGB color, as specified by ISO-8613-3.
|
||||
RGB(u8, u8, u8),
|
||||
}
|
||||
|
||||
|
||||
impl Colour {
|
||||
/// Return a `Style` with the foreground colour set to this colour.
|
||||
pub fn normal(self) -> Style {
|
||||
Style { foreground: Some(self), .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the bold property set.
|
||||
pub fn bold(self) -> Style {
|
||||
Style { foreground: Some(self), is_bold: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the dimmed property set.
|
||||
pub fn dimmed(self) -> Style {
|
||||
Style { foreground: Some(self), is_dimmed: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the italic property set.
|
||||
pub fn italic(self) -> Style {
|
||||
Style { foreground: Some(self), is_italic: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the underline property set.
|
||||
pub fn underline(self) -> Style {
|
||||
Style { foreground: Some(self), is_underline: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the blink property set.
|
||||
pub fn blink(self) -> Style {
|
||||
Style { foreground: Some(self), is_blink: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the reverse property set.
|
||||
pub fn reverse(self) -> Style {
|
||||
Style { foreground: Some(self), is_reverse: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the hidden property set.
|
||||
pub fn hidden(self) -> Style {
|
||||
Style { foreground: Some(self), is_hidden: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the strikethrough property set.
|
||||
pub fn strikethrough(self) -> Style {
|
||||
Style { foreground: Some(self), is_strikethrough: true, .. Style::default() }
|
||||
}
|
||||
|
||||
/// Returns a `Style` with the background colour property set.
|
||||
pub fn on(self, background: Colour) -> Style {
|
||||
Style { foreground: Some(self), background: Some(background), .. Style::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Colour> for Style {
|
||||
|
||||
/// You can turn a `Colour` into a `Style` with the foreground colour set
|
||||
/// with the `From` trait.
|
||||
///
|
||||
/// ```
|
||||
/// use ansi_term::{Style, Colour};
|
||||
/// let green_foreground = Style::default().fg(Colour::Green);
|
||||
/// assert_eq!(green_foreground, Colour::Green.normal());
|
||||
/// assert_eq!(green_foreground, Colour::Green.into());
|
||||
/// assert_eq!(green_foreground, Style::from(Colour::Green));
|
||||
/// ```
|
||||
fn from(colour: Colour) -> Style {
|
||||
colour.normal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,35 +1,40 @@
|
|||
/// 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<(), u64> {
|
||||
|
||||
#[link(name = "kernel32")]
|
||||
extern {
|
||||
fn GetStdHandle(handle: u64) -> *const i32;
|
||||
fn SetConsoleMode(handle: *const i32, mode: u32) -> bool;
|
||||
fn GetLastError() -> u64;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
const STD_OUT_HANDLE: u64 = -11i32 as u64;
|
||||
const ENABLE_ANSI_CODES: u32 = 7;
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms683231(v=vs.85).aspx
|
||||
let std_out_handle = GetStdHandle(STD_OUT_HANDLE);
|
||||
let error_code = GetLastError();
|
||||
if error_code != 0 { return Err(error_code); }
|
||||
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
|
||||
SetConsoleMode(std_out_handle, ENABLE_ANSI_CODES);
|
||||
let error_code = GetLastError();
|
||||
if error_code != 0 { return Err(error_code); }
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
/// 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> {
|
||||
use winapi::um::processenv::GetStdHandle;
|
||||
use winapi::um::errhandlingapi::GetLastError;
|
||||
use winapi::um::consoleapi::{GetConsoleMode, SetConsoleMode};
|
||||
|
||||
const STD_OUT_HANDLE: u32 = -11i32 as u32;
|
||||
const ENABLE_VIRTUAL_TERMINAL_PROCESSING: u32 = 0x0004;
|
||||
|
||||
unsafe {
|
||||
// https://docs.microsoft.com/en-us/windows/console/getstdhandle
|
||||
let std_out_handle = GetStdHandle(STD_OUT_HANDLE);
|
||||
let error_code = GetLastError();
|
||||
if error_code != 0 { return Err(error_code); }
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/console/getconsolemode
|
||||
let mut console_mode: u32 = 0;
|
||||
GetConsoleMode(std_out_handle, &mut console_mode);
|
||||
let error_code = GetLastError();
|
||||
if error_code != 0 { return Err(error_code); }
|
||||
|
||||
// VT processing not already enabled?
|
||||
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
SetConsoleMode(std_out_handle, console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
|
||||
let error_code = GetLastError();
|
||||
if error_code != 0 { return Err(error_code); }
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -1,40 +1,40 @@
|
|||
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 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 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)
|
||||
}
|
||||
}
|
||||
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 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 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)
|
||||
}
|
||||
}
|
||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -35,7 +35,7 @@ $ just run-test <test_name>
|
|||
|
||||
### Linting Code
|
||||
|
||||
During the CI process `clap` runs against many different lints using [`clippy`](https://github.com/Manishearth/rust-clippy). In order to check if these lints pass on your own computer prior to submitting a PR you'll need a nightly compiler.
|
||||
During the CI process `clap` runs against many different lints using [`clippy`](https://github.com/rust-lang-nursery/rust-clippy). In order to check if these lints pass on your own computer prior to submitting a PR you'll need a nightly compiler.
|
||||
|
||||
In order to check the code for lints run either:
|
||||
|
||||
|
|
|
@ -22,19 +22,21 @@ before_script:
|
|||
fi
|
||||
script:
|
||||
- |
|
||||
travis-cargo test -- --verbose --no-default-features &&
|
||||
travis-cargo --only stable test -- --verbose --no-default-features &&
|
||||
travis-cargo --skip nightly test -- --verbose --features "yaml unstable" &&
|
||||
travis-cargo --only nightly test -- --verbose --features "yaml unstable nightly" &&
|
||||
travis-cargo --only nightly bench
|
||||
travis-cargo --only nightly bench -- --no-run
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- binutils-dev
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
- libiberty-dev
|
||||
- cmake
|
||||
- gcc
|
||||
- binutils-dev
|
||||
- zlib1g-dev
|
||||
after_success:
|
||||
- |
|
||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
||||
|
|
|
@ -1,3 +1,134 @@
|
|||
<a name="v2.31.2"></a>
|
||||
### v2.31.2 (2018-03-19)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **Fish Completions:** fixes a bug that only allowed a single completion in in Fish Shell ([e8774a8](https://github.com/kbknapp/clap-rs/pull/1214/commits/e8774a84ee4a319c888036e7c595ab46451d8e48), closes [#1212](https://github.com/kbknapp/clap-rs/issues/1212))
|
||||
* **AllowExternalSubcommands**: fixes a bug where external subcommands would be blocked by a similarly named subcomand (suggestions were getting in the way). ([a410e85](https://github.com/kbknapp/clap-rs/pull/1215/commits/a410e855bcd82b05f9efa73fa8b9774dc8842c6b))
|
||||
|
||||
#### Documentation
|
||||
|
||||
* Fixes some typos in the `README.md` ([c8e685d7](https://github.com/kbknapp/clap-rs/commit/c8e685d76adee2a3cc06cac6952ffcf6f9548089))
|
||||
|
||||
<a name="v2.31.1"></a>
|
||||
### v2.31.1 (2018-03-06)
|
||||
|
||||
|
||||
#### Improvements
|
||||
|
||||
* **AllowMissingPositional:** improves the ability of AllowMissingPositional to allow 'skipping' to the last positional arg with '--' ([df20e6e2](https://github.com/kbknapp/clap-rs/commit/df20e6e24b4e782be0b423b484b9798e3e2efe2f))
|
||||
|
||||
|
||||
<a name="v2.31.0"></a>
|
||||
## v2.31.0 (2018-03-04)
|
||||
|
||||
|
||||
#### Features
|
||||
|
||||
* **Arg Indices:** adds the ability to query argument value indices ([f58d0576](https://github.com/kbknapp/clap-rs/commit/f58d05767ec8133c8eb2de117cb642b9ae29ccbc))
|
||||
* **Indices:** implements an Indices<Item=&usize> iterator ([1e67be44](https://github.com/kbknapp/clap-rs/commit/1e67be44f0ccf161cc84c4e6082382072e89c302))
|
||||
* **Raw Args** adds a convenience function to `Arg` that allows implying all of `Arg::last` `Arg::allow_hyphen_values` and `Arg::multiple(true)` ([66a78f29](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2))
|
||||
|
||||
#### Documentation
|
||||
|
||||
* Fix some typos and markdown issues. ([935ba0dd](https://github.com/kbknapp/clap-rs/commit/935ba0dd547a69c3f636c5486795012019408794))
|
||||
* **Arg Indices:** adds the documentation for the arg index querying methods ([50bc0047](https://github.com/kbknapp/clap-rs/commit/50bc00477afa64dc6cdc5de161d3de3ba1d105a7))
|
||||
* **CONTRIBUTING.md:** fix url to clippy upstream repo to point to https://github.com/rust-lang-nursery/rust-clippy instead of https://github.com/Manishearth/rust-clippy ([42407d7f](https://github.com/kbknapp/clap-rs/commit/42407d7f21d794103cda61f49d2615aae0a4bcd9))
|
||||
* **Values:** improves the docs example of the Values iterator ([74075d65](https://github.com/kbknapp/clap-rs/commit/74075d65e8db1ddb5e2a4558009a5729d749d1b6))
|
||||
* Updates readme to hint that the `wrap_help` feature is a thing ([fc7ab227](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2))
|
||||
|
||||
### Improvements
|
||||
|
||||
* Cargo.toml: use codegen-units = 1 in release and bench profiles ([19f425ea](https://github.com/kbknapp/clap-rs/commit/66a78f2972786f5fe7c07937a1ac23da2542afd2))
|
||||
* Adds WASM support (clap now compiles on WASM!) ([689949e5](https://github.com/kbknapp/clap-rs/commit/689949e57d390bb61bc69f3ed91f60a2105738d0))
|
||||
* Uses the short help tool-tip for PowerShell completion scripts ([ecda22ce](https://github.com/kbknapp/clap-rs/commit/ecda22ce7210ce56d7b2d1a5445dd1b8a2959656))
|
||||
|
||||
|
||||
<a name="v2.30.0"></a>
|
||||
## v2.30.0 (2018-02-13)
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **YAML:** Adds a missing conversion from `Arg::last` when instantiating from a YAML file ([aab77c81a5](https://github.com/kbknapp/clap-rs/pull/1175/commits/aab77c81a519b045f95946ae0dd3e850f9b93070), closes [#1160](https://github.com/kbknapp/clap-rs/issues/1173))
|
||||
|
||||
#### Improvements
|
||||
|
||||
* **Bash Completions:** instead of completing a generic option name, all bash completions fall back to file completions UNLESS `Arg::possible_values` was used ([872f02ae](https://github.com/kbknapp/clap-rs/commit/872f02aea900ffa376850a279eb164645e1234fa))
|
||||
* **Deps:** No longer needlessly compiles `ansi_term` on Windows since its not used ([b57ee946](https://github.com/kbknapp/clap-rs/commit/b57ee94609da3ddc897286cfba968f26ff961491), closes [#1155](https://github.com/kbknapp/clap-rs/issues/1155))
|
||||
* **Help Message:** changes the `[values: foo bar baz]` array to `[possible values: foo bar baz]` for consistency with the API ([414707e4e97](https://github.com/kbknapp/clap-rs/pull/1176/commits/414707e4e979d07bfe555247e5d130c546673708), closes [#1160](https://github.com/kbknapp/clap-rs/issues/1160))
|
||||
|
||||
|
||||
<a name="v2.29.4"></a>
|
||||
### v2.29.4 (2018-02-06)
|
||||
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **Overrides Self:** fixes a bug where options with multiple values couldnt ever have multiple values ([d95907cf](https://github.com/kbknapp/clap-rs/commit/d95907cff6d011a901fe35fa00b0f4e18547a1fb))
|
||||
|
||||
|
||||
|
||||
<a name="v2.29.3"></a>
|
||||
### v2.29.3 (2018-02-05)
|
||||
|
||||
|
||||
#### Improvements
|
||||
|
||||
* **Overrides:** clap now supports arguments which override with themselves ([6c7a0010](https://github.com/kbknapp/clap-rs/commit/6c7a001023ca1eac1cc6ffe6c936b4c4a2aa3c45), closes [#976](https://github.com/kbknapp/clap-rs/issues/976))
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **Requirements:** fixes an issue where conflicting args would still show up as required ([e06cefac](https://github.com/kbknapp/clap-rs/commit/e06cefac97083838c0a4e1444dcad02a5c3f911e), closes [#1158](https://github.com/kbknapp/clap-rs/issues/1158))
|
||||
* Fixes a bug which disallows proper nesting of `--` ([73993fe](https://github.com/kbknapp/clap-rs/commit/73993fe30d135f682e763ec93dcb0814ed518011), closes [#1161](https://github.com/kbknapp/clap-rs/issues/1161))
|
||||
|
||||
#### New Settings
|
||||
|
||||
* **AllArgsOverrideSelf:** adds a new convenience setting to allow all args to override themselves ([4670325d](https://github.com/kbknapp/clap-rs/commit/4670325d1bf0369addec2ae2bcb56f1be054c924))
|
||||
|
||||
|
||||
|
||||
<a name="v2.29.2"></a>
|
||||
### v2.29.2 (2018-01-16)
|
||||
|
||||
|
||||
#### Features
|
||||
|
||||
* **completions/zsh.rs:**
|
||||
* Escape possible values for options ([25561dec](https://github.com/kbknapp/clap-rs/commit/25561decf147d329b64634a14d9695673c2fc78f))
|
||||
* Implement postional argument possible values completion ([f3b0afd2](https://github.com/kbknapp/clap-rs/commit/f3b0afd2bef8b7be97162f8a7802ddf7603dff36))
|
||||
* Complete positional arguments properly ([e39aeab8](https://github.com/kbknapp/clap-rs/commit/e39aeab8487596046fbdbc6a226e5c8820585245))
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **completions/zsh.rs:**
|
||||
* Add missing autoload for is-at-least ([a6522607](https://github.com/kbknapp/clap-rs/commit/a652260795d1519f6ec2a7a09ccc1258499cad7b))
|
||||
* Don't pass -S to _arguments if Zsh is too old ([16b4f143](https://github.com/kbknapp/clap-rs/commit/16b4f143ff466b7ef18a267bc44ade0f9639109b))
|
||||
* Maybe fix completions with mixed positionals and subcommands ([1146f0da](https://github.com/kbknapp/clap-rs/commit/1146f0da154d6796fbfcb09db8efa3593cb0d898))
|
||||
* **completions/zsh.zsh:** Remove redundant code from output ([0e185b92](https://github.com/kbknapp/clap-rs/commit/0e185b922ed1e0fd653de00b4cd8d567d72ff68e), closes [#1142](https://github.com/kbknapp/clap-rs/issues/1142))
|
||||
|
||||
|
||||
|
||||
<a name="2.29.1"></a>
|
||||
### 2.29.1 (2018-01-09)
|
||||
|
||||
|
||||
#### Documentation
|
||||
|
||||
* fixes broken links. ([56e734b8](https://github.com/kbknapp/clap-rs/commit/56e734b839303d733d2e5baf7dac39bd7b97b8e4))
|
||||
* updates contributors list ([e1313a5a](https://github.com/kbknapp/clap-rs/commit/e1313a5a0f69d8f4016f73b860a63af8318a6676))
|
||||
|
||||
#### Performance
|
||||
|
||||
* further debloating by removing generics from error cases ([eb8d919e](https://github.com/kbknapp/clap-rs/commit/eb8d919e6f3443db279ba0c902f15d76676c02dc))
|
||||
* debloats clap by deduplicating logic and refactors ([03e413d7](https://github.com/kbknapp/clap-rs/commit/03e413d7175d35827cd7d8908d47dbae15a849a3))
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* fixes the ripgrep benchmark by adding a value to a flag that expects it ([d26ab2b9](https://github.com/kbknapp/clap-rs/commit/d26ab2b97cf9c0ea675b440b7b0eaf6ac3ad01f4))
|
||||
* **bash completion:** Change the bash completion script code generation to support hyphens. ([ba7f1d18](https://github.com/kbknapp/clap-rs/commit/ba7f1d18eba7a07ce7f57e0981986f66c994b639))
|
||||
* **completions/zsh.rs:** Fix completion of long option values ([46365cf8](https://github.com/kbknapp/clap-rs/commit/46365cf8be5331ba04c895eb183e2f230b5aad51))
|
||||
|
||||
|
||||
<a name="2.29.0"></a>
|
||||
## 2.29.0 (2017-12-02)
|
||||
|
||||
|
@ -590,7 +721,7 @@ Minimum version of Rust is now v1.13.0 (Stable)
|
|||
|
||||
#### Bug Fixes
|
||||
|
||||
* **Low Index Multiples:** fixes a bug where using low index multiples was propgated to subcommands ([33924e88](https://github.com/kbknapp/clap-rs/commit/33924e884461983c4e6b5ea1330fecc769a4ade7), closes [#725](https://github.com/kbknapp/clap-rs/issues/725))
|
||||
* **Low Index Multiples:** fixes a bug where using low index multiples was propagated to subcommands ([33924e88](https://github.com/kbknapp/clap-rs/commit/33924e884461983c4e6b5ea1330fecc769a4ade7), closes [#725](https://github.com/kbknapp/clap-rs/issues/725))
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1,85 +1,89 @@
|
|||
the following is a list of contributors:
|
||||
|
||||
|
||||
[<img alt="kbknapp" src="https://avatars1.githubusercontent.com/u/6942134?v=4&s=117" width="117">](https://github.com/kbknapp) |[<img alt="homu" src="https://avatars1.githubusercontent.com/u/10212162?v=4&s=117" width="117">](https://github.com/homu) |[<img alt="Vinatorul" src="https://avatars1.githubusercontent.com/u/6770624?v=4&s=117" width="117">](https://github.com/Vinatorul) |[<img alt="tormol" src="https://avatars3.githubusercontent.com/u/10460821?v=4&s=117" width="117">](https://github.com/tormol) |[<img alt="little-dude" src="https://avatars2.githubusercontent.com/u/6646324?v=4&s=117" width="117">](https://github.com/little-dude) |[<img alt="sru" src="https://avatars3.githubusercontent.com/u/2485892?v=4&s=117" width="117">](https://github.com/sru) |
|
||||
[<img alt="kbknapp" src="https://avatars1.githubusercontent.com/u/6942134?v=4&s=117" width="117">](https://github.com/kbknapp) |[<img alt="homu" src="https://avatars1.githubusercontent.com/u/10212162?v=4&s=117" width="117">](https://github.com/homu) |[<img alt="Vinatorul" src="https://avatars1.githubusercontent.com/u/6770624?v=4&s=117" width="117">](https://github.com/Vinatorul) |[<img alt="tormol" src="https://avatars3.githubusercontent.com/u/10460821?v=4&s=117" width="117">](https://github.com/tormol) |[<img alt="willmurphyscode" src="https://avatars3.githubusercontent.com/u/12529630?v=4&s=117" width="117">](https://github.com/willmurphyscode) |[<img alt="little-dude" src="https://avatars2.githubusercontent.com/u/6646324?v=4&s=117" width="117">](https://github.com/little-dude) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[kbknapp](https://github.com/kbknapp) |[homu](https://github.com/homu) |[Vinatorul](https://github.com/Vinatorul) |[tormol](https://github.com/tormol) |[little-dude](https://github.com/little-dude) |[sru](https://github.com/sru) |
|
||||
[kbknapp](https://github.com/kbknapp) |[homu](https://github.com/homu) |[Vinatorul](https://github.com/Vinatorul) |[tormol](https://github.com/tormol) |[willmurphyscode](https://github.com/willmurphyscode) |[little-dude](https://github.com/little-dude) |
|
||||
|
||||
[<img alt="willmurphyscode" src="https://avatars3.githubusercontent.com/u/12529630?v=4&s=117" width="117">](https://github.com/willmurphyscode) |[<img alt="mgeisler" src="https://avatars0.githubusercontent.com/u/89623?v=4&s=117" width="117">](https://github.com/mgeisler) |[<img alt="nabijaczleweli" src="https://avatars3.githubusercontent.com/u/6709544?v=4&s=117" width="117">](https://github.com/nabijaczleweli) |[<img alt="Byron" src="https://avatars2.githubusercontent.com/u/63622?v=4&s=117" width="117">](https://github.com/Byron) |[<img alt="hgrecco" src="https://avatars0.githubusercontent.com/u/278566?v=4&s=117" width="117">](https://github.com/hgrecco) |[<img alt="bluejekyll" src="https://avatars3.githubusercontent.com/u/986845?v=4&s=117" width="117">](https://github.com/bluejekyll) |
|
||||
[<img alt="sru" src="https://avatars3.githubusercontent.com/u/2485892?v=4&s=117" width="117">](https://github.com/sru) |[<img alt="mgeisler" src="https://avatars0.githubusercontent.com/u/89623?v=4&s=117" width="117">](https://github.com/mgeisler) |[<img alt="nabijaczleweli" src="https://avatars3.githubusercontent.com/u/6709544?v=4&s=117" width="117">](https://github.com/nabijaczleweli) |[<img alt="Byron" src="https://avatars2.githubusercontent.com/u/63622?v=4&s=117" width="117">](https://github.com/Byron) |[<img alt="hgrecco" src="https://avatars0.githubusercontent.com/u/278566?v=4&s=117" width="117">](https://github.com/hgrecco) |[<img alt="bluejekyll" src="https://avatars3.githubusercontent.com/u/986845?v=4&s=117" width="117">](https://github.com/bluejekyll) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[willmurphyscode](https://github.com/willmurphyscode) |[mgeisler](https://github.com/mgeisler) |[nabijaczleweli](https://github.com/nabijaczleweli) |[Byron](https://github.com/Byron) |[hgrecco](https://github.com/hgrecco) |[bluejekyll](https://github.com/bluejekyll) |
|
||||
[sru](https://github.com/sru) |[mgeisler](https://github.com/mgeisler) |[nabijaczleweli](https://github.com/nabijaczleweli) |[Byron](https://github.com/Byron) |[hgrecco](https://github.com/hgrecco) |[bluejekyll](https://github.com/bluejekyll) |
|
||||
|
||||
[<img alt="ignatenkobrain" src="https://avatars1.githubusercontent.com/u/2866862?v=4&s=117" width="117">](https://github.com/ignatenkobrain) |[<img alt="james-darkfox" src="https://avatars3.githubusercontent.com/u/637155?v=4&s=117" width="117">](https://github.com/james-darkfox) |[<img alt="H2CO3" src="https://avatars2.githubusercontent.com/u/742370?v=4&s=117" width="117">](https://github.com/H2CO3) |[<img alt="nateozem" src="https://avatars2.githubusercontent.com/u/22719441?v=4&s=117" width="117">](https://github.com/nateozem) |[<img alt="glowing-chemist" src="https://avatars0.githubusercontent.com/u/17074682?v=4&s=117" width="117">](https://github.com/glowing-chemist) |[<img alt="rtaycher" src="https://avatars0.githubusercontent.com/u/324733?v=4&s=117" width="117">](https://github.com/rtaycher) |
|
||||
[<img alt="segevfiner" src="https://avatars0.githubusercontent.com/u/24731903?v=4&s=117" width="117">](https://github.com/segevfiner) |[<img alt="ignatenkobrain" src="https://avatars1.githubusercontent.com/u/2866862?v=4&s=117" width="117">](https://github.com/ignatenkobrain) |[<img alt="james-darkfox" src="https://avatars3.githubusercontent.com/u/637155?v=4&s=117" width="117">](https://github.com/james-darkfox) |[<img alt="H2CO3" src="https://avatars2.githubusercontent.com/u/742370?v=4&s=117" width="117">](https://github.com/H2CO3) |[<img alt="nateozem" src="https://avatars2.githubusercontent.com/u/22719441?v=4&s=117" width="117">](https://github.com/nateozem) |[<img alt="glowing-chemist" src="https://avatars0.githubusercontent.com/u/17074682?v=4&s=117" width="117">](https://github.com/glowing-chemist) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[ignatenkobrain](https://github.com/ignatenkobrain) |[james-darkfox](https://github.com/james-darkfox) |[H2CO3](https://github.com/H2CO3) |[nateozem](https://github.com/nateozem) |[glowing-chemist](https://github.com/glowing-chemist) |[rtaycher](https://github.com/rtaycher) |
|
||||
[segevfiner](https://github.com/segevfiner) |[ignatenkobrain](https://github.com/ignatenkobrain) |[james-darkfox](https://github.com/james-darkfox) |[H2CO3](https://github.com/H2CO3) |[nateozem](https://github.com/nateozem) |[glowing-chemist](https://github.com/glowing-chemist) |
|
||||
|
||||
[<img alt="Arnavion" src="https://avatars2.githubusercontent.com/u/1096010?v=4&s=117" width="117">](https://github.com/Arnavion) |[<img alt="japaric" src="https://avatars3.githubusercontent.com/u/5018213?v=4&s=117" width="117">](https://github.com/japaric) |[<img alt="untitaker" src="https://avatars0.githubusercontent.com/u/837573?v=4&s=117" width="117">](https://github.com/untitaker) |[<img alt="afiune" src="https://avatars0.githubusercontent.com/u/5712253?v=4&s=117" width="117">](https://github.com/afiune) |[<img alt="crazymerlyn" src="https://avatars1.githubusercontent.com/u/6919679?v=4&s=117" width="117">](https://github.com/crazymerlyn) |[<img alt="SuperFluffy" src="https://avatars0.githubusercontent.com/u/701177?v=4&s=117" width="117">](https://github.com/SuperFluffy) |
|
||||
[<img alt="discosultan" src="https://avatars1.githubusercontent.com/u/2970736?v=4&s=117" width="117">](https://github.com/discosultan) |[<img alt="rtaycher" src="https://avatars0.githubusercontent.com/u/324733?v=4&s=117" width="117">](https://github.com/rtaycher) |[<img alt="Arnavion" src="https://avatars2.githubusercontent.com/u/1096010?v=4&s=117" width="117">](https://github.com/Arnavion) |[<img alt="japaric" src="https://avatars3.githubusercontent.com/u/5018213?v=4&s=117" width="117">](https://github.com/japaric) |[<img alt="untitaker" src="https://avatars0.githubusercontent.com/u/837573?v=4&s=117" width="117">](https://github.com/untitaker) |[<img alt="afiune" src="https://avatars0.githubusercontent.com/u/5712253?v=4&s=117" width="117">](https://github.com/afiune) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[Arnavion](https://github.com/Arnavion) |[japaric](https://github.com/japaric) |[untitaker](https://github.com/untitaker) |[afiune](https://github.com/afiune) |[crazymerlyn](https://github.com/crazymerlyn) |[SuperFluffy](https://github.com/SuperFluffy) |
|
||||
[discosultan](https://github.com/discosultan) |[rtaycher](https://github.com/rtaycher) |[Arnavion](https://github.com/Arnavion) |[japaric](https://github.com/japaric) |[untitaker](https://github.com/untitaker) |[afiune](https://github.com/afiune) |
|
||||
|
||||
[<img alt="malbarbo" src="https://avatars3.githubusercontent.com/u/1678126?v=4&s=117" width="117">](https://github.com/malbarbo) |[<img alt="matthiasbeyer" src="https://avatars0.githubusercontent.com/u/427866?v=4&s=117" width="117">](https://github.com/matthiasbeyer) |[<img alt="gohyda" src="https://avatars3.githubusercontent.com/u/10263838?v=4&s=117" width="117">](https://github.com/gohyda) |[<img alt="tshepang" src="https://avatars0.githubusercontent.com/u/588486?v=4&s=117" width="117">](https://github.com/tshepang) |[<img alt="golem131" src="https://avatars3.githubusercontent.com/u/2429587?v=4&s=117" width="117">](https://github.com/golem131) |[<img alt="jimmycuadra" src="https://avatars2.githubusercontent.com/u/122457?v=4&s=117" width="117">](https://github.com/jimmycuadra) |
|
||||
[<img alt="crazymerlyn" src="https://avatars1.githubusercontent.com/u/6919679?v=4&s=117" width="117">](https://github.com/crazymerlyn) |[<img alt="SuperFluffy" src="https://avatars0.githubusercontent.com/u/701177?v=4&s=117" width="117">](https://github.com/SuperFluffy) |[<img alt="matthiasbeyer" src="https://avatars0.githubusercontent.com/u/427866?v=4&s=117" width="117">](https://github.com/matthiasbeyer) |[<img alt="malbarbo" src="https://avatars3.githubusercontent.com/u/1678126?v=4&s=117" width="117">](https://github.com/malbarbo) |[<img alt="tshepang" src="https://avatars0.githubusercontent.com/u/588486?v=4&s=117" width="117">](https://github.com/tshepang) |[<img alt="golem131" src="https://avatars3.githubusercontent.com/u/2429587?v=4&s=117" width="117">](https://github.com/golem131) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[malbarbo](https://github.com/malbarbo) |[matthiasbeyer](https://github.com/matthiasbeyer) |[gohyda](https://github.com/gohyda) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) |[jimmycuadra](https://github.com/jimmycuadra) |
|
||||
[crazymerlyn](https://github.com/crazymerlyn) |[SuperFluffy](https://github.com/SuperFluffy) |[matthiasbeyer](https://github.com/matthiasbeyer) |[malbarbo](https://github.com/malbarbo) |[tshepang](https://github.com/tshepang) |[golem131](https://github.com/golem131) |
|
||||
|
||||
[<img alt="Nemo157" src="https://avatars1.githubusercontent.com/u/81079?v=4&s=117" width="117">](https://github.com/Nemo157) |[<img alt="SShrike" src="https://avatars1.githubusercontent.com/u/4061736?v=4&s=117" width="117">](https://github.com/SShrike) |[<img alt="Eijebong" src="https://avatars2.githubusercontent.com/u/3650385?v=4&s=117" width="117">](https://github.com/Eijebong) |[<img alt="cstorey" src="https://avatars3.githubusercontent.com/u/743059?v=4&s=117" width="117">](https://github.com/cstorey) |[<img alt="wdv4758h" src="https://avatars1.githubusercontent.com/u/2716047?v=4&s=117" width="117">](https://github.com/wdv4758h) |[<img alt="frewsxcv" src="https://avatars2.githubusercontent.com/u/416575?v=4&s=117" width="117">](https://github.com/frewsxcv) |
|
||||
[<img alt="jimmycuadra" src="https://avatars2.githubusercontent.com/u/122457?v=4&s=117" width="117">](https://github.com/jimmycuadra) |[<img alt="Nemo157" src="https://avatars1.githubusercontent.com/u/81079?v=4&s=117" width="117">](https://github.com/Nemo157) |[<img alt="severen" src="https://avatars1.githubusercontent.com/u/4061736?v=4&s=117" width="117">](https://github.com/severen) |[<img alt="Eijebong" src="https://avatars2.githubusercontent.com/u/3650385?v=4&s=117" width="117">](https://github.com/Eijebong) |[<img alt="cstorey" src="https://avatars3.githubusercontent.com/u/743059?v=4&s=117" width="117">](https://github.com/cstorey) |[<img alt="wdv4758h" src="https://avatars1.githubusercontent.com/u/2716047?v=4&s=117" width="117">](https://github.com/wdv4758h) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[Nemo157](https://github.com/Nemo157) |[SShrike](https://github.com/SShrike) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) |[frewsxcv](https://github.com/frewsxcv) |
|
||||
[jimmycuadra](https://github.com/jimmycuadra) |[Nemo157](https://github.com/Nemo157) |[severen](https://github.com/severen) |[Eijebong](https://github.com/Eijebong) |[cstorey](https://github.com/cstorey) |[wdv4758h](https://github.com/wdv4758h) |
|
||||
|
||||
[<img alt="hoodie" src="https://avatars1.githubusercontent.com/u/260370?v=4&s=117" width="117">](https://github.com/hoodie) |[<img alt="huonw" src="https://avatars1.githubusercontent.com/u/1203825?v=4&s=117" width="117">](https://github.com/huonw) |[<img alt="GrappigPanda" src="https://avatars0.githubusercontent.com/u/2055372?v=4&s=117" width="117">](https://github.com/GrappigPanda) |[<img alt="shepmaster" src="https://avatars0.githubusercontent.com/u/174509?v=4&s=117" width="117">](https://github.com/shepmaster) |[<img alt="porglezomp" src="https://avatars1.githubusercontent.com/u/1690225?v=4&s=117" width="117">](https://github.com/porglezomp) |[<img alt="kieraneglin" src="https://avatars0.githubusercontent.com/u/569917?v=4&s=117" width="117">](https://github.com/kieraneglin) |
|
||||
[<img alt="frewsxcv" src="https://avatars2.githubusercontent.com/u/416575?v=4&s=117" width="117">](https://github.com/frewsxcv) |[<img alt="hoodie" src="https://avatars1.githubusercontent.com/u/260370?v=4&s=117" width="117">](https://github.com/hoodie) |[<img alt="huonw" src="https://avatars1.githubusercontent.com/u/1203825?v=4&s=117" width="117">](https://github.com/huonw) |[<img alt="GrappigPanda" src="https://avatars0.githubusercontent.com/u/2055372?v=4&s=117" width="117">](https://github.com/GrappigPanda) |[<img alt="shepmaster" src="https://avatars0.githubusercontent.com/u/174509?v=4&s=117" width="117">](https://github.com/shepmaster) |[<img alt="starkat99" src="https://avatars1.githubusercontent.com/u/8295111?v=4&s=117" width="117">](https://github.com/starkat99) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[hoodie](https://github.com/hoodie) |[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[porglezomp](https://github.com/porglezomp) |[kieraneglin](https://github.com/kieraneglin) |
|
||||
[frewsxcv](https://github.com/frewsxcv) |[hoodie](https://github.com/hoodie) |[huonw](https://github.com/huonw) |[GrappigPanda](https://github.com/GrappigPanda) |[shepmaster](https://github.com/shepmaster) |[starkat99](https://github.com/starkat99) |
|
||||
|
||||
[<img alt="musoke" src="https://avatars0.githubusercontent.com/u/16665084?v=4&s=117" width="117">](https://github.com/musoke) |[<img alt="nelsonjchen" src="https://avatars1.githubusercontent.com/u/5363?v=4&s=117" width="117">](https://github.com/nelsonjchen) |[<img alt="pkgw" src="https://avatars0.githubusercontent.com/u/59598?v=4&s=117" width="117">](https://github.com/pkgw) |[<img alt="Deedasmi" src="https://avatars0.githubusercontent.com/u/5093293?v=4&s=117" width="117">](https://github.com/Deedasmi) |[<img alt="vmchale" src="https://avatars1.githubusercontent.com/u/13259982?v=4&s=117" width="117">](https://github.com/vmchale) |[<img alt="messense" src="https://avatars0.githubusercontent.com/u/1556054?v=4&s=117" width="117">](https://github.com/messense) |
|
||||
[<img alt="porglezomp" src="https://avatars1.githubusercontent.com/u/1690225?v=4&s=117" width="117">](https://github.com/porglezomp) |[<img alt="kraai" src="https://avatars1.githubusercontent.com/u/552646?v=4&s=117" width="117">](https://github.com/kraai) |[<img alt="musoke" src="https://avatars0.githubusercontent.com/u/16665084?v=4&s=117" width="117">](https://github.com/musoke) |[<img alt="nelsonjchen" src="https://avatars1.githubusercontent.com/u/5363?v=4&s=117" width="117">](https://github.com/nelsonjchen) |[<img alt="pkgw" src="https://avatars0.githubusercontent.com/u/59598?v=4&s=117" width="117">](https://github.com/pkgw) |[<img alt="Deedasmi" src="https://avatars0.githubusercontent.com/u/5093293?v=4&s=117" width="117">](https://github.com/Deedasmi) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[musoke](https://github.com/musoke) |[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) |[vmchale](https://github.com/vmchale) |[messense](https://github.com/messense) |
|
||||
[porglezomp](https://github.com/porglezomp) |[kraai](https://github.com/kraai) |[musoke](https://github.com/musoke) |[nelsonjchen](https://github.com/nelsonjchen) |[pkgw](https://github.com/pkgw) |[Deedasmi](https://github.com/Deedasmi) |
|
||||
|
||||
[<img alt="Keats" src="https://avatars2.githubusercontent.com/u/680355?v=4&s=117" width="117">](https://github.com/Keats) |[<img alt="starkat99" src="https://avatars1.githubusercontent.com/u/8295111?v=4&s=117" width="117">](https://github.com/starkat99) |[<img alt="durka" src="https://avatars3.githubusercontent.com/u/47007?v=4&s=117" width="117">](https://github.com/durka) |[<img alt="alex-gulyas" src="https://avatars0.githubusercontent.com/u/8698329?v=4&s=117" width="117">](https://github.com/alex-gulyas) |[<img alt="cite-reader" src="https://avatars1.githubusercontent.com/u/4196987?v=4&s=117" width="117">](https://github.com/cite-reader) |[<img alt="alexbool" src="https://avatars3.githubusercontent.com/u/1283792?v=4&s=117" width="117">](https://github.com/alexbool) |
|
||||
[<img alt="vmchale" src="https://avatars1.githubusercontent.com/u/13259982?v=4&s=117" width="117">](https://github.com/vmchale) |[<img alt="etopiei" src="https://avatars3.githubusercontent.com/u/17671663?v=4&s=117" width="117">](https://github.com/etopiei) |[<img alt="messense" src="https://avatars0.githubusercontent.com/u/1556054?v=4&s=117" width="117">](https://github.com/messense) |[<img alt="Keats" src="https://avatars2.githubusercontent.com/u/680355?v=4&s=117" width="117">](https://github.com/Keats) |[<img alt="kieraneglin" src="https://avatars0.githubusercontent.com/u/569917?v=4&s=117" width="117">](https://github.com/kieraneglin) |[<img alt="durka" src="https://avatars3.githubusercontent.com/u/47007?v=4&s=117" width="117">](https://github.com/durka) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[Keats](https://github.com/Keats) |[starkat99](https://github.com/starkat99) |[durka](https://github.com/durka) |[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) |
|
||||
[vmchale](https://github.com/vmchale) |[etopiei](https://github.com/etopiei) |[messense](https://github.com/messense) |[Keats](https://github.com/Keats) |[kieraneglin](https://github.com/kieraneglin) |[durka](https://github.com/durka) |
|
||||
|
||||
[<img alt="AluisioASG" src="https://avatars2.githubusercontent.com/u/1904165?v=4&s=117" width="117">](https://github.com/AluisioASG) |[<img alt="BurntSushi" src="https://avatars3.githubusercontent.com/u/456674?v=4&s=117" width="117">](https://github.com/BurntSushi) |[<img alt="nox" src="https://avatars0.githubusercontent.com/u/123095?v=4&s=117" width="117">](https://github.com/nox) |[<img alt="pixelistik" src="https://avatars1.githubusercontent.com/u/170929?v=4&s=117" width="117">](https://github.com/pixelistik) |[<img alt="brennie" src="https://avatars3.githubusercontent.com/u/156585?v=4&s=117" width="117">](https://github.com/brennie) |[<img alt="ogham" src="https://avatars3.githubusercontent.com/u/503760?v=4&s=117" width="117">](https://github.com/ogham) |
|
||||
[<img alt="alex-gulyas" src="https://avatars0.githubusercontent.com/u/8698329?v=4&s=117" width="117">](https://github.com/alex-gulyas) |[<img alt="cite-reader" src="https://avatars1.githubusercontent.com/u/4196987?v=4&s=117" width="117">](https://github.com/cite-reader) |[<img alt="alexbool" src="https://avatars3.githubusercontent.com/u/1283792?v=4&s=117" width="117">](https://github.com/alexbool) |[<img alt="AluisioASG" src="https://avatars2.githubusercontent.com/u/1904165?v=4&s=117" width="117">](https://github.com/AluisioASG) |[<img alt="BurntSushi" src="https://avatars3.githubusercontent.com/u/456674?v=4&s=117" width="117">](https://github.com/BurntSushi) |[<img alt="AndrewGaspar" src="https://avatars1.githubusercontent.com/u/2292643?v=4&s=117" width="117">](https://github.com/AndrewGaspar) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[AluisioASG](https://github.com/AluisioASG) |[BurntSushi](https://github.com/BurntSushi) |[nox](https://github.com/nox) |[pixelistik](https://github.com/pixelistik) |[brennie](https://github.com/brennie) |[ogham](https://github.com/ogham) |
|
||||
[alex-gulyas](https://github.com/alex-gulyas) |[cite-reader](https://github.com/cite-reader) |[alexbool](https://github.com/alexbool) |[AluisioASG](https://github.com/AluisioASG) |[BurntSushi](https://github.com/BurntSushi) |[AndrewGaspar](https://github.com/AndrewGaspar) |
|
||||
|
||||
[<img alt="Bilalh" src="https://avatars0.githubusercontent.com/u/171602?v=4&s=117" width="117">](https://github.com/Bilalh) |[<img alt="dotdash" src="https://avatars1.githubusercontent.com/u/230962?v=4&s=117" width="117">](https://github.com/dotdash) |[<img alt="bradurani" src="https://avatars0.githubusercontent.com/u/4195952?v=4&s=117" width="117">](https://github.com/bradurani) |[<img alt="Seeker14491" src="https://avatars2.githubusercontent.com/u/6490497?v=4&s=117" width="117">](https://github.com/Seeker14491) |[<img alt="brianp" src="https://avatars1.githubusercontent.com/u/179134?v=4&s=117" width="117">](https://github.com/brianp) |[<img alt="casey" src="https://avatars2.githubusercontent.com/u/1945?v=4&s=117" width="117">](https://github.com/casey) |
|
||||
[<img alt="nox" src="https://avatars0.githubusercontent.com/u/123095?v=4&s=117" width="117">](https://github.com/nox) |[<img alt="mitsuhiko" src="https://avatars1.githubusercontent.com/u/7396?v=4&s=117" width="117">](https://github.com/mitsuhiko) |[<img alt="pixelistik" src="https://avatars1.githubusercontent.com/u/170929?v=4&s=117" width="117">](https://github.com/pixelistik) |[<img alt="ogham" src="https://avatars3.githubusercontent.com/u/503760?v=4&s=117" width="117">](https://github.com/ogham) |[<img alt="Bilalh" src="https://avatars0.githubusercontent.com/u/171602?v=4&s=117" width="117">](https://github.com/Bilalh) |[<img alt="dotdash" src="https://avatars1.githubusercontent.com/u/230962?v=4&s=117" width="117">](https://github.com/dotdash) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[Bilalh](https://github.com/Bilalh) |[dotdash](https://github.com/dotdash) |[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[casey](https://github.com/casey) |
|
||||
[nox](https://github.com/nox) |[mitsuhiko](https://github.com/mitsuhiko) |[pixelistik](https://github.com/pixelistik) |[ogham](https://github.com/ogham) |[Bilalh](https://github.com/Bilalh) |[dotdash](https://github.com/dotdash) |
|
||||
|
||||
[<img alt="volks73" src="https://avatars1.githubusercontent.com/u/1915469?v=4&s=117" width="117">](https://github.com/volks73) |[<img alt="daboross" src="https://avatars1.githubusercontent.com/u/1152146?v=4&s=117" width="117">](https://github.com/daboross) |[<img alt="mernen" src="https://avatars0.githubusercontent.com/u/6412?v=4&s=117" width="117">](https://github.com/mernen) |[<img alt="dguo" src="https://avatars0.githubusercontent.com/u/2763135?v=4&s=117" width="117">](https://github.com/dguo) |[<img alt="davidszotten" src="https://avatars3.githubusercontent.com/u/412005?v=4&s=117" width="117">](https://github.com/davidszotten) |[<img alt="drusellers" src="https://avatars1.githubusercontent.com/u/63355?v=4&s=117" width="117">](https://github.com/drusellers) |
|
||||
[<img alt="bradurani" src="https://avatars0.githubusercontent.com/u/4195952?v=4&s=117" width="117">](https://github.com/bradurani) |[<img alt="Seeker14491" src="https://avatars2.githubusercontent.com/u/6490497?v=4&s=117" width="117">](https://github.com/Seeker14491) |[<img alt="brianp" src="https://avatars1.githubusercontent.com/u/179134?v=4&s=117" width="117">](https://github.com/brianp) |[<img alt="cldershem" src="https://avatars3.githubusercontent.com/u/201608?v=4&s=117" width="117">](https://github.com/cldershem) |[<img alt="casey" src="https://avatars2.githubusercontent.com/u/1945?v=4&s=117" width="117">](https://github.com/casey) |[<img alt="volks73" src="https://avatars1.githubusercontent.com/u/1915469?v=4&s=117" width="117">](https://github.com/volks73) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[volks73](https://github.com/volks73) |[daboross](https://github.com/daboross) |[mernen](https://github.com/mernen) |[dguo](https://github.com/dguo) |[davidszotten](https://github.com/davidszotten) |[drusellers](https://github.com/drusellers) |
|
||||
[bradurani](https://github.com/bradurani) |[Seeker14491](https://github.com/Seeker14491) |[brianp](https://github.com/brianp) |[cldershem](https://github.com/cldershem) |[casey](https://github.com/casey) |[volks73](https://github.com/volks73) |
|
||||
|
||||
[<img alt="eddyb" src="https://avatars2.githubusercontent.com/u/77424?v=4&s=117" width="117">](https://github.com/eddyb) |[<img alt="Fraser999" src="https://avatars3.githubusercontent.com/u/190532?v=4&s=117" width="117">](https://github.com/Fraser999) |[<img alt="birkenfeld" src="https://avatars0.githubusercontent.com/u/144359?v=4&s=117" width="117">](https://github.com/birkenfeld) |[<img alt="guanqun" src="https://avatars0.githubusercontent.com/u/53862?v=4&s=117" width="117">](https://github.com/guanqun) |[<img alt="tanakh" src="https://avatars2.githubusercontent.com/u/109069?v=4&s=117" width="117">](https://github.com/tanakh) |[<img alt="SirVer" src="https://avatars0.githubusercontent.com/u/140115?v=4&s=117" width="117">](https://github.com/SirVer) |
|
||||
[<img alt="daboross" src="https://avatars1.githubusercontent.com/u/1152146?v=4&s=117" width="117">](https://github.com/daboross) |[<img alt="da-x" src="https://avatars1.githubusercontent.com/u/321273?v=4&s=117" width="117">](https://github.com/da-x) |[<img alt="mernen" src="https://avatars0.githubusercontent.com/u/6412?v=4&s=117" width="117">](https://github.com/mernen) |[<img alt="dguo" src="https://avatars0.githubusercontent.com/u/2763135?v=4&s=117" width="117">](https://github.com/dguo) |[<img alt="davidszotten" src="https://avatars3.githubusercontent.com/u/412005?v=4&s=117" width="117">](https://github.com/davidszotten) |[<img alt="drusellers" src="https://avatars1.githubusercontent.com/u/63355?v=4&s=117" width="117">](https://github.com/drusellers) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[eddyb](https://github.com/eddyb) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) |[SirVer](https://github.com/SirVer) |
|
||||
[daboross](https://github.com/daboross) |[da-x](https://github.com/da-x) |[mernen](https://github.com/mernen) |[dguo](https://github.com/dguo) |[davidszotten](https://github.com/davidszotten) |[drusellers](https://github.com/drusellers) |
|
||||
|
||||
[<img alt="idmit" src="https://avatars1.githubusercontent.com/u/2546728?v=4&s=117" width="117">](https://github.com/idmit) |[<img alt="archer884" src="https://avatars1.githubusercontent.com/u/679494?v=4&s=117" width="117">](https://github.com/archer884) |[<img alt="jacobmischka" src="https://avatars1.githubusercontent.com/u/3939997?v=4&s=117" width="117">](https://github.com/jacobmischka) |[<img alt="jespino" src="https://avatars0.githubusercontent.com/u/290303?v=4&s=117" width="117">](https://github.com/jespino) |[<img alt="jfrankenau" src="https://avatars3.githubusercontent.com/u/2736480?v=4&s=117" width="117">](https://github.com/jfrankenau) |[<img alt="jtdowney" src="https://avatars1.githubusercontent.com/u/44654?v=4&s=117" width="117">](https://github.com/jtdowney) |
|
||||
[<img alt="eddyb" src="https://avatars2.githubusercontent.com/u/77424?v=4&s=117" width="117">](https://github.com/eddyb) |[<img alt="Enet4" src="https://avatars0.githubusercontent.com/u/4738426?v=4&s=117" width="117">](https://github.com/Enet4) |[<img alt="Fraser999" src="https://avatars3.githubusercontent.com/u/190532?v=4&s=117" width="117">](https://github.com/Fraser999) |[<img alt="birkenfeld" src="https://avatars0.githubusercontent.com/u/144359?v=4&s=117" width="117">](https://github.com/birkenfeld) |[<img alt="guanqun" src="https://avatars0.githubusercontent.com/u/53862?v=4&s=117" width="117">](https://github.com/guanqun) |[<img alt="tanakh" src="https://avatars2.githubusercontent.com/u/109069?v=4&s=117" width="117">](https://github.com/tanakh) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) |[jtdowney](https://github.com/jtdowney) |
|
||||
[eddyb](https://github.com/eddyb) |[Enet4](https://github.com/Enet4) |[Fraser999](https://github.com/Fraser999) |[birkenfeld](https://github.com/birkenfeld) |[guanqun](https://github.com/guanqun) |[tanakh](https://github.com/tanakh) |
|
||||
|
||||
[<img alt="andete" src="https://avatars2.githubusercontent.com/u/689017?v=4&s=117" width="117">](https://github.com/andete) |[<img alt="joshtriplett" src="https://avatars2.githubusercontent.com/u/162737?v=4&s=117" width="117">](https://github.com/joshtriplett) |[<img alt="Kalwyn" src="https://avatars3.githubusercontent.com/u/22778640?v=4&s=117" width="117">](https://github.com/Kalwyn) |[<img alt="manuel-rhdt" src="https://avatars1.githubusercontent.com/u/3199013?v=4&s=117" width="117">](https://github.com/manuel-rhdt) |[<img alt="Marwes" src="https://avatars3.githubusercontent.com/u/957312?v=4&s=117" width="117">](https://github.com/Marwes) |[<img alt="mdaffin" src="https://avatars1.githubusercontent.com/u/171232?v=4&s=117" width="117">](https://github.com/mdaffin) |
|
||||
[<img alt="SirVer" src="https://avatars0.githubusercontent.com/u/140115?v=4&s=117" width="117">](https://github.com/SirVer) |[<img alt="idmit" src="https://avatars1.githubusercontent.com/u/2546728?v=4&s=117" width="117">](https://github.com/idmit) |[<img alt="archer884" src="https://avatars1.githubusercontent.com/u/679494?v=4&s=117" width="117">](https://github.com/archer884) |[<img alt="jacobmischka" src="https://avatars1.githubusercontent.com/u/3939997?v=4&s=117" width="117">](https://github.com/jacobmischka) |[<img alt="jespino" src="https://avatars0.githubusercontent.com/u/290303?v=4&s=117" width="117">](https://github.com/jespino) |[<img alt="jfrankenau" src="https://avatars3.githubusercontent.com/u/2736480?v=4&s=117" width="117">](https://github.com/jfrankenau) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) |[mdaffin](https://github.com/mdaffin) |
|
||||
[SirVer](https://github.com/SirVer) |[idmit](https://github.com/idmit) |[archer884](https://github.com/archer884) |[jacobmischka](https://github.com/jacobmischka) |[jespino](https://github.com/jespino) |[jfrankenau](https://github.com/jfrankenau) |
|
||||
|
||||
[<img alt="iliekturtles" src="https://avatars3.githubusercontent.com/u/5081378?v=4&s=117" width="117">](https://github.com/iliekturtles) |[<img alt="nicompte" src="https://avatars2.githubusercontent.com/u/439369?v=4&s=117" width="117">](https://github.com/nicompte) |[<img alt="NickeZ" src="https://avatars2.githubusercontent.com/u/492753?v=4&s=117" width="117">](https://github.com/NickeZ) |[<img alt="nvzqz" src="https://avatars0.githubusercontent.com/u/10367662?v=4&s=117" width="117">](https://github.com/nvzqz) |[<img alt="nuew" src="https://avatars2.githubusercontent.com/u/26099511?v=4&s=117" width="117">](https://github.com/nuew) |[<img alt="Geogi" src="https://avatars1.githubusercontent.com/u/1818316?v=4&s=117" width="117">](https://github.com/Geogi) |
|
||||
[<img alt="jtdowney" src="https://avatars1.githubusercontent.com/u/44654?v=4&s=117" width="117">](https://github.com/jtdowney) |[<img alt="andete" src="https://avatars2.githubusercontent.com/u/689017?v=4&s=117" width="117">](https://github.com/andete) |[<img alt="joshtriplett" src="https://avatars2.githubusercontent.com/u/162737?v=4&s=117" width="117">](https://github.com/joshtriplett) |[<img alt="Kalwyn" src="https://avatars3.githubusercontent.com/u/22778640?v=4&s=117" width="117">](https://github.com/Kalwyn) |[<img alt="manuel-rhdt" src="https://avatars1.githubusercontent.com/u/3199013?v=4&s=117" width="117">](https://github.com/manuel-rhdt) |[<img alt="Marwes" src="https://avatars3.githubusercontent.com/u/957312?v=4&s=117" width="117">](https://github.com/Marwes) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) |[Geogi](https://github.com/Geogi) |
|
||||
[jtdowney](https://github.com/jtdowney) |[andete](https://github.com/andete) |[joshtriplett](https://github.com/joshtriplett) |[Kalwyn](https://github.com/Kalwyn) |[manuel-rhdt](https://github.com/manuel-rhdt) |[Marwes](https://github.com/Marwes) |
|
||||
|
||||
[<img alt="focusaurus" src="https://avatars1.githubusercontent.com/u/482377?v=4&s=117" width="117">](https://github.com/focusaurus) |[<img alt="flying-sheep" src="https://avatars0.githubusercontent.com/u/291575?v=4&s=117" width="117">](https://github.com/flying-sheep) |[<img alt="Phlosioneer" src="https://avatars2.githubusercontent.com/u/4657718?v=4&s=117" width="117">](https://github.com/Phlosioneer) |[<img alt="peppsac" src="https://avatars3.githubusercontent.com/u/2198295?v=4&s=117" width="117">](https://github.com/peppsac) |[<img alt="golddranks" src="https://avatars1.githubusercontent.com/u/2675542?v=4&s=117" width="117">](https://github.com/golddranks) |[<img alt="hexjelly" src="https://avatars0.githubusercontent.com/u/435283?v=4&s=117" width="117">](https://github.com/hexjelly) |
|
||||
[<img alt="mdaffin" src="https://avatars1.githubusercontent.com/u/171232?v=4&s=117" width="117">](https://github.com/mdaffin) |[<img alt="iliekturtles" src="https://avatars3.githubusercontent.com/u/5081378?v=4&s=117" width="117">](https://github.com/iliekturtles) |[<img alt="nicompte" src="https://avatars2.githubusercontent.com/u/439369?v=4&s=117" width="117">](https://github.com/nicompte) |[<img alt="NickeZ" src="https://avatars2.githubusercontent.com/u/492753?v=4&s=117" width="117">](https://github.com/NickeZ) |[<img alt="nvzqz" src="https://avatars0.githubusercontent.com/u/10367662?v=4&s=117" width="117">](https://github.com/nvzqz) |[<img alt="nuew" src="https://avatars2.githubusercontent.com/u/26099511?v=4&s=117" width="117">](https://github.com/nuew) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) |[hexjelly](https://github.com/hexjelly) |
|
||||
[mdaffin](https://github.com/mdaffin) |[iliekturtles](https://github.com/iliekturtles) |[nicompte](https://github.com/nicompte) |[NickeZ](https://github.com/NickeZ) |[nvzqz](https://github.com/nvzqz) |[nuew](https://github.com/nuew) |
|
||||
|
||||
[<img alt="rnelson" src="https://avatars3.githubusercontent.com/u/118361?v=4&s=117" width="117">](https://github.com/rnelson) |[<img alt="swatteau" src="https://avatars3.githubusercontent.com/u/5521255?v=4&s=117" width="117">](https://github.com/swatteau) |[<img alt="tspiteri" src="https://avatars0.githubusercontent.com/u/18604588?v=4&s=117" width="117">](https://github.com/tspiteri) |[<img alt="siiptuo" src="https://avatars0.githubusercontent.com/u/10729330?v=4&s=117" width="117">](https://github.com/siiptuo) |[<img alt="vks" src="https://avatars2.githubusercontent.com/u/33460?v=4&s=117" width="117">](https://github.com/vks) |[<img alt="vsupalov" src="https://avatars2.githubusercontent.com/u/2801030?v=4&s=117" width="117">](https://github.com/vsupalov) |
|
||||
[<img alt="Geogi" src="https://avatars1.githubusercontent.com/u/1818316?v=4&s=117" width="117">](https://github.com/Geogi) |[<img alt="focusaurus" src="https://avatars1.githubusercontent.com/u/482377?v=4&s=117" width="117">](https://github.com/focusaurus) |[<img alt="flying-sheep" src="https://avatars0.githubusercontent.com/u/291575?v=4&s=117" width="117">](https://github.com/flying-sheep) |[<img alt="Phlosioneer" src="https://avatars2.githubusercontent.com/u/4657718?v=4&s=117" width="117">](https://github.com/Phlosioneer) |[<img alt="peppsac" src="https://avatars3.githubusercontent.com/u/2198295?v=4&s=117" width="117">](https://github.com/peppsac) |[<img alt="golddranks" src="https://avatars1.githubusercontent.com/u/2675542?v=4&s=117" width="117">](https://github.com/golddranks) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[rnelson](https://github.com/rnelson) |[swatteau](https://github.com/swatteau) |[tspiteri](https://github.com/tspiteri) |[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) |
|
||||
[Geogi](https://github.com/Geogi) |[focusaurus](https://github.com/focusaurus) |[flying-sheep](https://github.com/flying-sheep) |[Phlosioneer](https://github.com/Phlosioneer) |[peppsac](https://github.com/peppsac) |[golddranks](https://github.com/golddranks) |
|
||||
|
||||
[<img alt="mineo" src="https://avatars1.githubusercontent.com/u/78236?v=4&s=117" width="117">](https://github.com/mineo) |[<img alt="wabain" src="https://avatars3.githubusercontent.com/u/7651435?v=4&s=117" width="117">](https://github.com/wabain) |[<img alt="grossws" src="https://avatars2.githubusercontent.com/u/171284?v=4&s=117" width="117">](https://github.com/grossws) |[<img alt="kennytm" src="https://avatars1.githubusercontent.com/u/103023?v=4&s=117" width="117">](https://github.com/kennytm) |[<img alt="mvaude" src="https://avatars1.githubusercontent.com/u/9532611?v=4&s=117" width="117">](https://github.com/mvaude) |[<img alt="panicbit" src="https://avatars2.githubusercontent.com/u/628445?v=4&s=117" width="117">](https://github.com/panicbit) |
|
||||
[<img alt="hexjelly" src="https://avatars0.githubusercontent.com/u/435283?v=4&s=117" width="117">](https://github.com/hexjelly) |[<img alt="rom1v" src="https://avatars1.githubusercontent.com/u/543275?v=4&s=117" width="117">](https://github.com/rom1v) |[<img alt="rnelson" src="https://avatars3.githubusercontent.com/u/118361?v=4&s=117" width="117">](https://github.com/rnelson) |[<img alt="swatteau" src="https://avatars3.githubusercontent.com/u/5521255?v=4&s=117" width="117">](https://github.com/swatteau) |[<img alt="tchajed" src="https://avatars3.githubusercontent.com/u/1255037?v=4&s=117" width="117">](https://github.com/tchajed) |[<img alt="tspiteri" src="https://avatars0.githubusercontent.com/u/18604588?v=4&s=117" width="117">](https://github.com/tspiteri) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) |[kennytm](https://github.com/kennytm) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) |
|
||||
[hexjelly](https://github.com/hexjelly) |[rom1v](https://github.com/rom1v) |[rnelson](https://github.com/rnelson) |[swatteau](https://github.com/swatteau) |[tchajed](https://github.com/tchajed) |[tspiteri](https://github.com/tspiteri) |
|
||||
|
||||
[<img alt="mitsuhiko" src="https://avatars1.githubusercontent.com/u/7396?v=4&s=117" width="117">](https://github.com/mitsuhiko) |
|
||||
:---: |
|
||||
[mitsuhiko](https://github.com/mitsuhiko) |
|
||||
[<img alt="siiptuo" src="https://avatars0.githubusercontent.com/u/10729330?v=4&s=117" width="117">](https://github.com/siiptuo) |[<img alt="vks" src="https://avatars2.githubusercontent.com/u/33460?v=4&s=117" width="117">](https://github.com/vks) |[<img alt="vsupalov" src="https://avatars2.githubusercontent.com/u/2801030?v=4&s=117" width="117">](https://github.com/vsupalov) |[<img alt="mineo" src="https://avatars1.githubusercontent.com/u/78236?v=4&s=117" width="117">](https://github.com/mineo) |[<img alt="wabain" src="https://avatars3.githubusercontent.com/u/7651435?v=4&s=117" width="117">](https://github.com/wabain) |[<img alt="grossws" src="https://avatars2.githubusercontent.com/u/171284?v=4&s=117" width="117">](https://github.com/grossws) |
|
||||
:---: |:---: |:---: |:---: |:---: |:---: |
|
||||
[siiptuo](https://github.com/siiptuo) |[vks](https://github.com/vks) |[vsupalov](https://github.com/vsupalov) |[mineo](https://github.com/mineo) |[wabain](https://github.com/wabain) |[grossws](https://github.com/grossws) |
|
||||
|
||||
[<img alt="kennytm" src="https://avatars1.githubusercontent.com/u/103023?v=4&s=117" width="117">](https://github.com/kennytm) |[<img alt="king6cong" src="https://avatars3.githubusercontent.com/u/302560?v=4&s=117" width="117">](https://github.com/king6cong) |[<img alt="mvaude" src="https://avatars1.githubusercontent.com/u/9532611?v=4&s=117" width="117">](https://github.com/mvaude) |[<img alt="panicbit" src="https://avatars2.githubusercontent.com/u/628445?v=4&s=117" width="117">](https://github.com/panicbit) |[<img alt="brennie" src="https://avatars3.githubusercontent.com/u/156585?v=4&s=117" width="117">](https://github.com/brennie) |
|
||||
:---: |:---: |:---: |:---: |:---: |
|
||||
[kennytm](https://github.com/kennytm) |[king6cong](https://github.com/king6cong) |[mvaude](https://github.com/mvaude) |[panicbit](https://github.com/panicbit) |[brennie](https://github.com/brennie) |
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
|
||||
[package]
|
||||
name = "clap"
|
||||
version = "2.29.0"
|
||||
version = "2.31.2"
|
||||
authors = ["Kevin K. <kbknapp@gmail.com>"]
|
||||
exclude = ["examples/*", "clap-test/*", "tests/*", "benches/*", "*.png", "clap-perf/*", "*.dot"]
|
||||
description = "A simple to use, efficient, and full featured Command Line Argument Parser\n"
|
||||
homepage = "https://clap.rs/"
|
||||
documentation = "https://docs.rs/clap/"
|
||||
readme = "README.md"
|
||||
keywords = ["argument", "command", "arg", "parser", "parse"]
|
||||
keywords = ["argument", "cli", "arg", "parser", "parse"]
|
||||
categories = ["command-line-interface"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/kbknapp/clap-rs"
|
||||
|
@ -44,6 +44,7 @@ rpath = false
|
|||
[profile.bench]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
debug = false
|
||||
debug-assertions = false
|
||||
rpath = false
|
||||
|
@ -59,13 +60,10 @@ rpath = false
|
|||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
debug = false
|
||||
debug-assertions = false
|
||||
rpath = false
|
||||
[dependencies.ansi_term]
|
||||
version = "0.10.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.atty]
|
||||
version = "0.2.2"
|
||||
optional = true
|
||||
|
@ -78,7 +76,7 @@ version = "~0.0.166"
|
|||
optional = true
|
||||
|
||||
[dependencies.strsim]
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
optional = true
|
||||
|
||||
[dependencies.term_size]
|
||||
|
@ -119,8 +117,24 @@ suggestions = ["strsim"]
|
|||
unstable = []
|
||||
wrap_help = ["term_size", "textwrap/term_size"]
|
||||
yaml = ["yaml-rust"]
|
||||
[target."cfg(not(windows))".dependencies.ansi_term]
|
||||
version = "0.11"
|
||||
optional = true
|
||||
[badges.appveyor]
|
||||
repository = "kbknapp/clap-rs"
|
||||
|
||||
[badges.coveralls]
|
||||
branch = "master"
|
||||
repostiory = "kbknapp/clap-rs"
|
||||
|
||||
[badges.is-it-maintained-issue-resolution]
|
||||
repository = "kbknapp/clap-rs"
|
||||
|
||||
[badges.is-it-maintained-open-issues]
|
||||
repository = "kbknapp/clap-rs"
|
||||
|
||||
[badges.maintenance]
|
||||
status = "actively-developed"
|
||||
|
||||
[badges.travis-ci]
|
||||
repository = "kbknapp/clap-rs"
|
||||
|
|
|
@ -42,25 +42,36 @@ Created by [gh-md-toc](https://github.com/ekalinin/github-markdown-toc)
|
|||
|
||||
## What's New
|
||||
|
||||
Here's whats new in 2.29.0:
|
||||
Here's whats new in 2.31.2:
|
||||
|
||||
* **Arg:** adds Arg::hide_env_values(bool) which allows one to hide any current env values and display only the key in help messages
|
||||
* **Fish Completions:** fixes a bug that only allowed a single completion in in Fish Shell
|
||||
* **AllowExternalSubcommands**: fixes a bug where external subcommands would be blocked by a similarly named subcomand
|
||||
* Fixes some typos in the `README.md`
|
||||
|
||||
Here's whats new in 2.28.0:
|
||||
Here's whats new in 2.31.1:
|
||||
|
||||
The minimum required Rust is now 1.20. This was done to start using bitflags 1.0 and having >1.0 deps is a *very good* thing!
|
||||
* **AllowMissingPositional:** improves the ability of `AppSetting::AllowMissingPositional` to allow "skipping" to the last positional arg with the `--` operator
|
||||
|
||||
* Updates `bitflags` to 1.0
|
||||
* Adds the traits to be used with the `clap-derive` crate to be able to use Custom Derive (for now must be accessed with `unstable` feature flag)
|
||||
* Adds Arg::case_insensitive(bool) which allows matching Arg::possible_values without worrying about ASCII case
|
||||
* Fixes a regression where --help couldn't be overridden
|
||||
* adds '[SUBCOMMAND]' to usage strings with only AppSettings::AllowExternalSubcommands is used with no other subcommands
|
||||
* uses `.bash` for Bash completion scripts now instead of `.bash-completion` due to convention and `.bash-completion` not being supported by completion projects
|
||||
* Fix URL path to github hosted files
|
||||
* fix typos in docs
|
||||
* **README.md:** updates the readme and pulls out some redundant sections
|
||||
* fixes a bug that allowed options to pass parsing when no value was provided
|
||||
* ignore PropagateGlobalValuesDown deprecation warning
|
||||
Here's whats new in 2.31.0:
|
||||
|
||||
* **Arg Indices:** adds the ability to query argument value indices
|
||||
* implements an `Indices<Item=usize>` iterator
|
||||
* adds the documentation for the arg index querying methods
|
||||
* **Improves PowerShell completions** - Uses the short help tool-tip for PowerShell completion scripts
|
||||
* Adds WASM support (clap now compiles on WASM!)
|
||||
* **Raw Args** adds a convenience function to `Arg` that allows implying all of `Arg::last` `Arg::allow_hyphen_values` and `Arg::multiple(true)`
|
||||
* **CONTRIBUTING.md:** fix url to clippy upstream repo
|
||||
* **Values Documentation:** improves the docs example of the Values iterator
|
||||
* Updates README.md to hint that the `wrap_help` feature is a thing
|
||||
* Use `codegen-units = 1` in release and bench profiles to improve bench performance
|
||||
* Fix some typos and markdown issues in the docs
|
||||
|
||||
Here's whats new in 2.30.x:
|
||||
|
||||
* **Bash Completions:** instead of completing a generic option name, all bash completions fall back to file completions UNLESS `Arg::possible_values` was used
|
||||
* **YAML:** Adds a missing conversion from `Arg::last` when instantiating from a YAML file
|
||||
* **Deps:** No longer needlessly compiles `ansi_term` on Windows since its not used
|
||||
* **Help Message:** changes the `[values: foo bar baz]` array to `[possible values: foo bar baz]` for consistency with the API
|
||||
|
||||
For full details, see [CHANGELOG.md](https://github.com/kbknapp/clap-rs/blob/master/CHANGELOG.md)
|
||||
|
||||
|
@ -163,7 +174,7 @@ The following examples show a quick example of some of the very basic functional
|
|||
|
||||
**NOTE:** All of these examples are functionally the same, but show different styles in which to use `clap`. These different styles are purely a matter of personal preference.
|
||||
|
||||
The first example shows a method using the 'Builder Pattern' which allows more advanced configuration options (not shown in this small example), or even dynamically generating arguments when desired. The downside is it's more verbose.
|
||||
The first example shows a method using the 'Builder Pattern' which allows more advanced configuration options (not shown in this small example), or even dynamically generating arguments when desired.
|
||||
|
||||
```rust
|
||||
// (Full example with detailed comments in examples/01b_quick_example.rs)
|
||||
|
@ -233,37 +244,7 @@ fn main() {
|
|||
}
|
||||
```
|
||||
|
||||
The next example shows a far less verbose method, but sacrifices some of the advanced configuration options (not shown in this small example). This method also takes a *very* minor runtime penalty.
|
||||
|
||||
```rust
|
||||
// (Full example with detailed comments in examples/01a_quick_example.rs)
|
||||
//
|
||||
// This example demonstrates clap's "usage strings" method of creating arguments
|
||||
// which is less verbose
|
||||
extern crate clap;
|
||||
use clap::{Arg, App, SubCommand};
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("myapp")
|
||||
.version("1.0")
|
||||
.author("Kevin K. <kbknapp@gmail.com>")
|
||||
.about("Does awesome things")
|
||||
.args_from_usage(
|
||||
"-c, --config=[FILE] 'Sets a custom config file'
|
||||
<INPUT> 'Sets the input file to use'
|
||||
-v... 'Sets the level of verbosity'")
|
||||
.subcommand(SubCommand::with_name("test")
|
||||
.about("controls testing features")
|
||||
.version("1.3")
|
||||
.author("Someone E. <someone_else@other.com>")
|
||||
.arg_from_usage("-d, --debug 'Print debug information'"))
|
||||
.get_matches();
|
||||
|
||||
// Same as previous example...
|
||||
}
|
||||
```
|
||||
|
||||
This third method shows how you can use a YAML file to build your CLI and keep your Rust source tidy
|
||||
One could also optionally decleare their CLI in YAML format and keep your Rust source tidy
|
||||
or support multiple localized translations by having different YAML files for each localization.
|
||||
|
||||
First, create the `cli.yml` file to hold your CLI options, but it could be called anything we like:
|
||||
|
@ -301,7 +282,7 @@ subcommands:
|
|||
|
||||
Since this feature requires additional dependencies that not everyone may want, it is *not* compiled in by default and we need to enable a feature flag in Cargo.toml:
|
||||
|
||||
Simply change your `clap = "2.29"` to `clap = {version = "2.87", features = ["yaml"]}`.
|
||||
Simply change your `clap = "2.31"` to `clap = {version = "2.31", features = ["yaml"]}`.
|
||||
|
||||
Finally we create our `main.rs` file just like we would have with the previous two examples:
|
||||
|
||||
|
@ -323,32 +304,6 @@ fn main() {
|
|||
}
|
||||
```
|
||||
|
||||
Last but not least there is a macro version, which is like a hybrid approach offering the runtime speed of the builder pattern (the first example), but without all the verbosity.
|
||||
|
||||
```rust
|
||||
#[macro_use]
|
||||
extern crate clap;
|
||||
|
||||
fn main() {
|
||||
let matches = clap_app!(myapp =>
|
||||
(version: "1.0")
|
||||
(author: "Kevin K. <kbknapp@gmail.com>")
|
||||
(about: "Does awesome things")
|
||||
(@arg CONFIG: -c --config +takes_value "Sets a custom config file")
|
||||
(@arg INPUT: +required "Sets the input file to use")
|
||||
(@arg debug: -d ... "Sets the level of debugging information")
|
||||
(@subcommand test =>
|
||||
(about: "controls testing features")
|
||||
(version: "1.3")
|
||||
(author: "Someone E. <someone_else@other.com>")
|
||||
(@arg verbose: -v --verbose "Print test information verbosely")
|
||||
)
|
||||
).get_matches();
|
||||
|
||||
// Same as before...
|
||||
}
|
||||
```
|
||||
|
||||
If you were to compile any of the above programs and run them with the flag `--help` or `-h` (or `help` subcommand, since we defined `test` as a subcommand) the following would be output
|
||||
|
||||
```sh
|
||||
|
@ -378,6 +333,13 @@ SUBCOMMANDS:
|
|||
|
||||
**NOTE:** You could also run `myapp test --help` or `myapp help test` to see the help message for the `test` subcommand.
|
||||
|
||||
There are also two other methods to create CLIs. Which style you choose is largely a matter of personal preference. The two other methods are:
|
||||
|
||||
* Using [usage strings (examples/01a_quick_example.rs)](examples/01a_quick_example.rs) similar to (but not exact) docopt style usage statements. This is far less verbose than the above methods, but incurs a slight runtime penalty.
|
||||
* Using [a macro (examples/01c_quick_example.rs)](examples/01c_quick_example.rs) which is like a hybrid of the builder and usage string style. It's less verbose, but doesn't incur the runtime penalty of the usage string style. The downside is that it's harder to debug, and more opaque.
|
||||
|
||||
Examples of each method can be found in the [examples/](examples) directory of this repository.
|
||||
|
||||
## Try it!
|
||||
|
||||
### Pre-Built Test
|
||||
|
@ -421,7 +383,7 @@ For full usage, add `clap` as a dependency in your `Cargo.toml` () to use from c
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
clap = "~2.29"
|
||||
clap = "~2.31"
|
||||
```
|
||||
|
||||
(**note**: If you are concerned with supporting a minimum version of Rust that is *older* than the current stable Rust minus 2 stable releases, it's recommended to use the `~major.minor.patch` style versions in your `Cargo.toml` which will only update the patch version automatically. For more information see the [Compatibility Policy](#compatibility-policy))
|
||||
|
@ -437,14 +399,14 @@ Then run `cargo build` or `cargo update && cargo build` for your project.
|
|||
#### Features enabled by default
|
||||
|
||||
* **"suggestions"**: Turns on the `Did you mean '--myoption'?` feature for when users make typos. (builds dependency `strsim`)
|
||||
* **"color"**: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term`)
|
||||
* **"color"**: Turns on colored error messages. This feature only works on non-Windows OSs. (builds dependency `ansi-term` only on non-Windows targets)
|
||||
* **"vec_map"**: Use [`VecMap`](https://crates.io/crates/vec_map) internally instead of a [`BTreeMap`](https://doc.rust-lang.org/stable/std/collections/struct.BTreeMap.html). This feature provides a _slight_ performance improvement. (builds dependency `vec_map`)
|
||||
|
||||
To disable these, add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies.clap]
|
||||
version = "2.29"
|
||||
version = "2.31"
|
||||
default-features = false
|
||||
```
|
||||
|
||||
|
@ -452,7 +414,7 @@ You can also selectively enable only the features you'd like to include, by addi
|
|||
|
||||
```toml
|
||||
[dependencies.clap]
|
||||
version = "2.29"
|
||||
version = "2.31"
|
||||
default-features = false
|
||||
|
||||
# Cherry-pick the features you'd like to use
|
||||
|
@ -463,6 +425,7 @@ features = [ "suggestions", "color" ]
|
|||
|
||||
* **"yaml"**: Enables building CLIs from YAML documents. (builds dependency `yaml-rust`)
|
||||
* **"unstable"**: Enables unstable `clap` features that may change from release to release
|
||||
* **"wrap_help"**: Turns on the help text wrapping feature, based on the terminal size. (builds dependency `term-size`)
|
||||
|
||||
### Dependencies Tree
|
||||
|
||||
|
@ -500,7 +463,7 @@ In order to keep from being surprised of breaking changes, it is **highly** reco
|
|||
|
||||
```toml
|
||||
[dependencies]
|
||||
clap = "~2.29"
|
||||
clap = "~2.31"
|
||||
```
|
||||
|
||||
This will cause *only* the patch version to be updated upon a `cargo update` call, and therefore cannot break due to new features, or bumped minimum versions of Rust.
|
||||
|
@ -517,11 +480,11 @@ Right now Cargo's version resolution is pretty naive, it's just a brute-force se
|
|||
|
||||
# In one Cargo.toml
|
||||
[dependencies]
|
||||
clap = "~2.29.0"
|
||||
clap = "~2.31.2"
|
||||
|
||||
# In another Cargo.toml
|
||||
[dependencies]
|
||||
clap = "2.29"
|
||||
clap = "2.31"
|
||||
```
|
||||
|
||||
This is inherently an unresolvable crate graph in Cargo right now. Cargo requires there's only one major version of a crate, and being in the same workspace these two crates must share a version. This is impossible in this location, though, as these version constraints cannot be met.
|
||||
|
@ -542,6 +505,11 @@ Upon bumping the minimum version of Rust (assuming it's within the stable-2 rang
|
|||
* The breaking change is to be fixing a bug (i.e. relying on a bug as a feature)
|
||||
* The breaking change is a feature isn't used in the wild, or all users of said feature have given approval *prior* to the change
|
||||
|
||||
#### Compatibility with Wasm
|
||||
|
||||
A best effort is made to ensure that `clap` will work on projects targeting `wasm32-unknown-unknown`. However there is no dedicated CI build
|
||||
covering this specific target.
|
||||
|
||||
## License
|
||||
|
||||
`clap` is licensed under the MIT license. Please read the [LICENSE-MIT](LICENSE-MIT) file in this repository for more information.
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
The following is a list of [sponsors](https://www.clap.rs/sponsor/) for the clap-rs project:
|
||||
The following is a list of [sponsors](https://clap.rs/sponsorship/) for the clap-rs project:
|
||||
|
||||
[<img alt="Noelia Seva-Gonzalez" src="https://www.clap.rs/sponsor/img/noelia_sm.png" width="117">](http://vsgrealestategroup.com.realproserver.com/About)
|
||||
[<img alt="Noelia Seva-Gonzalez" src="https://clap.rs/wp-content/uploads/2017/10/noelia_sm-1.png" width="117">](https://noeliasg.com/about/)
|
||||
Noelia Seva-Gonzalez
|
||||
|
||||
[<img alt="Rob Tsuk" src="https://www.clap.rs/sponsor/img/robtsuk_sm.png" width="117">](https://github.com/rtsuk)
|
||||
Rob Tsuk
|
||||
[<img alt="Rob Tsuk" src="https://clap.rs/wp-content/uploads/2017/10/robtsuk_sm.png" width="117">](https://github.com/rtsuk)
|
||||
Rob Tsuk
|
||||
|
||||
[<img alt="messense" src="https://clap.rs/wp-content/uploads/2018/01/messense-400x400.png" width="117">](https://github.com/messense)
|
||||
Messense
|
||||
|
|
|
@ -39,6 +39,16 @@ mod test {
|
|||
assert_eq!(stderr, err.use_stderr());
|
||||
compare(left, right)
|
||||
}
|
||||
pub fn compare_output2(l: App, args: &str, right1: &str, right2: &str, stderr: bool) -> bool {
|
||||
let mut buf = Cursor::new(Vec::with_capacity(50));
|
||||
let res = l.get_matches_from_safe(args.split(' ').collect::<Vec<_>>());
|
||||
let err = res.unwrap_err();
|
||||
err.write_to(&mut buf).unwrap();
|
||||
let content = buf.into_inner();
|
||||
let left = String::from_utf8(content).unwrap();
|
||||
assert_eq!(stderr, err.use_stderr());
|
||||
compare(&*left, right1) || compare(&*left, right2)
|
||||
}
|
||||
|
||||
// Legacy tests from the pyhton script days
|
||||
|
||||
|
|
|
@ -550,14 +550,14 @@ impl<'a> Help<'a> {
|
|||
debugln!("Help::spec_vals: Found possible vals...{:?}", pv);
|
||||
spec_vals.push(if self.color {
|
||||
format!(
|
||||
" [values: {}]",
|
||||
" [possible values: {}]",
|
||||
pv.iter()
|
||||
.map(|v| format!("{}", self.cizer.good(v)))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
} else {
|
||||
format!(" [values: {}]", pv.join(", "))
|
||||
format!(" [possible values: {}]", pv.join(", "))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -841,7 +841,7 @@ fn copy_and_capture<R: Read, W: Write>(
|
|||
|
||||
// The end of the reader was found without finding the closing tag.
|
||||
// Write the opening byte and captured text to the writer.
|
||||
// Return 0 indicating that nothing was caputred but the reader still contains data.
|
||||
// Return 0 indicating that nothing was captured but the reader still contains data.
|
||||
DelimiterNotFound(not_tag_length) => match w.write(b"{") {
|
||||
Err(e) => Some(Err(e)),
|
||||
_ => match w.write(&tag_buffer.get_ref()[0..not_tag_length]) {
|
||||
|
@ -884,7 +884,7 @@ impl<'a> Help<'a> {
|
|||
let mut tmplr = Cursor::new(&template);
|
||||
let mut tag_buf = Cursor::new(vec![0u8; 15]);
|
||||
|
||||
// The strategy is to copy the template from the the reader to wrapped stream
|
||||
// The strategy is to copy the template from the reader to wrapped stream
|
||||
// until a tag is found. Depending on its value, the appropriate content is copied
|
||||
// to the wrapped stream.
|
||||
// The copy from template is then resumed, repeating this sequence until reading
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
macro_rules! remove_overriden {
|
||||
(@remove_requires $rem_from:expr, $a:ident.$ov:ident) => {
|
||||
if let Some(ora) = $a.$ov() {
|
||||
for i in (0 .. $rem_from.len()).rev() {
|
||||
let should_remove = ora.iter().any(|&(_, ref name)| name == &$rem_from[i]);
|
||||
if should_remove { $rem_from.swap_remove(i); }
|
||||
}
|
||||
}
|
||||
};
|
||||
(@remove $rem_from:expr, $a:ident.$ov:ident) => {
|
||||
if let Some(ora) = $a.$ov() {
|
||||
vec_remove_all!($rem_from, ora.iter());
|
||||
}
|
||||
};
|
||||
(@arg $_self:ident, $arg:ident) => {
|
||||
remove_overriden!(@remove_requires $_self.required, $arg.requires);
|
||||
remove_overriden!(@remove $_self.blacklist, $arg.blacklist);
|
||||
remove_overriden!(@remove $_self.overrides, $arg.overrides);
|
||||
};
|
||||
($_self:ident, $name:expr) => {
|
||||
debugln!("remove_overriden!;");
|
||||
if let Some(o) = $_self.opts.iter() .find(|o| o.b.name == *$name) {
|
||||
remove_overriden!(@arg $_self, o);
|
||||
} else if let Some(f) = $_self.flags.iter() .find(|f| f.b.name == *$name) {
|
||||
remove_overriden!(@arg $_self, f);
|
||||
} else {
|
||||
let p = $_self.positionals.values()
|
||||
.find(|p| p.b.name == *$name)
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
remove_overriden!(@arg $_self, p);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! arg_post_processing {
|
||||
($me:ident, $arg:ident, $matcher:ident) => {
|
||||
debugln!("arg_post_processing!;");
|
||||
// Handle POSIX overrides
|
||||
debug!("arg_post_processing!: Is '{}' in overrides...", $arg.to_string());
|
||||
if $me.overrides.contains(&$arg.name()) {
|
||||
if let Some(ref name) = find_name_from!($me, &$arg.name(), overrides, $matcher) {
|
||||
sdebugln!("Yes by {}", name);
|
||||
$matcher.remove(name);
|
||||
remove_overriden!($me, name);
|
||||
}
|
||||
} else { sdebugln!("No"); }
|
||||
|
||||
// Add overrides
|
||||
debug!("arg_post_processing!: Does '{}' have overrides...", $arg.to_string());
|
||||
if let Some(or) = $arg.overrides() {
|
||||
sdebugln!("Yes");
|
||||
$matcher.remove_all(or);
|
||||
for pa in or { remove_overriden!($me, pa); }
|
||||
$me.overrides.extend(or);
|
||||
vec_remove_all!($me.required, or.iter());
|
||||
} else { sdebugln!("No"); }
|
||||
|
||||
// Handle conflicts
|
||||
debug!("arg_post_processing!: Does '{}' have conflicts...", $arg.to_string());
|
||||
if let Some(bl) = $arg.blacklist() {
|
||||
sdebugln!("Yes");
|
||||
|
||||
for c in bl {
|
||||
// Inject two-way conflicts
|
||||
debug!("arg_post_processing!: Has '{}' already been matched...", c);
|
||||
if $matcher.contains(c) {
|
||||
sdebugln!("Yes");
|
||||
// find who blacklisted us...
|
||||
$me.blacklist.push(&$arg.b.name);
|
||||
} else {
|
||||
sdebugln!("No");
|
||||
}
|
||||
}
|
||||
|
||||
$me.blacklist.extend_from_slice(bl);
|
||||
vec_remove_all!($me.overrides, bl.iter());
|
||||
// vec_remove_all!($me.required, bl.iter());
|
||||
} else { sdebugln!("No"); }
|
||||
|
||||
// Add all required args which aren't already found in matcher to the master
|
||||
// list
|
||||
debug!("arg_post_processing!: Does '{}' have requirements...", $arg.to_string());
|
||||
if let Some(reqs) = $arg.requires() {
|
||||
for n in reqs.iter()
|
||||
.filter(|&&(val, _)| val.is_none())
|
||||
.filter(|&&(_, req)| !$matcher.contains(&req))
|
||||
.map(|&(_, name)| name) {
|
||||
|
||||
$me.required.push(n);
|
||||
}
|
||||
} else { sdebugln!("No"); }
|
||||
|
||||
_handle_group_reqs!($me, $arg);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! _handle_group_reqs{
|
||||
($me:ident, $arg:ident) => ({
|
||||
use args::AnyArg;
|
||||
debugln!("_handle_group_reqs!;");
|
||||
for grp in &$me.groups {
|
||||
let found = if grp.args.contains(&$arg.name()) {
|
||||
if let Some(ref reqs) = grp.requires {
|
||||
debugln!("_handle_group_reqs!: Adding {:?} to the required list", reqs);
|
||||
$me.required.extend(reqs);
|
||||
}
|
||||
if let Some(ref bl) = grp.conflicts {
|
||||
$me.blacklist.extend(bl);
|
||||
}
|
||||
true // What if arg is in more than one group with different reqs?
|
||||
} else {
|
||||
false
|
||||
};
|
||||
debugln!("_handle_group_reqs!:iter: grp={}, found={:?}", grp.name, found);
|
||||
if found {
|
||||
for i in (0 .. $me.required.len()).rev() {
|
||||
let should_remove = grp.args.contains(&$me.required[i]);
|
||||
if should_remove { $me.required.swap_remove(i); }
|
||||
}
|
||||
debugln!("_handle_group_reqs!:iter: Adding args from group to blacklist...{:?}", grp.args);
|
||||
if !grp.multiple {
|
||||
$me.blacklist.extend(&grp.args);
|
||||
debugln!("_handle_group_reqs!: removing {:?} from blacklist", $arg.name());
|
||||
for i in (0 .. $me.blacklist.len()).rev() {
|
||||
let should_remove = $me.blacklist[i] == $arg.name();
|
||||
if should_remove { $me.blacklist.swap_remove(i); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! parse_positional {
|
||||
(
|
||||
$_self:ident,
|
||||
$p:ident,
|
||||
$arg_os:ident,
|
||||
$pos_counter:ident,
|
||||
$matcher:ident
|
||||
) => {
|
||||
debugln!("parse_positional!;");
|
||||
|
||||
if !$_self.is_set(AS::TrailingValues) &&
|
||||
($_self.is_set(AS::TrailingVarArg) &&
|
||||
$pos_counter == $_self.positionals.len()) {
|
||||
$_self.settings.set(AS::TrailingValues);
|
||||
}
|
||||
let _ = $_self.add_val_to_arg($p, &$arg_os, $matcher)?;
|
||||
|
||||
$matcher.inc_occurrence_of($p.b.name);
|
||||
let _ = $_self.groups_for_arg($p.b.name)
|
||||
.and_then(|vec| Some($matcher.inc_occurrences_of(&*vec)));
|
||||
if $_self.cache.map_or(true, |name| name != $p.b.name) {
|
||||
arg_post_processing!($_self, $p, $matcher);
|
||||
$_self.cache = Some($p.b.name);
|
||||
}
|
||||
|
||||
$_self.settings.set(AS::ValidArgFound);
|
||||
// Only increment the positional counter if it doesn't allow multiples
|
||||
if !$p.b.settings.is_set(ArgSettings::Multiple) {
|
||||
$pos_counter += 1;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
mod settings;
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
pub mod parser;
|
||||
mod meta;
|
||||
mod help;
|
||||
|
@ -787,7 +785,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Adds multiple [arguments] to the list of valid possibilties
|
||||
/// Adds multiple [arguments] to the list of valid possibilities
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -978,7 +976,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
/// Meaning any of the arguments that belong to that group will cause a failure if present with
|
||||
/// the conflicting argument.
|
||||
///
|
||||
/// Another added benfit of [`ArgGroup`]s is that you can extract a value from a group instead
|
||||
/// Another added benefit of [`ArgGroup`]s is that you can extract a value from a group instead
|
||||
/// of determining exactly which argument was used.
|
||||
///
|
||||
/// Finally, using [`ArgGroup`]s to ensure exclusion between arguments is another very common
|
||||
|
@ -1158,7 +1156,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
/// [`-h` (short)]: ./struct.Arg.html#method.help
|
||||
/// [`--help` (long)]: ./struct.Arg.html#method.long_help
|
||||
pub fn print_help(&mut self) -> ClapResult<()> {
|
||||
// If there are global arguments, or settings we need to propgate them down to subcommands
|
||||
// If there are global arguments, or settings we need to propagate them down to subcommands
|
||||
// before parsing incase we run into a subcommand
|
||||
self.p.propagate_globals();
|
||||
self.p.propagate_settings();
|
||||
|
@ -1188,7 +1186,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
/// [`-h` (short)]: ./struct.Arg.html#method.help
|
||||
/// [`--help` (long)]: ./struct.Arg.html#method.long_help
|
||||
pub fn print_long_help(&mut self) -> ClapResult<()> {
|
||||
// If there are global arguments, or settings we need to propgate them down to subcommands
|
||||
// If there are global arguments, or settings we need to propagate them down to subcommands
|
||||
// before parsing incase we run into a subcommand
|
||||
self.p.propagate_globals();
|
||||
self.p.propagate_settings();
|
||||
|
@ -1208,7 +1206,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
///
|
||||
/// **NOTE:** There is a known bug where this method does not write propagated global arguments
|
||||
/// or autogenerated arguments (i.e. the default help/version args). Prefer
|
||||
/// [`App::write_long_help`] instead if possibe!
|
||||
/// [`App::write_long_help`] instead if possible!
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -1225,7 +1223,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
pub fn write_help<W: Write>(&self, w: &mut W) -> ClapResult<()> {
|
||||
// PENDING ISSUE: 808
|
||||
// https://github.com/kbknapp/clap-rs/issues/808
|
||||
// If there are global arguments, or settings we need to propgate them down to subcommands
|
||||
// If there are global arguments, or settings we need to propagate them down to subcommands
|
||||
// before parsing incase we run into a subcommand
|
||||
// self.p.propagate_globals();
|
||||
// self.p.propagate_settings();
|
||||
|
@ -1306,7 +1304,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
|
||||
/// Generate a completions file for a specified shell at compile time.
|
||||
///
|
||||
/// **NOTE:** to generate the this file at compile time you must use a `build.rs` "Build Script"
|
||||
/// **NOTE:** to generate the file at compile time you must use a `build.rs` "Build Script"
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -1598,7 +1596,7 @@ impl<'a, 'b> App<'a, 'b> {
|
|||
I: IntoIterator<Item = T>,
|
||||
T: Into<OsString> + Clone,
|
||||
{
|
||||
// If there are global arguments, or settings we need to propgate them down to subcommands
|
||||
// If there are global arguments, or settings we need to propagate them down to subcommands
|
||||
// before parsing incase we run into a subcommand
|
||||
if !self.p.is_set(AppSettings::Propagated) {
|
||||
self.p.propagate_globals();
|
||||
|
|
|
@ -3,11 +3,12 @@ use std::ffi::{OsStr, OsString};
|
|||
use std::fmt::Display;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufWriter, Write};
|
||||
#[cfg(feature = "debug")]
|
||||
#[cfg(all(feature = "debug", not(target_arch = "wasm32")))]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::PathBuf;
|
||||
use std::slice::Iter;
|
||||
use std::iter::Peekable;
|
||||
use std::cell::Cell;
|
||||
|
||||
// Internal
|
||||
use INTERNAL_ERROR_MSG;
|
||||
|
@ -61,13 +62,13 @@ where
|
|||
pub global_args: Vec<Arg<'a, 'b>>,
|
||||
pub required: Vec<&'a str>,
|
||||
pub r_ifs: Vec<(&'a str, &'b str, &'a str)>,
|
||||
pub blacklist: Vec<&'b str>,
|
||||
pub overrides: Vec<&'b str>,
|
||||
pub overrides: Vec<(&'b str, &'a str)>,
|
||||
help_short: Option<char>,
|
||||
version_short: Option<char>,
|
||||
cache: Option<&'a str>,
|
||||
pub help_message: Option<&'a str>,
|
||||
pub version_message: Option<&'a str>,
|
||||
cur_idx: Cell<usize>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Parser<'a, 'b>
|
||||
|
@ -78,6 +79,7 @@ where
|
|||
Parser {
|
||||
meta: AppMeta::with_name(n),
|
||||
g_settings: AppFlags::zeroed(),
|
||||
cur_idx: Cell::new(0),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -155,12 +157,10 @@ where
|
|||
g.unwrap()
|
||||
.args
|
||||
.iter()
|
||||
.find(|arg| {
|
||||
!(self.flags.iter().any(|f| &&f.b.name == arg)
|
||||
|| self.opts.iter().any(|o| &&o.b.name == arg)
|
||||
|| self.positionals.values().any(|p| &&p.b.name == arg)
|
||||
|| self.groups.iter().any(|g| &&g.name == arg))
|
||||
})
|
||||
.find(|arg| !(self.flags.iter().any(|f| &&f.b.name == arg)
|
||||
|| self.opts.iter().any(|o| &&o.b.name == arg)
|
||||
|| self.positionals.values().any(|p| &&p.b.name == arg)
|
||||
|| self.groups.iter().any(|g| &&g.name == arg)))
|
||||
.unwrap()
|
||||
);
|
||||
true
|
||||
|
@ -252,6 +252,7 @@ where
|
|||
fn add_reqs(&mut self, a: &Arg<'a, 'b>) {
|
||||
if a.is_set(ArgSettings::Required) {
|
||||
// If the arg is required, add all it's requirements to master required list
|
||||
self.required.push(a.b.name);
|
||||
if let Some(ref areqs) = a.b.requires {
|
||||
for name in areqs
|
||||
.iter()
|
||||
|
@ -261,7 +262,6 @@ where
|
|||
self.required.push(name);
|
||||
}
|
||||
}
|
||||
self.required.push(a.b.name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -346,9 +346,9 @@ where
|
|||
if let Some(ref reqs) = group.requires {
|
||||
self.required.extend_from_slice(reqs);
|
||||
}
|
||||
if let Some(ref bl) = group.conflicts {
|
||||
self.blacklist.extend_from_slice(bl);
|
||||
}
|
||||
// if let Some(ref bl) = group.conflicts {
|
||||
// self.blacklist.extend_from_slice(bl);
|
||||
// }
|
||||
}
|
||||
if self.groups.iter().any(|g| g.name == group.name) {
|
||||
let grp = self.groups
|
||||
|
@ -555,9 +555,7 @@ where
|
|||
|
||||
let count = self.positionals
|
||||
.values()
|
||||
.filter(|p| {
|
||||
p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none()
|
||||
})
|
||||
.filter(|p| p.b.settings.is_set(ArgSettings::Multiple) && p.v.num_vals.is_none())
|
||||
.count();
|
||||
let ok = count <= 1
|
||||
|| (last.is_set(ArgSettings::Last) && last.is_set(ArgSettings::Multiple)
|
||||
|
@ -570,7 +568,6 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
if self.is_set(AS::AllowMissingPositional) {
|
||||
// Check that if a required positional argument is found, all positions with a lower
|
||||
// index are also required.
|
||||
|
@ -628,9 +625,10 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
if self.positionals.values().any(|p| {
|
||||
p.b.is_set(ArgSettings::Last) && p.b.is_set(ArgSettings::Required)
|
||||
}) && self.has_subcommands() && !self.is_set(AS::SubcommandsNegateReqs)
|
||||
if self.positionals
|
||||
.values()
|
||||
.any(|p| p.b.is_set(ArgSettings::Last) && p.b.is_set(ArgSettings::Required))
|
||||
&& self.has_subcommands() && !self.is_set(AS::SubcommandsNegateReqs)
|
||||
{
|
||||
panic!(
|
||||
"Having a required positional argument with .last(true) set *and* child \
|
||||
|
@ -658,9 +656,9 @@ where
|
|||
fn possible_subcommand(&self, arg_os: &OsStr) -> (bool, Option<&str>) {
|
||||
debugln!("Parser::possible_subcommand: arg={:?}", arg_os);
|
||||
fn starts(h: &str, n: &OsStr) -> bool {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(not(any(target_os = "windows", target_arch = "wasm32")))]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
|
||||
use osstringext::OsStrExt3;
|
||||
|
||||
let n_bytes = n.as_bytes();
|
||||
|
@ -773,12 +771,8 @@ where
|
|||
|
||||
// allow wrong self convention due to self.valid_neg_num = true and it's a private method
|
||||
#[cfg_attr(feature = "lints", allow(wrong_self_convention))]
|
||||
fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult<'a>) -> bool {
|
||||
debugln!(
|
||||
"Parser::is_new_arg: arg={:?}, Needs Val of={:?}",
|
||||
arg_os,
|
||||
needs_val_of
|
||||
);
|
||||
fn is_new_arg(&mut self, arg_os: &OsStr, needs_val_of: ParseResult) -> bool {
|
||||
debugln!("Parser::is_new_arg:{:?}:{:?}", arg_os, needs_val_of);
|
||||
let app_wide_settings = if self.is_set(AS::AllowLeadingHyphen) {
|
||||
true
|
||||
} else if self.is_set(AS::AllowNegativeNumbers) {
|
||||
|
@ -807,12 +801,10 @@ where
|
|||
.expect(INTERNAL_ERROR_MSG);
|
||||
(p.is_set(ArgSettings::AllowLeadingHyphen) || app_wide_settings)
|
||||
}
|
||||
ParseResult::ValuesDone => return true,
|
||||
_ => false,
|
||||
};
|
||||
debugln!(
|
||||
"Parser::is_new_arg: Arg::allow_leading_hyphen({:?})",
|
||||
arg_allows_tac
|
||||
);
|
||||
debugln!("Parser::is_new_arg: arg_allows_tac={:?}", arg_allows_tac);
|
||||
|
||||
// Is this a new argument, or values from a previous option?
|
||||
let mut ret = if arg_os.starts_with(b"--") {
|
||||
|
@ -882,7 +874,9 @@ where
|
|||
self.unset(AS::ValidNegNumFound);
|
||||
// Is this a new argument, or values from a previous option?
|
||||
let starts_new_arg = self.is_new_arg(&arg_os, needs_val_of);
|
||||
if arg_os.starts_with(b"--") && arg_os.len_() == 2 && starts_new_arg {
|
||||
if !self.is_set(AS::TrailingValues) && arg_os.starts_with(b"--") && arg_os.len_() == 2
|
||||
&& starts_new_arg
|
||||
{
|
||||
debugln!("Parser::get_matches_with: setting TrailingVals=true");
|
||||
self.set(AS::TrailingValues);
|
||||
continue;
|
||||
|
@ -913,7 +907,60 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
if !starts_new_arg {
|
||||
if starts_new_arg {
|
||||
let check_all = self.is_set(AS::AllArgsOverrideSelf);
|
||||
{
|
||||
let any_arg = find_any_by_name!(self, self.cache.unwrap_or(""));
|
||||
matcher.process_arg_overrides(
|
||||
any_arg,
|
||||
&mut self.overrides,
|
||||
&mut self.required,
|
||||
check_all,
|
||||
);
|
||||
}
|
||||
|
||||
if arg_os.starts_with(b"--") {
|
||||
needs_val_of = self.parse_long_arg(matcher, &arg_os)?;
|
||||
debugln!(
|
||||
"Parser:get_matches_with: After parse_long_arg {:?}",
|
||||
needs_val_of
|
||||
);
|
||||
match needs_val_of {
|
||||
ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => {
|
||||
continue
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
|
||||
// Try to parse short args like normal, if AllowLeadingHyphen or
|
||||
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
|
||||
// an error, and instead return Ok(None)
|
||||
needs_val_of = self.parse_short_arg(matcher, &arg_os)?;
|
||||
// If it's None, we then check if one of those two AppSettings was set
|
||||
debugln!(
|
||||
"Parser:get_matches_with: After parse_short_arg {:?}",
|
||||
needs_val_of
|
||||
);
|
||||
match needs_val_of {
|
||||
ParseResult::MaybeNegNum => {
|
||||
if !(arg_os.to_string_lossy().parse::<i64>().is_ok()
|
||||
|| arg_os.to_string_lossy().parse::<f64>().is_ok())
|
||||
{
|
||||
return Err(Error::unknown_argument(
|
||||
&*arg_os.to_string_lossy(),
|
||||
"",
|
||||
&*usage::create_error_usage(self, matcher, None),
|
||||
self.color(),
|
||||
));
|
||||
}
|
||||
}
|
||||
ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => {
|
||||
continue
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let ParseResult::Opt(name) = needs_val_of {
|
||||
// Check to see if parsing a value from a previous arg
|
||||
let arg = self.opts
|
||||
|
@ -925,69 +972,30 @@ where
|
|||
// get the next value from the iterator
|
||||
continue;
|
||||
}
|
||||
} else if arg_os.starts_with(b"--") {
|
||||
needs_val_of = self.parse_long_arg(matcher, &arg_os)?;
|
||||
debugln!(
|
||||
"Parser:get_matches_with: After parse_long_arg {:?}",
|
||||
needs_val_of
|
||||
);
|
||||
match needs_val_of {
|
||||
ParseResult::Flag | ParseResult::Opt(..) | ParseResult::ValuesDone => {
|
||||
continue
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
} else if arg_os.starts_with(b"-") && arg_os.len_() != 1 {
|
||||
// Try to parse short args like normal, if AllowLeadingHyphen or
|
||||
// AllowNegativeNumbers is set, parse_short_arg will *not* throw
|
||||
// an error, and instead return Ok(None)
|
||||
needs_val_of = self.parse_short_arg(matcher, &arg_os)?;
|
||||
// If it's None, we then check if one of those two AppSettings was set
|
||||
debugln!(
|
||||
"Parser:get_matches_with: After parse_short_arg {:?}",
|
||||
needs_val_of
|
||||
);
|
||||
match needs_val_of {
|
||||
ParseResult::MaybeNegNum => {
|
||||
if !(arg_os.to_string_lossy().parse::<i64>().is_ok()
|
||||
|| arg_os.to_string_lossy().parse::<f64>().is_ok())
|
||||
{
|
||||
return Err(Error::unknown_argument(
|
||||
&*arg_os.to_string_lossy(),
|
||||
"",
|
||||
&*usage::create_error_usage(self, matcher, None),
|
||||
self.color(),
|
||||
));
|
||||
}
|
||||
}
|
||||
ParseResult::Opt(..) | ParseResult::Flag | ParseResult::ValuesDone => {
|
||||
continue
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound))
|
||||
&& !self.is_set(AS::InferSubcommands)
|
||||
if !(self.is_set(AS::ArgsNegateSubcommands) && self.is_set(AS::ValidArgFound))
|
||||
&& !self.is_set(AS::InferSubcommands) && !self.is_set(AS::AllowExternalSubcommands)
|
||||
{
|
||||
if let Some(cdate) =
|
||||
suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self))
|
||||
{
|
||||
if let Some(cdate) =
|
||||
suggestions::did_you_mean(&*arg_os.to_string_lossy(), sc_names!(self))
|
||||
{
|
||||
return Err(Error::invalid_subcommand(
|
||||
arg_os.to_string_lossy().into_owned(),
|
||||
cdate,
|
||||
self.meta.bin_name.as_ref().unwrap_or(&self.meta.name),
|
||||
&*usage::create_error_usage(self, matcher, None),
|
||||
self.color(),
|
||||
));
|
||||
}
|
||||
return Err(Error::invalid_subcommand(
|
||||
arg_os.to_string_lossy().into_owned(),
|
||||
cdate,
|
||||
self.meta.bin_name.as_ref().unwrap_or(&self.meta.name),
|
||||
&*usage::create_error_usage(self, matcher, None),
|
||||
self.color(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let low_index_mults = self.is_set(AS::LowIndexMultiplePositional)
|
||||
&& pos_counter == (self.positionals.len() - 1);
|
||||
let missing_pos = self.is_set(AS::AllowMissingPositional)
|
||||
&& pos_counter == (self.positionals.len() - 1);
|
||||
&& (pos_counter == (self.positionals.len() - 1)
|
||||
&& !self.is_set(AS::TrailingValues));
|
||||
debugln!(
|
||||
"Parser::get_matches_with: Positional counter...{}",
|
||||
pos_counter
|
||||
|
@ -1020,7 +1028,9 @@ where
|
|||
debugln!("Parser::get_matches_with: Bumping the positional counter...");
|
||||
pos_counter += 1;
|
||||
}
|
||||
} else if self.is_set(AS::ContainsLast) && self.is_set(AS::TrailingValues) {
|
||||
} else if (self.is_set(AS::AllowMissingPositional) && self.is_set(AS::TrailingValues))
|
||||
|| (self.is_set(AS::ContainsLast) && self.is_set(AS::TrailingValues))
|
||||
{
|
||||
// Came to -- and one postional has .last(true) set, so we go immediately
|
||||
// to the last (highest index) positional
|
||||
debugln!("Parser::get_matches_with: .last(true) and --, setting last pos");
|
||||
|
@ -1035,7 +1045,35 @@ where
|
|||
self.color(),
|
||||
));
|
||||
}
|
||||
parse_positional!(self, p, arg_os, pos_counter, matcher);
|
||||
if !self.is_set(AS::TrailingValues)
|
||||
&& (self.is_set(AS::TrailingVarArg) && pos_counter == self.positionals.len())
|
||||
{
|
||||
self.settings.set(AS::TrailingValues);
|
||||
}
|
||||
if self.cache.map_or(true, |name| name != p.b.name) {
|
||||
let check_all = self.is_set(AS::AllArgsOverrideSelf);
|
||||
{
|
||||
let any_arg = find_any_by_name!(self, self.cache.unwrap_or(""));
|
||||
matcher.process_arg_overrides(
|
||||
any_arg,
|
||||
&mut self.overrides,
|
||||
&mut self.required,
|
||||
check_all,
|
||||
);
|
||||
}
|
||||
self.cache = Some(p.b.name);
|
||||
}
|
||||
let _ = self.add_val_to_arg(p, &arg_os, matcher)?;
|
||||
|
||||
matcher.inc_occurrence_of(p.b.name);
|
||||
let _ = self.groups_for_arg(p.b.name)
|
||||
.and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
|
||||
|
||||
self.settings.set(AS::ValidArgFound);
|
||||
// Only increment the positional counter if it doesn't allow multiples
|
||||
if !p.b.settings.is_set(ArgSettings::Multiple) {
|
||||
pos_counter += 1;
|
||||
}
|
||||
self.settings.set(AS::ValidArgFound);
|
||||
} else if self.is_set(AS::AllowExternalSubcommands) {
|
||||
// Get external subcommand name
|
||||
|
@ -1136,9 +1174,50 @@ where
|
|||
});
|
||||
}
|
||||
|
||||
// In case the last arg was new, we need to process it's overrides
|
||||
let check_all = self.is_set(AS::AllArgsOverrideSelf);
|
||||
{
|
||||
let any_arg = find_any_by_name!(self, self.cache.unwrap_or(""));
|
||||
matcher.process_arg_overrides(
|
||||
any_arg,
|
||||
&mut self.overrides,
|
||||
&mut self.required,
|
||||
check_all,
|
||||
);
|
||||
}
|
||||
|
||||
self.remove_overrides(matcher);
|
||||
|
||||
Validator::new(self).validate(needs_val_of, subcmd_name, matcher)
|
||||
}
|
||||
|
||||
fn remove_overrides(&mut self, matcher: &mut ArgMatcher) {
|
||||
debugln!("Parser::remove_overrides:{:?};", self.overrides);
|
||||
for &(overr, name) in &self.overrides {
|
||||
debugln!("Parser::remove_overrides:iter:({},{});", overr, name);
|
||||
if matcher.is_present(overr) {
|
||||
debugln!(
|
||||
"Parser::remove_overrides:iter:({},{}): removing {};",
|
||||
overr,
|
||||
name,
|
||||
name
|
||||
);
|
||||
matcher.remove(name);
|
||||
for i in (0..self.required.len()).rev() {
|
||||
debugln!(
|
||||
"Parser::remove_overrides:iter:({},{}): removing required {};",
|
||||
overr,
|
||||
name,
|
||||
name
|
||||
);
|
||||
if self.required[i] == name {
|
||||
self.required.swap_remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_help_version(&mut self) {
|
||||
debugln!("Parser::propagate_help_version;");
|
||||
|
@ -1463,6 +1542,10 @@ where
|
|||
) -> ClapResult<ParseResult<'a>> {
|
||||
// maybe here lifetime should be 'a
|
||||
debugln!("Parser::parse_long_arg;");
|
||||
|
||||
// Update the curent index
|
||||
self.cur_idx.set(self.cur_idx.get() + 1);
|
||||
|
||||
let mut val = None;
|
||||
debug!("Parser::parse_long_arg: Does it contain '='...");
|
||||
let arg = if full_arg.contains_byte(b'=') {
|
||||
|
@ -1483,7 +1566,6 @@ where
|
|||
self.settings.set(AS::ValidArgFound);
|
||||
let ret = self.parse_opt(val, opt, val.is_some(), matcher)?;
|
||||
if self.cache.map_or(true, |name| name != opt.b.name) {
|
||||
arg_post_processing!(self, opt, matcher);
|
||||
self.cache = Some(opt.b.name);
|
||||
}
|
||||
|
||||
|
@ -1501,10 +1583,9 @@ where
|
|||
self.parse_flag(flag, matcher)?;
|
||||
|
||||
// Handle conflicts, requirements, etc.
|
||||
// if self.cache.map_or(true, |name| name != flag.b.name) {
|
||||
arg_post_processing!(self, flag, matcher);
|
||||
// self.cache = Some(flag.b.name);
|
||||
// }
|
||||
if self.cache.map_or(true, |name| name != flag.b.name) {
|
||||
self.cache = Some(flag.b.name);
|
||||
}
|
||||
|
||||
return Ok(ParseResult::Flag);
|
||||
} else if self.is_set(AS::AllowLeadingHyphen) {
|
||||
|
@ -1548,6 +1629,10 @@ where
|
|||
let mut ret = ParseResult::NotFound;
|
||||
for c in arg.chars() {
|
||||
debugln!("Parser::parse_short_arg:iter:{}", c);
|
||||
|
||||
// update each index because `-abcd` is four indices to clap
|
||||
self.cur_idx.set(self.cur_idx.get() + 1);
|
||||
|
||||
// Check for matching short options, and return the name if there is no trailing
|
||||
// concatenated value: -oval
|
||||
// Option: -o
|
||||
|
@ -1580,7 +1665,6 @@ where
|
|||
let ret = self.parse_opt(val, opt, false, matcher)?;
|
||||
|
||||
if self.cache.map_or(true, |name| name != opt.b.name) {
|
||||
arg_post_processing!(self, opt, matcher);
|
||||
self.cache = Some(opt.b.name);
|
||||
}
|
||||
|
||||
|
@ -1595,7 +1679,6 @@ where
|
|||
// Handle conflicts, requirements, overrides, etc.
|
||||
// Must be called here due to mutablilty
|
||||
if self.cache.map_or(true, |name| name != flag.b.name) {
|
||||
arg_post_processing!(self, flag, matcher);
|
||||
self.cache = Some(flag.b.name);
|
||||
}
|
||||
} else {
|
||||
|
@ -1725,12 +1808,20 @@ where
|
|||
{
|
||||
debugln!("Parser::add_single_val_to_arg;");
|
||||
debugln!("Parser::add_single_val_to_arg: adding val...{:?}", v);
|
||||
|
||||
// update the current index because each value is a distinct index to clap
|
||||
self.cur_idx.set(self.cur_idx.get() + 1);
|
||||
|
||||
// @TODO @docs @p4: docs for indices should probably note that a terminator isn't a value
|
||||
// and therefore not reported in indices
|
||||
if let Some(t) = arg.val_terminator() {
|
||||
if t == v {
|
||||
return Ok(ParseResult::ValuesDone);
|
||||
}
|
||||
}
|
||||
|
||||
matcher.add_val_to(arg.name(), v);
|
||||
matcher.add_index_to(arg.name(), self.cur_idx.get());
|
||||
|
||||
// Increment or create the group "args"
|
||||
if let Some(grps) = self.groups_for_arg(arg.name()) {
|
||||
|
@ -1745,7 +1836,6 @@ where
|
|||
Ok(ParseResult::ValuesDone)
|
||||
}
|
||||
|
||||
|
||||
fn parse_flag(
|
||||
&self,
|
||||
flag: &FlagBuilder<'a, 'b>,
|
||||
|
@ -1754,6 +1844,8 @@ where
|
|||
debugln!("Parser::parse_flag;");
|
||||
|
||||
matcher.inc_occurrence_of(flag.b.name);
|
||||
matcher.add_index_to(flag.b.name, self.cur_idx.get());
|
||||
|
||||
// Increment or create the group "args"
|
||||
self.groups_for_arg(flag.b.name)
|
||||
.and_then(|vec| Some(matcher.inc_occurrences_of(&*vec)));
|
||||
|
@ -1844,7 +1936,6 @@ where
|
|||
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
|
||||
|
||||
if $_self.cache.map_or(true, |name| name != $a.name()) {
|
||||
arg_post_processing!($_self, $a, $m);
|
||||
$_self.cache = Some($a.name());
|
||||
}
|
||||
} else if $m.get($a.b.name).is_some() {
|
||||
|
@ -1855,7 +1946,6 @@ where
|
|||
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
|
||||
|
||||
if $_self.cache.map_or(true, |name| name != $a.name()) {
|
||||
arg_post_processing!($_self, $a, $m);
|
||||
$_self.cache = Some($a.name());
|
||||
}
|
||||
}
|
||||
|
@ -1881,7 +1971,6 @@ where
|
|||
if add {
|
||||
$_self.add_val_to_arg($a, OsStr::new(default), $m)?;
|
||||
if $_self.cache.map_or(true, |name| name != $a.name()) {
|
||||
arg_post_processing!($_self, $a, $m);
|
||||
$_self.cache = Some($a.name());
|
||||
}
|
||||
done = true;
|
||||
|
@ -1920,7 +2009,6 @@ where
|
|||
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
|
||||
|
||||
if $_self.cache.map_or(true, |name| name != $a.name()) {
|
||||
arg_post_processing!($_self, $a, $m);
|
||||
$_self.cache = Some($a.name());
|
||||
}
|
||||
}
|
||||
|
@ -1929,7 +2017,6 @@ where
|
|||
$_self.add_val_to_arg($a, OsStr::new(val), $m)?;
|
||||
|
||||
if $_self.cache.map_or(true, |name| name != $a.name()) {
|
||||
arg_post_processing!($_self, $a, $m);
|
||||
$_self.cache = Some($a.name());
|
||||
}
|
||||
}
|
||||
|
@ -1972,7 +2059,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg> {
|
||||
pub fn find_any_arg(&self, name: &str) -> Option<&AnyArg<'a, 'b>> {
|
||||
if let Some(f) = find_by_name!(self, name, flags, iter) {
|
||||
return Some(f);
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ bitflags! {
|
|||
const VALID_ARG_FOUND = 1 << 37;
|
||||
const INFER_SUBCOMMANDS = 1 << 38;
|
||||
const CONTAINS_LAST = 1 << 39;
|
||||
const ARGS_OVERRIDE_SELF = 1 << 40;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,6 +76,7 @@ impl AppFlags {
|
|||
impl_settings! { AppSettings,
|
||||
ArgRequiredElseHelp => Flags::A_REQUIRED_ELSE_HELP,
|
||||
ArgsNegateSubcommands => Flags::ARGS_NEGATE_SCS,
|
||||
AllArgsOverrideSelf => Flags::ARGS_OVERRIDE_SELF,
|
||||
AllowExternalSubcommands => Flags::ALLOW_UNK_SC,
|
||||
AllowInvalidUtf8 => Flags::UTF8_NONE,
|
||||
AllowLeadingHyphen => Flags::LEADING_HYPHEN,
|
||||
|
@ -165,6 +167,13 @@ pub enum AppSettings {
|
|||
/// [`ArgMatches::lossy_values_of`]: ./struct.ArgMatches.html#method.lossy_values_of
|
||||
AllowInvalidUtf8,
|
||||
|
||||
/// Essentially sets [`Arg::overrides_with("itself")`] for all arguments.
|
||||
///
|
||||
/// **WARNING:** Positional arguments cannot override themselves (or we would never be able
|
||||
/// to advance to the next positional). This setting ignores positional arguments.
|
||||
/// [`Arg::overrides_with("itself")`]: ./struct.Arg.html#method.overrides_with
|
||||
AllArgsOverrideSelf,
|
||||
|
||||
/// Specifies that leading hyphens are allowed in argument *values*, such as negative numbers
|
||||
/// like `-10`. (which would otherwise be parsed as another flag or option)
|
||||
///
|
||||
|
@ -212,7 +221,9 @@ pub enum AppSettings {
|
|||
/// [`AllowLeadingHyphen`]: ./enum.AppSettings.html#variant.AllowLeadingHyphen
|
||||
AllowNegativeNumbers,
|
||||
|
||||
/// Allows one to implement a CLI where the second to last positional argument is optional, but
|
||||
/// Allows one to implement two styles of CLIs where positionals can be used out of order.
|
||||
///
|
||||
/// The first example is a CLI where the second to last positional argument is optional, but
|
||||
/// the final positional argument is required. Such as `$ prog [optional] <required>` where one
|
||||
/// of the two following usages is allowed:
|
||||
///
|
||||
|
@ -221,11 +232,46 @@ pub enum AppSettings {
|
|||
///
|
||||
/// This would otherwise not be allowed. This is useful when `[optional]` has a default value.
|
||||
///
|
||||
/// **Note:** In addition to using this setting, the second positional argument *must* be
|
||||
/// [required]
|
||||
/// **Note:** when using this style of "missing positionals" the final positional *must* be
|
||||
/// [required] if `--` will not be used to skip to the final positional argument.
|
||||
///
|
||||
/// **Note:** This style also only allows a single positional argument to be "skipped" without
|
||||
/// the use of `--`. To skip more than one, see the second example.
|
||||
///
|
||||
/// The second example is when one wants to skip multiple optional positional arguments, and use
|
||||
/// of the `--` operator is OK (but not required if all arguments will be specified anyways).
|
||||
///
|
||||
/// For example, imagine a CLI which has three positional arguments `[foo] [bar] [baz]...` where
|
||||
/// `baz` accepts multiple values (similar to man `ARGS...` style training arguments).
|
||||
///
|
||||
/// With this setting the following invocations are posisble:
|
||||
///
|
||||
/// * `$ prog foo bar baz1 baz2 baz3`
|
||||
/// * `$ prog foo -- baz1 baz2 baz3`
|
||||
/// * `$ prog -- baz1 baz2 baz3`
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Style number one from above:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, AppSettings};
|
||||
/// // Assume there is an external subcommand named "subcmd"
|
||||
/// let m = App::new("myprog")
|
||||
/// .setting(AppSettings::AllowMissingPositional)
|
||||
/// .arg(Arg::with_name("arg1"))
|
||||
/// .arg(Arg::with_name("arg2")
|
||||
/// .required(true))
|
||||
/// .get_matches_from(vec![
|
||||
/// "prog", "other"
|
||||
/// ]);
|
||||
///
|
||||
/// assert_eq!(m.value_of("arg1"), None);
|
||||
/// assert_eq!(m.value_of("arg2"), Some("other"));
|
||||
/// ```
|
||||
///
|
||||
/// Now the same example, but using a default value for the first optional positional argument
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, AppSettings};
|
||||
/// // Assume there is an external subcommand named "subcmd"
|
||||
|
@ -236,12 +282,49 @@ pub enum AppSettings {
|
|||
/// .arg(Arg::with_name("arg2")
|
||||
/// .required(true))
|
||||
/// .get_matches_from(vec![
|
||||
/// "myprog", "other"
|
||||
/// "prog", "other"
|
||||
/// ]);
|
||||
///
|
||||
/// assert_eq!(m.value_of("arg1"), Some("something"));
|
||||
/// assert_eq!(m.value_of("arg2"), Some("other"));
|
||||
/// ```
|
||||
/// Style number two from above:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, AppSettings};
|
||||
/// // Assume there is an external subcommand named "subcmd"
|
||||
/// let m = App::new("myprog")
|
||||
/// .setting(AppSettings::AllowMissingPositional)
|
||||
/// .arg(Arg::with_name("foo"))
|
||||
/// .arg(Arg::with_name("bar"))
|
||||
/// .arg(Arg::with_name("baz").multiple(true))
|
||||
/// .get_matches_from(vec![
|
||||
/// "prog", "foo", "bar", "baz1", "baz2", "baz3"
|
||||
/// ]);
|
||||
///
|
||||
/// assert_eq!(m.value_of("foo"), Some("foo"));
|
||||
/// assert_eq!(m.value_of("bar"), Some("bar"));
|
||||
/// assert_eq!(m.values_of("baz").unwrap().collect::<Vec<_>>(), &["baz1", "baz2", "baz3"]);
|
||||
/// ```
|
||||
///
|
||||
/// Now nofice if we don't specifiy `foo` or `baz` but use the `--` operator.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg, AppSettings};
|
||||
/// // Assume there is an external subcommand named "subcmd"
|
||||
/// let m = App::new("myprog")
|
||||
/// .setting(AppSettings::AllowMissingPositional)
|
||||
/// .arg(Arg::with_name("foo"))
|
||||
/// .arg(Arg::with_name("bar"))
|
||||
/// .arg(Arg::with_name("baz").multiple(true))
|
||||
/// .get_matches_from(vec![
|
||||
/// "prog", "--", "baz1", "baz2", "baz3"
|
||||
/// ]);
|
||||
///
|
||||
/// assert_eq!(m.value_of("foo"), None);
|
||||
/// assert_eq!(m.value_of("bar"), None);
|
||||
/// assert_eq!(m.values_of("baz").unwrap().collect::<Vec<_>>(), &["baz1", "baz2", "baz3"]);
|
||||
/// ```
|
||||
/// [required]: ./struct.Arg.html#method.required
|
||||
AllowMissingPositional,
|
||||
|
||||
|
@ -496,7 +579,7 @@ pub enum AppSettings {
|
|||
DeriveDisplayOrder,
|
||||
|
||||
/// Specifies to use the version of the current command for all child [`SubCommand`]s.
|
||||
/// (Defaults to `false`; subcommands have independant version strings from their parents.)
|
||||
/// (Defaults to `false`; subcommands have independent version strings from their parents.)
|
||||
///
|
||||
/// **NOTE:** The version for the current command **and** this setting must be set **prior** to
|
||||
/// adding any child subcommands
|
||||
|
@ -544,7 +627,7 @@ pub enum AppSettings {
|
|||
/// designing CLIs which allow inferred subcommands and have potential positional/free
|
||||
/// arguments whose values could start with the same characters as subcommands. If this is the
|
||||
/// case, it's recommended to use settings such as [`AppSeettings::ArgsNegateSubcommands`] in
|
||||
/// conjuction with this setting.
|
||||
/// conjunction with this setting.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
|
|
@ -36,11 +36,14 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
self.0.add_defaults(matcher)?;
|
||||
if let ParseResult::Opt(a) = needs_val_of {
|
||||
debugln!("Validator::validate: needs_val_of={:?}", a);
|
||||
let o = self.0
|
||||
let o = {
|
||||
self.0
|
||||
.opts
|
||||
.iter()
|
||||
.find(|o| o.b.name == a)
|
||||
.expect(INTERNAL_ERROR_MSG);
|
||||
.expect(INTERNAL_ERROR_MSG)
|
||||
.clone()
|
||||
};
|
||||
self.validate_required(matcher)?;
|
||||
reqs_validated = true;
|
||||
let should_err = if let Some(v) = matcher.0.args.get(&*o.b.name) {
|
||||
|
@ -50,7 +53,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
};
|
||||
if should_err {
|
||||
return Err(Error::empty_value(
|
||||
o,
|
||||
&o,
|
||||
&*usage::create_error_usage(self.0, matcher, None),
|
||||
self.0.color(),
|
||||
));
|
||||
|
@ -78,7 +81,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_values<A>(
|
||||
fn validate_arg_values<A>(
|
||||
&self,
|
||||
arg: &A,
|
||||
ma: &MatchedArg,
|
||||
|
@ -87,11 +90,11 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
{
|
||||
debugln!("Validator::validate_values: arg={:?}", arg.name());
|
||||
debugln!("Validator::validate_arg_values: arg={:?}", arg.name());
|
||||
for val in &ma.vals {
|
||||
if self.0.is_set(AS::StrictUtf8) && val.to_str().is_none() {
|
||||
debugln!(
|
||||
"Validator::validate_values: invalid UTF-8 found in val {:?}",
|
||||
"Validator::validate_arg_values: invalid UTF-8 found in val {:?}",
|
||||
val
|
||||
);
|
||||
return Err(Error::invalid_utf8(
|
||||
|
@ -100,7 +103,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
));
|
||||
}
|
||||
if let Some(p_vals) = arg.possible_vals() {
|
||||
debugln!("Validator::validate_values: possible_vals={:?}", p_vals);
|
||||
debugln!("Validator::validate_arg_values: possible_vals={:?}", p_vals);
|
||||
let val_str = val.to_string_lossy();
|
||||
let ok = if arg.is_set(ArgSettings::CaseInsensitive) {
|
||||
p_vals.iter().any(|pv| pv.eq_ignore_ascii_case(&*val_str))
|
||||
|
@ -120,7 +123,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
if !arg.is_set(ArgSettings::EmptyValues) && val.is_empty_()
|
||||
&& matcher.contains(&*arg.name())
|
||||
{
|
||||
debugln!("Validator::validate_values: illegal empty val found");
|
||||
debugln!("Validator::validate_arg_values: illegal empty val found");
|
||||
return Err(Error::empty_value(
|
||||
arg,
|
||||
&*usage::create_error_usage(self.0, matcher, None),
|
||||
|
@ -128,7 +131,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
));
|
||||
}
|
||||
if let Some(vtor) = arg.validator() {
|
||||
debug!("Validator::validate_values: checking validator...");
|
||||
debug!("Validator::validate_arg_values: checking validator...");
|
||||
if let Err(e) = vtor(val.to_string_lossy().into_owned()) {
|
||||
sdebugln!("error");
|
||||
return Err(Error::value_validation(Some(arg), e, self.0.color()));
|
||||
|
@ -137,7 +140,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
}
|
||||
}
|
||||
if let Some(vtor) = arg.validator_os() {
|
||||
debug!("Validator::validate_values: checking validator_os...");
|
||||
debug!("Validator::validate_arg_values: checking validator_os...");
|
||||
if let Err(e) = vtor(val) {
|
||||
sdebugln!("error");
|
||||
return Err(Error::value_validation(
|
||||
|
@ -153,44 +156,83 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
|
||||
debugln!(
|
||||
"Validator::validate_blacklist: blacklist={:?}",
|
||||
self.0.blacklist
|
||||
fn build_err(&self, name: &str, matcher: &ArgMatcher) -> ClapResult<()> {
|
||||
debugln!("build_err!: name={}", name);
|
||||
let mut c_with = find_from!(self.0, &name, blacklist, &matcher);
|
||||
c_with = c_with.or(
|
||||
self.0.find_any_arg(&name).map_or(None, |aa| aa.blacklist())
|
||||
.map_or(None,
|
||||
|bl| bl.iter().find(|arg| matcher.contains(arg)))
|
||||
.map_or(None, |an| self.0.find_any_arg(an))
|
||||
.map_or(None, |aa| Some(format!("{}", aa)))
|
||||
);
|
||||
macro_rules! build_err {
|
||||
($p:expr, $name:expr, $matcher:ident) => ({
|
||||
debugln!("build_err!: name={}", $name);
|
||||
let mut c_with = find_from!($p, &$name, blacklist, &$matcher);
|
||||
c_with = c_with.or(
|
||||
$p.find_any_arg(&$name).map_or(None, |aa| aa.blacklist())
|
||||
.map_or(None,
|
||||
|bl| bl.iter().find(|arg| $matcher.contains(arg)))
|
||||
.map_or(None, |an| $p.find_any_arg(an))
|
||||
.map_or(None, |aa| Some(format!("{}", aa)))
|
||||
);
|
||||
debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &$name);
|
||||
$matcher.remove(&$name);
|
||||
let usg = usage::create_error_usage($p, $matcher, None);
|
||||
if let Some(f) = find_by_name!($p, $name, flags, iter) {
|
||||
debugln!("build_err!: It was a flag...");
|
||||
Error::argument_conflict(f, c_with, &*usg, self.0.color())
|
||||
} else if let Some(o) = find_by_name!($p, $name, opts, iter) {
|
||||
debugln!("build_err!: It was an option...");
|
||||
Error::argument_conflict(o, c_with, &*usg, self.0.color())
|
||||
} else {
|
||||
match find_by_name!($p, $name, positionals, values) {
|
||||
Some(p) => {
|
||||
debugln!("build_err!: It was a positional...");
|
||||
Error::argument_conflict(p, c_with, &*usg, self.0.color())
|
||||
},
|
||||
None => panic!(INTERNAL_ERROR_MSG)
|
||||
debugln!("build_err!: '{:?}' conflicts with '{}'", c_with, &name);
|
||||
// matcher.remove(&name);
|
||||
let usg = usage::create_error_usage(self.0, matcher, None);
|
||||
if let Some(f) = find_by_name!(self.0, name, flags, iter) {
|
||||
debugln!("build_err!: It was a flag...");
|
||||
Err(Error::argument_conflict(f, c_with, &*usg, self.0.color()))
|
||||
} else if let Some(o) = find_by_name!(self.0, name, opts, iter) {
|
||||
debugln!("build_err!: It was an option...");
|
||||
Err(Error::argument_conflict(o, c_with, &*usg, self.0.color()))
|
||||
} else {
|
||||
match find_by_name!(self.0, name, positionals, values) {
|
||||
Some(p) => {
|
||||
debugln!("build_err!: It was a positional...");
|
||||
Err(Error::argument_conflict(p, c_with, &*usg, self.0.color()))
|
||||
},
|
||||
None => panic!(INTERNAL_ERROR_MSG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_blacklist(&self, matcher: &mut ArgMatcher) -> ClapResult<()> {
|
||||
debugln!("Validator::validate_blacklist;");
|
||||
let mut conflicts: Vec<&str> = vec![];
|
||||
for (&name, _) in matcher.iter() {
|
||||
debugln!("Validator::validate_blacklist:iter:{};", name);
|
||||
if let Some(grps) = self.0.groups_for_arg(name) {
|
||||
for grp in &grps {
|
||||
if let Some(g) = self.0.groups.iter().find(|g| &g.name == grp) {
|
||||
if !g.multiple {
|
||||
for arg in &g.args {
|
||||
if arg == &name {
|
||||
continue;
|
||||
}
|
||||
conflicts.push(arg);
|
||||
}
|
||||
}
|
||||
if let Some(ref gc) = g.conflicts {
|
||||
conflicts.extend(&*gc);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(arg) = find_any_by_name!(self.0, name) {
|
||||
if let Some(bl) = arg.blacklist() {
|
||||
for conf in bl {
|
||||
if matcher.get(conf).is_some() {
|
||||
conflicts.push(conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debugln!("Validator::validate_blacklist:iter:{}:group;", name);
|
||||
let args = self.0.arg_names_in_group(name);
|
||||
for arg in &args {
|
||||
debugln!("Validator::validate_blacklist:iter:{}:group:iter:{};", name, arg);
|
||||
if let Some(bl) = find_any_by_name!(self.0, *arg).unwrap().blacklist() {
|
||||
for conf in bl {
|
||||
if matcher.get(conf).is_some() {
|
||||
conflicts.push(conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for name in &self.0.blacklist {
|
||||
for name in &conflicts {
|
||||
debugln!(
|
||||
"Validator::validate_blacklist:iter:{}: Checking blacklisted arg",
|
||||
name
|
||||
|
@ -213,7 +255,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
name,
|
||||
n
|
||||
);
|
||||
return Err(build_err!(self.0, n, matcher));
|
||||
return self.build_err(n, matcher);
|
||||
}
|
||||
}
|
||||
} else if let Some(ma) = matcher.get(name) {
|
||||
|
@ -224,7 +266,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
should_err = ma.occurs > 0;
|
||||
}
|
||||
if should_err {
|
||||
return Err(build_err!(self.0, *name, matcher));
|
||||
return self.build_err(*name, matcher);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -240,7 +282,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
);
|
||||
if let Some(opt) = find_by_name!(self.0, *name, opts, iter) {
|
||||
self.validate_arg_num_vals(opt, ma, matcher)?;
|
||||
self.validate_values(opt, ma, matcher)?;
|
||||
self.validate_arg_values(opt, ma, matcher)?;
|
||||
self.validate_arg_requires(opt, ma, matcher)?;
|
||||
self.validate_arg_num_occurs(opt, ma, matcher)?;
|
||||
} else if let Some(flag) = find_by_name!(self.0, *name, flags, iter) {
|
||||
|
@ -249,7 +291,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
} else if let Some(pos) = find_by_name!(self.0, *name, positionals, values) {
|
||||
self.validate_arg_num_vals(pos, ma, matcher)?;
|
||||
self.validate_arg_num_occurs(pos, ma, matcher)?;
|
||||
self.validate_values(pos, ma, matcher)?;
|
||||
self.validate_arg_values(pos, ma, matcher)?;
|
||||
self.validate_arg_requires(pos, ma, matcher)?;
|
||||
} else {
|
||||
let grp = self.0
|
||||
|
@ -297,7 +339,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
{
|
||||
debugln!("Validator::validate_arg_num_vals;");
|
||||
debugln!("Validator::validate_arg_num_vals:{}", a.name());
|
||||
if let Some(num) = a.num_vals() {
|
||||
debugln!("Validator::validate_arg_num_vals: num_vals set...{}", num);
|
||||
let should_err = if a.is_set(ArgSettings::Multiple) {
|
||||
|
@ -381,7 +423,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
{
|
||||
debugln!("Validator::validate_arg_requires;");
|
||||
debugln!("Validator::validate_arg_requires:{};", a.name());
|
||||
if let Some(a_reqs) = a.requires() {
|
||||
for &(val, name) in a_reqs.iter().filter(|&&(val, _)| val.is_some()) {
|
||||
let missing_req =
|
||||
|
@ -390,31 +432,55 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
return self.missing_required_error(matcher, None);
|
||||
}
|
||||
}
|
||||
for &(_, name) in a_reqs.iter().filter(|&&(val, _)| val.is_none()) {
|
||||
if !matcher.contains(name) {
|
||||
return self.missing_required_error(matcher, Some(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_required(&self, matcher: &ArgMatcher) -> ClapResult<()> {
|
||||
fn validate_required(&mut self, matcher: &ArgMatcher) -> ClapResult<()> {
|
||||
debugln!(
|
||||
"Validator::validate_required: required={:?};",
|
||||
self.0.required
|
||||
);
|
||||
'outer: for name in &self.0.required {
|
||||
|
||||
let mut should_err = false;
|
||||
let mut to_rem = Vec::new();
|
||||
for name in &self.0.required {
|
||||
debugln!("Validator::validate_required:iter:{}:", name);
|
||||
if matcher.contains(name) {
|
||||
continue 'outer;
|
||||
continue;
|
||||
}
|
||||
if let Some(a) = find_by_name!(self.0, *name, flags, iter) {
|
||||
if to_rem.contains(name) {
|
||||
continue;
|
||||
} else if let Some(a) = find_any_by_name!(self.0, *name) {
|
||||
if self.is_missing_required_ok(a, matcher) {
|
||||
continue 'outer;
|
||||
to_rem.push(a.name());
|
||||
if let Some(reqs) = a.requires() {
|
||||
for r in reqs
|
||||
.iter()
|
||||
.filter(|&&(val, _)| val.is_none())
|
||||
.map(|&(_, name)| name)
|
||||
{
|
||||
to_rem.push(r);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} else if let Some(a) = find_by_name!(self.0, *name, opts, iter) {
|
||||
if self.is_missing_required_ok(a, matcher) {
|
||||
continue 'outer;
|
||||
}
|
||||
} else if let Some(a) = find_by_name!(self.0, *name, positionals, values) {
|
||||
if self.is_missing_required_ok(a, matcher) {
|
||||
continue 'outer;
|
||||
}
|
||||
should_err = true;
|
||||
break;
|
||||
}
|
||||
if should_err {
|
||||
for r in &to_rem {
|
||||
'inner: for i in (0 .. self.0.required.len()).rev() {
|
||||
if &self.0.required[i] == r {
|
||||
self.0.required.swap_remove(i);
|
||||
break 'inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
return self.missing_required_error(matcher, None);
|
||||
|
@ -431,11 +497,8 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_conflicts<A>(&self, a: &A, matcher: &ArgMatcher) -> Option<bool>
|
||||
where
|
||||
A: AnyArg<'a, 'b>,
|
||||
{
|
||||
debugln!("Validator::validate_conflicts: a={:?};", a.name());
|
||||
fn validate_arg_conflicts(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
|
||||
debugln!("Validator::validate_arg_conflicts: a={:?};", a.name());
|
||||
a.blacklist().map(|bl| {
|
||||
bl.iter().any(|conf| {
|
||||
matcher.contains(conf)
|
||||
|
@ -448,10 +511,7 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
})
|
||||
}
|
||||
|
||||
fn validate_required_unless<A>(&self, a: &A, matcher: &ArgMatcher) -> Option<bool>
|
||||
where
|
||||
A: AnyArg<'a, 'b>,
|
||||
{
|
||||
fn validate_required_unless(&self, a: &AnyArg, matcher: &ArgMatcher) -> Option<bool> {
|
||||
debugln!("Validator::validate_required_unless: a={:?};", a.name());
|
||||
macro_rules! check {
|
||||
($how:ident, $_self:expr, $a:ident, $m:ident) => {{
|
||||
|
@ -506,12 +566,9 @@ impl<'a, 'b, 'z> Validator<'a, 'b, 'z> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn is_missing_required_ok<A>(&self, a: &A, matcher: &ArgMatcher) -> bool
|
||||
where
|
||||
A: AnyArg<'a, 'b>,
|
||||
{
|
||||
fn is_missing_required_ok(&self, a: &AnyArg, matcher: &ArgMatcher) -> bool {
|
||||
debugln!("Validator::is_missing_required_ok: a={}", a.name());
|
||||
self.validate_conflicts(a, matcher).unwrap_or(false)
|
||||
self.validate_arg_conflicts(a, matcher).unwrap_or(false)
|
||||
|| self.validate_required_unless(a, matcher).unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use std::ffi::{OsStr, OsString};
|
|||
// Internal
|
||||
use args::settings::ArgSettings;
|
||||
use map::{self, VecMap};
|
||||
use INTERNAL_ERROR_MSG;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait AnyArg<'n, 'e>: std_fmt::Display {
|
||||
|
@ -41,3 +42,33 @@ pub trait AnyArg<'n, 'e>: std_fmt::Display {
|
|||
pub trait DispOrder {
|
||||
fn disp_ord(&self) -> usize;
|
||||
}
|
||||
|
||||
impl<'n, 'e, 'z, T: ?Sized> AnyArg<'n, 'e> for &'z T where T: AnyArg<'n, 'e> + 'z {
|
||||
fn name(&self) -> &'n str { (*self).name() }
|
||||
fn overrides(&self) -> Option<&[&'e str]> { (*self).overrides() }
|
||||
fn aliases(&self) -> Option<Vec<&'e str>> { (*self).aliases() }
|
||||
fn requires(&self) -> Option<&[(Option<&'e str>, &'n str)]> { (*self).requires() }
|
||||
fn blacklist(&self) -> Option<&[&'e str]> { (*self).blacklist() }
|
||||
fn required_unless(&self) -> Option<&[&'e str]> { (*self).required_unless() }
|
||||
fn is_set(&self, a: ArgSettings) -> bool { (*self).is_set(a) }
|
||||
fn set(&mut self, _: ArgSettings) { panic!(INTERNAL_ERROR_MSG) }
|
||||
fn has_switch(&self) -> bool { (*self).has_switch() }
|
||||
fn max_vals(&self) -> Option<u64> { (*self).max_vals() }
|
||||
fn min_vals(&self) -> Option<u64> { (*self).min_vals() }
|
||||
fn num_vals(&self) -> Option<u64> { (*self).num_vals() }
|
||||
fn possible_vals(&self) -> Option<&[&'e str]> { (*self).possible_vals() }
|
||||
fn validator(&self) -> Option<&Rc<Fn(String) -> Result<(), String>>> { (*self).validator() }
|
||||
fn validator_os(&self) -> Option<&Rc<Fn(&OsStr) -> Result<(), OsString>>> { (*self).validator_os() }
|
||||
fn short(&self) -> Option<char> { (*self).short() }
|
||||
fn long(&self) -> Option<&'e str> { (*self).long() }
|
||||
fn val_delim(&self) -> Option<char> { (*self).val_delim() }
|
||||
fn takes_value(&self) -> bool { (*self).takes_value() }
|
||||
fn val_names(&self) -> Option<&VecMap<&'e str>> { (*self).val_names() }
|
||||
fn help(&self) -> Option<&'e str> { (*self).help() }
|
||||
fn long_help(&self) -> Option<&'e str> { (*self).long_help() }
|
||||
fn default_val(&self) -> Option<&'e OsStr> { (*self).default_val() }
|
||||
fn default_vals_ifs(&self) -> Option<map::Values<(&'n str, Option<&'e OsStr>, &'e OsStr)>> { (*self).default_vals_ifs() }
|
||||
fn env<'s>(&'s self) -> Option<(&'n OsStr, Option<&'s OsString>)> { (*self).env() }
|
||||
fn longest_filter(&self) -> bool { (*self).longest_filter() }
|
||||
fn val_terminator(&self) -> Option<&'e str> { (*self).val_terminator() }
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::rc::Rc;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
|
||||
use osstringext::OsStrExt3;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(not(any(target_os = "windows", target_arch = "wasm32")))]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::env;
|
||||
|
||||
|
@ -122,6 +122,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
"value_name" => yaml_to_str!(a, v, value_name),
|
||||
"use_delimiter" => yaml_to_bool!(a, v, use_delimiter),
|
||||
"allow_hyphen_values" => yaml_to_bool!(a, v, allow_hyphen_values),
|
||||
"last" => yaml_to_bool!(a, v, last),
|
||||
"require_delimiter" => yaml_to_bool!(a, v, require_delimiter),
|
||||
"value_delimiter" => yaml_to_str!(a, v, value_delimiter),
|
||||
"required_unless" => yaml_to_str!(a, v, required_unless),
|
||||
|
@ -146,8 +147,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
}
|
||||
s => panic!(
|
||||
"Unknown Arg setting '{}' in YAML file for arg '{}'",
|
||||
s,
|
||||
name_str
|
||||
s, name_str
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -822,14 +822,14 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
|
||||
/// Allows values which start with a leading hyphen (`-`)
|
||||
///
|
||||
/// **WARNING**: Take caution when using this setting, combined with [`Arg::multiple(true)`] as
|
||||
/// it this becomes ambigous `$ prog --arg -- -- val`. All three `--, --, val` will be values
|
||||
/// **WARNING**: Take caution when using this setting combined with [`Arg::multiple(true)`], as
|
||||
/// this becomes ambiguous `$ prog --arg -- -- val`. All three `--, --, val` will be values
|
||||
/// when the user may have thought the second `--` would constitute the normal, "Only
|
||||
/// positional args follow" idiom. To fix this, consider using [`Arg::number_of_values(1)`]
|
||||
///
|
||||
/// **WARNING**: When building your CLIs, consider the effects of allowing leading hyphens and
|
||||
/// the user passing in a value that matches a valid short. For example `prog -opt -F` where
|
||||
/// `-F` is supposed to be a value, yet `-F` is *also* a valid short for anther arg. Care should
|
||||
/// `-F` is supposed to be a value, yet `-F` is *also* a valid short for another arg. Care should
|
||||
/// should be taken when designing these args. This is compounded by the ability to "stack"
|
||||
/// short args. I.e. if `-val` is supposed to be a value, but `-v`, `-a`, and `-l` are all valid
|
||||
/// shorts.
|
||||
|
@ -1203,6 +1203,10 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// **NOTE:** When an argument is overridden it is essentially as if it never was used, any
|
||||
/// conflicts, requirements, etc. are evaluated **after** all "overrides" have been removed
|
||||
///
|
||||
/// **WARNING:** Positional arguments cannot override themselves (or we would never be able
|
||||
/// to advance to the next positional). If a positional agument lists itself as an override,
|
||||
/// it is simply ignored.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -1222,6 +1226,74 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// // was never used because it was overridden with color
|
||||
/// assert!(!m.is_present("flag"));
|
||||
/// ```
|
||||
/// Care must be taken when using this setting, and having an arg override with itself. This
|
||||
/// is common practice when supporting things like shell aliases, config files, etc.
|
||||
/// However, when combined with multiple values, it can get dicy.
|
||||
/// Here is how clap handles such situations:
|
||||
///
|
||||
/// When a flag overrides itself, it's as if the flag was only ever used once (essentially
|
||||
/// preventing a "Unexpected multiple usage" error):
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("posix")
|
||||
/// .arg(Arg::from_usage("--flag 'some flag'").overrides_with("flag"))
|
||||
/// .get_matches_from(vec!["posix", "--flag", "--flag"]);
|
||||
/// assert!(m.is_present("flag"));
|
||||
/// assert_eq!(m.occurrences_of("flag"), 1);
|
||||
/// ```
|
||||
/// Making a arg `multiple(true)` and override itself is essentially meaningless. Therefore
|
||||
/// clap ignores an override of self if it's a flag and it already accepts multiple occurrences.
|
||||
///
|
||||
/// ```
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("posix")
|
||||
/// .arg(Arg::from_usage("--flag... 'some flag'").overrides_with("flag"))
|
||||
/// .get_matches_from(vec!["", "--flag", "--flag", "--flag", "--flag"]);
|
||||
/// assert!(m.is_present("flag"));
|
||||
/// assert_eq!(m.occurrences_of("flag"), 4);
|
||||
/// ```
|
||||
/// Now notice with options (which *do not* set `multiple(true)`), it's as if only the last
|
||||
/// occurrence happened.
|
||||
///
|
||||
/// ```
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("posix")
|
||||
/// .arg(Arg::from_usage("--opt [val] 'some option'").overrides_with("opt"))
|
||||
/// .get_matches_from(vec!["", "--opt=some", "--opt=other"]);
|
||||
/// assert!(m.is_present("opt"));
|
||||
/// assert_eq!(m.occurrences_of("opt"), 1);
|
||||
/// assert_eq!(m.value_of("opt"), Some("other"));
|
||||
/// ```
|
||||
///
|
||||
/// Just like flags, options with `multiple(true)` set, will ignore the "override self" setting.
|
||||
///
|
||||
/// ```
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("posix")
|
||||
/// .arg(Arg::from_usage("--opt [val]... 'some option'")
|
||||
/// .overrides_with("opt"))
|
||||
/// .get_matches_from(vec!["", "--opt", "first", "over", "--opt", "other", "val"]);
|
||||
/// assert!(m.is_present("opt"));
|
||||
/// assert_eq!(m.occurrences_of("opt"), 2);
|
||||
/// assert_eq!(m.values_of("opt").unwrap().collect::<Vec<_>>(), &["first", "over", "other", "val"]);
|
||||
/// ```
|
||||
///
|
||||
/// A safe thing to do if you'd like to support an option which supports multiple values, but
|
||||
/// also is "overridable" by itself, is to use `use_delimiter(false)` and *not* use
|
||||
/// `multiple(true)` while telling users to seperate values with a comma (i.e. `val1,val2`)
|
||||
///
|
||||
/// ```
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("posix")
|
||||
/// .arg(Arg::from_usage("--opt [val] 'some option'")
|
||||
/// .overrides_with("opt")
|
||||
/// .use_delimiter(false))
|
||||
/// .get_matches_from(vec!["", "--opt=some,other", "--opt=one,two"]);
|
||||
/// assert!(m.is_present("opt"));
|
||||
/// assert_eq!(m.occurrences_of("opt"), 1);
|
||||
/// assert_eq!(m.values_of("opt").unwrap().collect::<Vec<_>>(), &["one,two"]);
|
||||
/// ```
|
||||
pub fn overrides_with(mut self, name: &'a str) -> Self {
|
||||
if let Some(ref mut vec) = self.b.overrides {
|
||||
vec.push(name.as_ref());
|
||||
|
@ -1907,7 +1979,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// **WARNING:**
|
||||
///
|
||||
/// When using args with `multiple(true)` on [options] or [positionals] (i.e. those args that
|
||||
/// accept values) and [subcommands], one needs to consider the posibility of an argument value
|
||||
/// accept values) and [subcommands], one needs to consider the possibility of an argument value
|
||||
/// being the same as a valid subcommand. By default `clap` will parse the argument in question
|
||||
/// as a value *only if* a value is possible at that moment. Otherwise it will be parsed as a
|
||||
/// subcommand. In effect, this means using `multiple(true)` with no additional parameters and
|
||||
|
@ -1977,7 +2049,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// let files: Vec<_> = m.values_of("file").unwrap().collect();
|
||||
/// assert_eq!(files, ["file1", "file2", "file3"]);
|
||||
/// ```
|
||||
/// This is functionally equivilant to the example above
|
||||
/// This is functionally equivalent to the example above
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
|
@ -2148,7 +2220,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// # ;
|
||||
/// ```
|
||||
///
|
||||
/// For example, assume an appliction with two subcommands, and you'd like to define a
|
||||
/// For example, assume an application with two subcommands, and you'd like to define a
|
||||
/// `--verbose` flag that can be called on any of the subcommands and parent, but you don't
|
||||
/// want to clutter the source with three duplicate [`Arg`] definitions.
|
||||
///
|
||||
|
@ -2655,9 +2727,9 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
///
|
||||
/// **NOTE:** This does *not* implicitly set [`Arg::multiple(true)`]. This is because
|
||||
/// `-o val -o val` is multiple occurrences but a single value and `-o val1 val2` is a single
|
||||
/// occurence with multiple values. For positional arguments this **does** set
|
||||
/// occurrence with multiple values. For positional arguments this **does** set
|
||||
/// [`Arg::multiple(true)`] because there is no way to determine the difference between multiple
|
||||
/// occurences and multiple values.
|
||||
/// occurrences and multiple values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -2718,9 +2790,9 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
///
|
||||
/// **NOTE:** This does not implicitly set [`Arg::multiple(true)`]. This is because
|
||||
/// `-o val -o val` is multiple occurrences but a single value and `-o val1 val2` is a single
|
||||
/// occurence with multiple values. For positional arguments this **does** set
|
||||
/// occurrence with multiple values. For positional arguments this **does** set
|
||||
/// [`Arg::multiple(true)`] because there is no way to determine the difference between multiple
|
||||
/// occurences and multiple values.
|
||||
/// occurrences and multiple values.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -3115,7 +3187,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// **NOTE:** This implicitly sets [`Arg::takes_value(true)`].
|
||||
///
|
||||
/// **NOTE:** This setting effectively disables `AppSettings::ArgRequiredElseHelp` if used in
|
||||
/// conjuction as it ensures that some argument will always be present.
|
||||
/// conjunction as it ensures that some argument will always be present.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -3357,7 +3429,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// ```
|
||||
///
|
||||
/// We can also see that these values are applied in order, and if more than one condition is
|
||||
/// true, only the first evaluatd "wins"
|
||||
/// true, only the first evaluated "wins"
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
|
@ -3412,7 +3484,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
/// will return `0` even though the [`ArgMatches::value_of`] will return the default specified.
|
||||
///
|
||||
/// **NOTE:** If the user *does not* use this argument at runtime [`ArgMatches::is_present`] will
|
||||
/// return `true` if the variable is present in the environemnt . If you wish to determine whether
|
||||
/// return `true` if the variable is present in the environment . If you wish to determine whether
|
||||
/// the argument was used at runtime or not, consider [`ArgMatches::occurrences_of`] which will
|
||||
/// return `0` if the argument was *not* used at runtime.
|
||||
///
|
||||
|
@ -3503,7 +3575,9 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
///
|
||||
/// assert_eq!(m.values_of("flag").unwrap().collect::<Vec<_>>(), vec!["env1", "env2"]);
|
||||
/// ```
|
||||
pub fn env(self, name: &'a str) -> Self { self.env_os(OsStr::new(name)) }
|
||||
pub fn env(self, name: &'a str) -> Self {
|
||||
self.env_os(OsStr::new(name))
|
||||
}
|
||||
|
||||
/// Specifies that if the value is not passed in as an argument, that it should be retrieved
|
||||
/// from the environment if available in the exact same manner as [`Arg::env`] only using
|
||||
|
@ -3516,7 +3590,7 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
}
|
||||
|
||||
/// @TODO @p2 @docs @release: write docs
|
||||
pub fn hide_env_values(self, hide: bool) -> Self {
|
||||
pub fn hide_env_values(self, hide: bool) -> Self {
|
||||
if hide {
|
||||
self.set(ArgSettings::HideEnvValues)
|
||||
} else {
|
||||
|
@ -3638,9 +3712,34 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Indicates that all parameters passed after this should not be parsed
|
||||
/// individually, but rather passed in their entirety. It is worth noting
|
||||
/// that setting this requires all values to come after a `--` to indicate they
|
||||
/// should all be captured. For example:
|
||||
///
|
||||
/// ```notrust
|
||||
/// --foo something -- -v -v -v -b -b -b --baz -q -u -x
|
||||
/// ```
|
||||
/// Will result in everything after `--` to be considered one raw argument. This behavior
|
||||
/// may not be exactly what you are expecting and using [`AppSettings::TrailingVarArg`]
|
||||
/// may be more appropriate.
|
||||
///
|
||||
/// **NOTE:** Implicitly sets [`Arg::multiple(true)`], [`Arg::allow_hyphen_values(true)`], and
|
||||
/// [`Arg::last(true)`] when set to `true`
|
||||
///
|
||||
/// [`Arg::multiple(true)`]: ./struct.Arg.html#method.multiple
|
||||
/// [`Arg::allow_hyphen_values(true)`]: ./struct.Arg.html#method.allow_hyphen_values
|
||||
/// [`Arg::last(true)`]: ./struct.Arg.html#method.last
|
||||
/// [`AppSettings::TrailingVarArg`]: ./enum.AppSettings.html#variant.TrailingVarArg
|
||||
pub fn raw(self, raw: bool) -> Self {
|
||||
self.multiple(raw).allow_hyphen_values(raw).last(raw)
|
||||
}
|
||||
|
||||
/// Checks if one of the [`ArgSettings`] settings is set for the argument
|
||||
/// [`ArgSettings`]: ./enum.ArgSettings.html
|
||||
pub fn is_set(&self, s: ArgSettings) -> bool { self.b.is_set(s) }
|
||||
pub fn is_set(&self, s: ArgSettings) -> bool {
|
||||
self.b.is_set(s)
|
||||
}
|
||||
|
||||
/// Sets one of the [`ArgSettings`] settings for the argument
|
||||
/// [`ArgSettings`]: ./enum.ArgSettings.html
|
||||
|
@ -3657,10 +3756,14 @@ impl<'a, 'b> Arg<'a, 'b> {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn setb(&mut self, s: ArgSettings) { self.b.set(s); }
|
||||
pub fn setb(&mut self, s: ArgSettings) {
|
||||
self.b.set(s);
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn unsetb(&mut self, s: ArgSettings) { self.b.unset(s); }
|
||||
pub fn unsetb(&mut self, s: ArgSettings) {
|
||||
self.b.unset(s);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> {
|
||||
|
@ -3676,5 +3779,7 @@ impl<'a, 'b, 'z> From<&'z Arg<'a, 'b>> for Arg<'a, 'b> {
|
|||
}
|
||||
|
||||
impl<'n, 'e> PartialEq for Arg<'n, 'e> {
|
||||
fn eq(&self, other: &Arg<'n, 'e>) -> bool { self.b == other.b }
|
||||
fn eq(&self, other: &Arg<'n, 'e>) -> bool {
|
||||
self.b == other.b
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ impl<'n, 'e> AnyArg<'n, 'e> for FlagBuilder<'n, 'e> {
|
|||
self.b.requires.as_ref().map(|o| &o[..])
|
||||
}
|
||||
fn blacklist(&self) -> Option<&[&'e str]> { self.b.blacklist.as_ref().map(|o| &o[..]) }
|
||||
fn required_unless(&self) -> Option<&[&'e str]> { None }
|
||||
fn required_unless(&self) -> Option<&[&'e str]> { self.b.r_unless.as_ref().map(|o| &o[..]) }
|
||||
fn is_set(&self, s: ArgSettings) -> bool { self.b.settings.is_set(s) }
|
||||
fn has_switch(&self) -> bool { true }
|
||||
fn takes_value(&self) -> bool { false }
|
||||
|
|
|
@ -21,11 +21,67 @@ impl<'a> Default for ArgMatcher<'a> {
|
|||
impl<'a> ArgMatcher<'a> {
|
||||
pub fn new() -> Self { ArgMatcher::default() }
|
||||
|
||||
pub fn process_arg_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>, overrides: &mut Vec<(&'b str, &'a str)>, required: &mut Vec<&'a str>, check_all: bool) {
|
||||
debugln!("ArgMatcher::process_arg_overrides:{:?};", a.map_or(None, |a| Some(a.name())));
|
||||
if let Some(aa) = a {
|
||||
let mut self_done = false;
|
||||
if let Some(a_overrides) = aa.overrides() {
|
||||
for overr in a_overrides {
|
||||
debugln!("ArgMatcher::process_arg_overrides:iter:{};", overr);
|
||||
if overr == &aa.name() {
|
||||
self_done = true;
|
||||
self.handle_self_overrides(a);
|
||||
} else if self.is_present(overr) {
|
||||
debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing from matches;", overr);
|
||||
self.remove(overr);
|
||||
for i in (0 .. required.len()).rev() {
|
||||
if &required[i] == overr {
|
||||
debugln!("ArgMatcher::process_arg_overrides:iter:{}: removing required;", overr);
|
||||
required.swap_remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
overrides.push((overr, aa.name()));
|
||||
} else {
|
||||
overrides.push((overr, aa.name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
if check_all && !self_done {
|
||||
self.handle_self_overrides(a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_self_overrides<'b>(&mut self, a: Option<&AnyArg<'a, 'b>>) {
|
||||
debugln!("ArgMatcher::handle_self_overrides:{:?};", a.map_or(None, |a| Some(a.name())));
|
||||
if let Some(aa) = a {
|
||||
if !aa.has_switch() || aa.is_set(ArgSettings::Multiple) {
|
||||
// positional args can't override self or else we would never advance to the next
|
||||
|
||||
// Also flags with --multiple set are ignored otherwise we could never have more
|
||||
// than one
|
||||
return;
|
||||
}
|
||||
if let Some(ma) = self.get_mut(aa.name()) {
|
||||
if ma.vals.len() > 1 {
|
||||
// swap_remove(0) would be O(1) but does not preserve order, which
|
||||
// we need
|
||||
ma.vals.remove(0);
|
||||
ma.occurs = 1;
|
||||
} else if !aa.takes_value() && ma.occurs > 1 {
|
||||
ma.occurs = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_present(&self, name: &str) -> bool {
|
||||
self.0.is_present(name)
|
||||
}
|
||||
|
||||
pub fn propagate_globals(&mut self, global_arg_vec: &[&'a str]) {
|
||||
debugln!(
|
||||
"ArgMatcher::get_global_values: global_arg_vec={:?}",
|
||||
global_arg_vec
|
||||
);
|
||||
debugln!( "ArgMatcher::get_global_values: global_arg_vec={:?}", global_arg_vec );
|
||||
let mut vals_map = HashMap::new();
|
||||
self.fill_in_global_values(global_arg_vec, &mut vals_map);
|
||||
}
|
||||
|
@ -116,12 +172,21 @@ impl<'a> ArgMatcher<'a> {
|
|||
pub fn add_val_to(&mut self, arg: &'a str, val: &OsStr) {
|
||||
let ma = self.entry(arg).or_insert(MatchedArg {
|
||||
occurs: 0,
|
||||
indices: Vec::with_capacity(1),
|
||||
vals: Vec::with_capacity(1),
|
||||
});
|
||||
// let len = ma.vals.len() + 1;
|
||||
ma.vals.push(val.to_owned());
|
||||
}
|
||||
|
||||
pub fn add_index_to(&mut self, arg: &'a str, idx: usize) {
|
||||
let ma = self.entry(arg).or_insert(MatchedArg {
|
||||
occurs: 0,
|
||||
indices: Vec::with_capacity(1),
|
||||
vals: Vec::new(),
|
||||
});
|
||||
ma.indices.push(idx);
|
||||
}
|
||||
|
||||
pub fn needs_more_vals<'b, A>(&self, o: &A) -> bool
|
||||
where
|
||||
A: AnyArg<'a, 'b>,
|
||||
|
|
|
@ -364,6 +364,229 @@ impl<'a> ArgMatches<'a> {
|
|||
self.args.get(name.as_ref()).map_or(0, |a| a.occurs)
|
||||
}
|
||||
|
||||
/// Gets the starting index of the argument in respect to all other arguments. Indices are
|
||||
/// similar to argv indices, but are not exactly 1:1.
|
||||
///
|
||||
/// For flags (i.e. those arguments which don't have an associated value), indices refer
|
||||
/// to occurrence of the switch, such as `-f`, or `--flag`. However, for options the indices
|
||||
/// refer to the *values* `-o val` would therefore not represent two distinct indices, only the
|
||||
/// index for `val` would be recorded. This is by design.
|
||||
///
|
||||
/// Besides the flag/option descrepancy, the primary difference between an argv index and clap
|
||||
/// index, is that clap continues counting once all arguments have properly seperated, whereas
|
||||
/// an argv index does not.
|
||||
///
|
||||
/// The examples should clear this up.
|
||||
///
|
||||
/// *NOTE:* If an argument is allowed multiple times, this method will only give the *first*
|
||||
/// index.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The argv indices are listed in the comments below. See how they correspond to the clap
|
||||
/// indices. Note that if it's not listed in a clap index, this is becuase it's not saved in
|
||||
/// in an `ArgMatches` struct for querying.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("flag")
|
||||
/// .short("f"))
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true))
|
||||
/// .get_matches_from(vec!["myapp", "-f", "-o", "val"]);
|
||||
/// // ARGV idices: ^0 ^1 ^2 ^3
|
||||
/// // clap idices: ^1 ^3
|
||||
///
|
||||
/// assert_eq!(m.index_of("flag"), Some(1));
|
||||
/// assert_eq!(m.index_of("option"), Some(3));
|
||||
/// ```
|
||||
///
|
||||
/// Now notice, if we use one of the other styles of options:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("flag")
|
||||
/// .short("f"))
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true))
|
||||
/// .get_matches_from(vec!["myapp", "-f", "-o=val"]);
|
||||
/// // ARGV idices: ^0 ^1 ^2
|
||||
/// // clap idices: ^1 ^3
|
||||
///
|
||||
/// assert_eq!(m.index_of("flag"), Some(1));
|
||||
/// assert_eq!(m.index_of("option"), Some(3));
|
||||
/// ```
|
||||
///
|
||||
/// Things become much more complicated, or clear if we look at a more complex combination of
|
||||
/// flags. Let's also throw in the final option style for good measure.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("flag")
|
||||
/// .short("f"))
|
||||
/// .arg(Arg::with_name("flag2")
|
||||
/// .short("F"))
|
||||
/// .arg(Arg::with_name("flag3")
|
||||
/// .short("z"))
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true))
|
||||
/// .get_matches_from(vec!["myapp", "-fzF", "-oval"]);
|
||||
/// // ARGV idices: ^0 ^1 ^2
|
||||
/// // clap idices: ^1,2,3 ^5
|
||||
/// //
|
||||
/// // clap sees the above as 'myapp -f -z -F -o val'
|
||||
/// // ^0 ^1 ^2 ^3 ^4 ^5
|
||||
/// assert_eq!(m.index_of("flag"), Some(1));
|
||||
/// assert_eq!(m.index_of("flag2"), Some(3));
|
||||
/// assert_eq!(m.index_of("flag3"), Some(2));
|
||||
/// assert_eq!(m.index_of("option"), Some(5));
|
||||
/// ```
|
||||
///
|
||||
/// One final combination of flags/options to see how they combine:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("flag")
|
||||
/// .short("f"))
|
||||
/// .arg(Arg::with_name("flag2")
|
||||
/// .short("F"))
|
||||
/// .arg(Arg::with_name("flag3")
|
||||
/// .short("z"))
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true)
|
||||
/// .multiple(true))
|
||||
/// .get_matches_from(vec!["myapp", "-fzFoval"]);
|
||||
/// // ARGV idices: ^0 ^1
|
||||
/// // clap idices: ^1,2,3^5
|
||||
/// //
|
||||
/// // clap sees the above as 'myapp -f -z -F -o val'
|
||||
/// // ^0 ^1 ^2 ^3 ^4 ^5
|
||||
/// assert_eq!(m.index_of("flag"), Some(1));
|
||||
/// assert_eq!(m.index_of("flag2"), Some(3));
|
||||
/// assert_eq!(m.index_of("flag3"), Some(2));
|
||||
/// assert_eq!(m.index_of("option"), Some(5));
|
||||
/// ```
|
||||
///
|
||||
/// The last part to mention is when values are sent in multiple groups with a [delimiter].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true)
|
||||
/// .multiple(true))
|
||||
/// .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]);
|
||||
/// // ARGV idices: ^0 ^1
|
||||
/// // clap idices: ^2 ^3 ^4
|
||||
/// //
|
||||
/// // clap sees the above as 'myapp -o val1 val2 val3'
|
||||
/// // ^0 ^1 ^2 ^3 ^4
|
||||
/// assert_eq!(m.index_of("option"), Some(2));
|
||||
/// ```
|
||||
/// [`ArgMatches`]: ./struct.ArgMatches.html
|
||||
/// [delimiter]: ./struct.Arg.html#method.value_delimiter
|
||||
pub fn index_of<S: AsRef<str>>(&self, name: S) -> Option<usize> {
|
||||
if let Some(arg) = self.args.get(name.as_ref()) {
|
||||
if let Some(i) = arg.indices.get(0) {
|
||||
return Some(*i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets all indices of the argument in respect to all other arguments. Indices are
|
||||
/// similar to argv indices, but are not exactly 1:1.
|
||||
///
|
||||
/// For flags (i.e. those arguments which don't have an associated value), indices refer
|
||||
/// to occurrence of the switch, such as `-f`, or `--flag`. However, for options the indices
|
||||
/// refer to the *values* `-o val` would therefore not represent two distinct indices, only the
|
||||
/// index for `val` would be recorded. This is by design.
|
||||
///
|
||||
/// *NOTE:* For more information about how clap indices compare to argv indices, see
|
||||
/// [`ArgMatches::index_of`]
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true)
|
||||
/// .use_delimiter(true)
|
||||
/// .multiple(true))
|
||||
/// .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]);
|
||||
/// // ARGV idices: ^0 ^1
|
||||
/// // clap idices: ^2 ^3 ^4
|
||||
/// //
|
||||
/// // clap sees the above as 'myapp -o val1 val2 val3'
|
||||
/// // ^0 ^1 ^2 ^3 ^4
|
||||
/// assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2, 3, 4]);
|
||||
/// ```
|
||||
///
|
||||
/// Another quick example is when flags and options are used together
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true)
|
||||
/// .multiple(true))
|
||||
/// .arg(Arg::with_name("flag")
|
||||
/// .short("f")
|
||||
/// .multiple(true))
|
||||
/// .get_matches_from(vec!["myapp", "-o", "val1", "-f", "-o", "val2", "-f"]);
|
||||
/// // ARGV idices: ^0 ^1 ^2 ^3 ^4 ^5 ^6
|
||||
/// // clap idices: ^2 ^3 ^5 ^6
|
||||
///
|
||||
/// assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2, 5]);
|
||||
/// assert_eq!(m.indices_of("flag").unwrap().collect::<Vec<_>>(), &[3, 6]);
|
||||
/// ```
|
||||
///
|
||||
/// One final example, which is an odd case; if we *don't* use value delimiter as we did with
|
||||
/// the first example above instead of `val1`, `val2` and `val3` all being distinc values, they
|
||||
/// would all be a single value of `val1,val2,val3`, in which case case they'd only receive a
|
||||
/// single index.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("option")
|
||||
/// .short("o")
|
||||
/// .takes_value(true)
|
||||
/// .multiple(true))
|
||||
/// .get_matches_from(vec!["myapp", "-o=val1,val2,val3"]);
|
||||
/// // ARGV idices: ^0 ^1
|
||||
/// // clap idices: ^2
|
||||
/// //
|
||||
/// // clap sees the above as 'myapp -o "val1,val2,val3"'
|
||||
/// // ^0 ^1 ^2
|
||||
/// assert_eq!(m.indices_of("option").unwrap().collect::<Vec<_>>(), &[2]);
|
||||
/// ```
|
||||
/// [`ArgMatches`]: ./struct.ArgMatches.html
|
||||
/// [`ArgMatches::index_of`]: ./struct.ArgMatches.html#method.index_of
|
||||
/// [delimiter]: ./struct.Arg.html#method.value_delimiter
|
||||
pub fn indices_of<S: AsRef<str>>(&'a self, name: S) -> Option<Indices<'a>> {
|
||||
if let Some(arg) = self.args.get(name.as_ref()) {
|
||||
fn to_usize(i: &usize) -> usize { *i }
|
||||
let to_usize: fn(&usize) -> usize = to_usize; // coerce to fn pointer
|
||||
return Some(Indices {
|
||||
iter: arg.indices.iter().map(to_usize),
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Because [`Subcommand`]s are essentially "sub-[`App`]s" they have their own [`ArgMatches`]
|
||||
/// as well. This method returns the [`ArgMatches`] for a particular subcommand or `None` if
|
||||
/// the subcommand wasn't present at runtime.
|
||||
|
@ -551,10 +774,16 @@ impl<'a> ArgMatches<'a> {
|
|||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("output")
|
||||
/// .short("o")
|
||||
/// .multiple(true)
|
||||
/// .takes_value(true))
|
||||
/// .get_matches_from(vec!["myapp", "something"]);
|
||||
/// .get_matches_from(vec!["myapp", "-o", "val1", "val2"]);
|
||||
///
|
||||
/// assert_eq!(m.value_of("output"), Some("something"));
|
||||
/// let mut values = m.values_of("output").unwrap();
|
||||
///
|
||||
/// assert_eq!(values.next(), Some("val1"));
|
||||
/// assert_eq!(values.next(), Some("val2"));
|
||||
/// assert_eq!(values.next(), None);
|
||||
/// ```
|
||||
/// [`ArgMatches::values_of`]: ./struct.ArgMatches.html#method.values_of
|
||||
#[derive(Clone)]
|
||||
|
@ -588,19 +817,6 @@ impl<'a> Default for Values<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_values() {
|
||||
let mut values: Values = Values::default();
|
||||
assert_eq!(values.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_values_with_shorter_lifetime() {
|
||||
let matches = ArgMatches::new();
|
||||
let mut values = matches.values_of("").unwrap_or_default();
|
||||
assert_eq!(values.next(), None);
|
||||
}
|
||||
|
||||
/// An iterator for getting multiple values out of an argument via the [`ArgMatches::values_of_os`]
|
||||
/// method. Usage of this iterator allows values which contain invalid UTF-8 code points unlike
|
||||
/// [`Values`].
|
||||
|
@ -651,15 +867,98 @@ impl<'a> Default for OsValues<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_osvalues() {
|
||||
let mut values: OsValues = OsValues::default();
|
||||
assert_eq!(values.next(), None);
|
||||
/// An iterator for getting multiple indices out of an argument via the [`ArgMatches::indices_of`]
|
||||
/// method.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use clap::{App, Arg};
|
||||
/// let m = App::new("myapp")
|
||||
/// .arg(Arg::with_name("output")
|
||||
/// .short("o")
|
||||
/// .multiple(true)
|
||||
/// .takes_value(true))
|
||||
/// .get_matches_from(vec!["myapp", "-o", "val1", "val2"]);
|
||||
///
|
||||
/// let mut indices = m.indices_of("output").unwrap();
|
||||
///
|
||||
/// assert_eq!(indices.next(), Some(2));
|
||||
/// assert_eq!(indices.next(), Some(3));
|
||||
/// assert_eq!(indices.next(), None);
|
||||
/// ```
|
||||
/// [`ArgMatches::indices_of`]: ./struct.ArgMatches.html#method.indices_of
|
||||
#[derive(Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Indices<'a> { // would rather use '_, but: https://github.com/rust-lang/rust/issues/48469
|
||||
iter: Map<Iter<'a, usize>, fn(&'a usize) -> usize>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_osvalues_with_shorter_lifetime() {
|
||||
let matches = ArgMatches::new();
|
||||
let mut values = matches.values_of_os("").unwrap_or_default();
|
||||
assert_eq!(values.next(), None);
|
||||
impl<'a> Iterator for Indices<'a> {
|
||||
type Item = usize;
|
||||
|
||||
fn next(&mut self) -> Option<usize> { self.iter.next() }
|
||||
fn size_hint(&self) -> (usize, Option<usize>) { self.iter.size_hint() }
|
||||
}
|
||||
|
||||
impl<'a> DoubleEndedIterator for Indices<'a> {
|
||||
fn next_back(&mut self) -> Option<usize> { self.iter.next_back() }
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for Indices<'a> {}
|
||||
|
||||
/// Creates an empty iterator.
|
||||
impl<'a> Default for Indices<'a> {
|
||||
fn default() -> Self {
|
||||
static EMPTY: [usize; 0] = [];
|
||||
// This is never called because the iterator is empty:
|
||||
fn to_usize(_: &usize) -> usize { unreachable!() };
|
||||
Indices {
|
||||
iter: EMPTY[..].iter().map(to_usize),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_values() {
|
||||
let mut values: Values = Values::default();
|
||||
assert_eq!(values.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_values_with_shorter_lifetime() {
|
||||
let matches = ArgMatches::new();
|
||||
let mut values = matches.values_of("").unwrap_or_default();
|
||||
assert_eq!(values.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_osvalues() {
|
||||
let mut values: OsValues = OsValues::default();
|
||||
assert_eq!(values.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_osvalues_with_shorter_lifetime() {
|
||||
let matches = ArgMatches::new();
|
||||
let mut values = matches.values_of_os("").unwrap_or_default();
|
||||
assert_eq!(values.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_indices() {
|
||||
let mut indices: Indices = Indices::default();
|
||||
assert_eq!(indices.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_indices_with_shorter_lifetime() {
|
||||
let matches = ArgMatches::new();
|
||||
let mut indices = matches.indices_of("").unwrap_or_default();
|
||||
assert_eq!(indices.next(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::ffi::OsString;
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct MatchedArg {
|
||||
#[doc(hidden)] pub occurs: u64,
|
||||
#[doc(hidden)] pub indices: Vec<usize>,
|
||||
#[doc(hidden)] pub vals: Vec<OsString>,
|
||||
}
|
||||
|
||||
|
@ -12,7 +13,8 @@ impl Default for MatchedArg {
|
|||
fn default() -> Self {
|
||||
MatchedArg {
|
||||
occurs: 1,
|
||||
vals: Vec::with_capacity(1),
|
||||
indices: Vec::new(),
|
||||
vals: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ impl Default for ArgFlags {
|
|||
|
||||
/// Various settings that apply to arguments and may be set, unset, and checked via getter/setter
|
||||
/// methods [`Arg::set`], [`Arg::unset`], and [`Arg::is_set`]
|
||||
///
|
||||
/// [`Arg::set`]: ./struct.Arg.html#method.set
|
||||
/// [`Arg::unset`]: ./struct.Arg.html#method.unset
|
||||
/// [`Arg::is_set`]: ./struct.Arg.html#method.is_set
|
||||
|
@ -79,7 +80,7 @@ pub enum ArgSettings {
|
|||
Hidden,
|
||||
/// The argument accepts a value, such as `--option <value>`
|
||||
TakesValue,
|
||||
/// Determines if the argument allows values to be grouped via a delimter
|
||||
/// Determines if the argument allows values to be grouped via a delimiter
|
||||
UseValueDelimiter,
|
||||
/// Prints the help text on the line after the argument
|
||||
NextLineHelp,
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::io::Write;
|
|||
|
||||
// Internal
|
||||
use app::parser::Parser;
|
||||
use args::{ArgSettings, OptBuilder};
|
||||
use args::OptBuilder;
|
||||
use completions;
|
||||
|
||||
pub struct BashGen<'a, 'b>
|
||||
|
@ -81,10 +81,11 @@ complete -F _{name} -o bashdefault -o default {name}
|
|||
subcmds = format!(
|
||||
"{}
|
||||
{name})
|
||||
cmd+=\"__{name}\"
|
||||
cmd+=\"__{fn_name}\"
|
||||
;;",
|
||||
subcmds,
|
||||
name = sc.replace("-", "__")
|
||||
name = sc,
|
||||
fn_name = sc.replace("-", "__")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -167,49 +168,13 @@ complete -F _{name} -o bashdefault -o default {name}
|
|||
fn vals_for(&self, o: &OptBuilder) -> String {
|
||||
debugln!("BashGen::vals_for: o={}", o.b.name);
|
||||
use args::AnyArg;
|
||||
let mut ret = String::new();
|
||||
let mut needs_quotes = true;
|
||||
if let Some(vals) = o.possible_vals() {
|
||||
needs_quotes = false;
|
||||
ret = format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" "));
|
||||
} else if let Some(vec) = o.val_names() {
|
||||
let mut it = vec.iter().peekable();
|
||||
while let Some((_, val)) = it.next() {
|
||||
ret = format!(
|
||||
"{}<{}>{}",
|
||||
ret,
|
||||
val,
|
||||
if it.peek().is_some() { " " } else { "" }
|
||||
);
|
||||
}
|
||||
let num = vec.len();
|
||||
if o.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
ret = format!("{}...", ret);
|
||||
}
|
||||
} else if let Some(num) = o.num_vals() {
|
||||
let mut it = (0..num).peekable();
|
||||
while let Some(_) = it.next() {
|
||||
ret = format!(
|
||||
"{}<{}>{}",
|
||||
ret,
|
||||
o.name(),
|
||||
if it.peek().is_some() { " " } else { "" }
|
||||
);
|
||||
}
|
||||
if o.is_set(ArgSettings::Multiple) && num == 1 {
|
||||
ret = format!("{}...", ret);
|
||||
}
|
||||
format!("$(compgen -W \"{}\" -- ${{cur}})", vals.join(" "))
|
||||
} else {
|
||||
ret = format!("<{}>", o.name());
|
||||
if o.is_set(ArgSettings::Multiple) {
|
||||
ret = format!("{}...", ret);
|
||||
}
|
||||
String::from("$(compgen -f ${cur})")
|
||||
}
|
||||
if needs_quotes {
|
||||
ret = format!("\"{}\"", ret);
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
fn all_options_for_path(&self, path: &str) -> String {
|
||||
debugln!("BashGen::all_options_for_path: path={}", path);
|
||||
let mut p = self.p;
|
||||
|
|
|
@ -16,25 +16,8 @@ impl<'a, 'b> FishGen<'a, 'b> {
|
|||
|
||||
pub fn generate_to<W: Write>(&self, buf: &mut W) {
|
||||
let command = self.p.meta.bin_name.as_ref().unwrap();
|
||||
|
||||
// function to detect subcommand
|
||||
let detect_subcommand_function = r#"function __fish_using_command
|
||||
set cmd (commandline -opc)
|
||||
if [ (count $cmd) -eq (count $argv) ]
|
||||
for i in (seq (count $argv))
|
||||
if [ $cmd[$i] != $argv[$i] ]
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
return 1
|
||||
end
|
||||
|
||||
"#.to_string();
|
||||
|
||||
let mut buffer = detect_subcommand_function;
|
||||
gen_fish_inner(command, self, &command.to_string(), &mut buffer);
|
||||
let mut buffer = String::new();
|
||||
gen_fish_inner(command, self, command, &mut buffer);
|
||||
w!(buf, buffer.as_bytes());
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +25,7 @@ end
|
|||
// Escape string inside single quotes
|
||||
fn escape_string(string: &str) -> String { string.replace("\\", "\\\\").replace("'", "\\'") }
|
||||
|
||||
fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, parent_cmds: &str, buffer: &mut String) {
|
||||
fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, subcommand: &str, buffer: &mut String) {
|
||||
debugln!("FishGen::gen_fish_inner;");
|
||||
// example :
|
||||
//
|
||||
|
@ -54,13 +37,15 @@ fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, parent_cmds: &str, buf
|
|||
// -a "{possible_arguments}"
|
||||
// -r # if require parameter
|
||||
// -f # don't use file completion
|
||||
// -n "__fish_using_command myprog subcmd1" # complete for command "myprog subcmd1"
|
||||
// -n "__fish_use_subcommand" # complete for command "myprog"
|
||||
// -n "__fish_seen_subcommand_from subcmd1" # complete for command "myprog subcmd1"
|
||||
|
||||
let basic_template = format!(
|
||||
"complete -c {} -n \"__fish_using_command {}\"",
|
||||
root_command,
|
||||
parent_cmds
|
||||
);
|
||||
let mut basic_template = format!("complete -c {} -n ", root_command);
|
||||
if root_command == subcommand {
|
||||
basic_template.push_str("\"__fish_use_subcommand\"");
|
||||
} else {
|
||||
basic_template.push_str(format!("\"__fish_seen_subcommand_from {}\"", subcommand).as_str());
|
||||
}
|
||||
|
||||
for option in comp_gen.p.opts() {
|
||||
let mut template = basic_template.clone();
|
||||
|
@ -109,12 +94,6 @@ fn gen_fish_inner(root_command: &str, comp_gen: &FishGen, parent_cmds: &str, buf
|
|||
// generate options of subcommands
|
||||
for subcommand in &comp_gen.p.subcommands {
|
||||
let sub_comp_gen = FishGen::new(&subcommand.p);
|
||||
// make new "parent_cmds" for different subcommands
|
||||
let mut sub_parent_cmds = parent_cmds.to_string();
|
||||
if !sub_parent_cmds.is_empty() {
|
||||
sub_parent_cmds.push_str(" ");
|
||||
}
|
||||
sub_parent_cmds.push_str(&subcommand.p.meta.name);
|
||||
gen_fish_inner(root_command, &sub_comp_gen, &sub_parent_cmds, buffer);
|
||||
gen_fish_inner(root_command, &sub_comp_gen, &subcommand.to_string(), buffer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,12 +91,7 @@ pub fn subcommands_of(p: &Parser) -> Vec<(String, String)> {
|
|||
p.has_subcommands()
|
||||
);
|
||||
if !p.has_subcommands() {
|
||||
let mut ret = vec![
|
||||
(
|
||||
p.meta.name.clone(),
|
||||
p.meta.bin_name.as_ref().unwrap().clone(),
|
||||
),
|
||||
];
|
||||
let mut ret = vec![];
|
||||
debugln!("subcommands_of: Looking for aliases...");
|
||||
if let Some(ref aliases) = p.meta.aliases {
|
||||
for &(n, _) in aliases {
|
||||
|
|
|
@ -19,55 +19,37 @@ impl<'a, 'b> PowerShellGen<'a, 'b> {
|
|||
let bin_name = self.p.meta.bin_name.as_ref().unwrap();
|
||||
|
||||
let mut names = vec![];
|
||||
let (subcommands_detection_cases, subcommands_cases) =
|
||||
let subcommands_cases =
|
||||
generate_inner(self.p, "", &mut names);
|
||||
|
||||
let mut bin_names = vec![bin_name.to_string(), format!("./{0}", bin_name)];
|
||||
if cfg!(windows) {
|
||||
bin_names.push(format!("{0}.exe", bin_name));
|
||||
bin_names.push(format!(r".\{0}", bin_name));
|
||||
bin_names.push(format!(r".\{0}.exe", bin_name));
|
||||
bin_names.push(format!(r"./{0}.exe", bin_name));
|
||||
}
|
||||
|
||||
let bin_names = bin_names.iter().fold(String::new(), |previous, current| {
|
||||
format!("{0}, '{1}'", previous, current)
|
||||
});
|
||||
let bin_names = bin_names.trim_left_matches(", ");
|
||||
|
||||
let result = format!(r#"
|
||||
@({bin_names}) | %{{
|
||||
Register-ArgumentCompleter -Native -CommandName $_ -ScriptBlock {{
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
using namespace System.Management.Automation
|
||||
using namespace System.Management.Automation.Language
|
||||
|
||||
$command = '_{bin_name}'
|
||||
$commandAst.CommandElements |
|
||||
Select-Object -Skip 1 |
|
||||
%{{
|
||||
switch ($_.ToString()) {{
|
||||
{subcommands_detection_cases}
|
||||
default {{
|
||||
break
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
Register-ArgumentCompleter -Native -CommandName '{bin_name}' -ScriptBlock {{
|
||||
param($wordToComplete, $commandAst, $cursorPosition)
|
||||
|
||||
$completions = @()
|
||||
|
||||
switch ($command) {{
|
||||
{subcommands_cases}
|
||||
$commandElements = $commandAst.CommandElements
|
||||
$command = @(
|
||||
'{bin_name}'
|
||||
for ($i = 1; $i -lt $commandElements.Count; $i++) {{
|
||||
$element = $commandElements[$i]
|
||||
if ($element -isnot [StringConstantExpressionAst] -or
|
||||
$element.StringConstantType -ne [StringConstantType]::BareWord -or
|
||||
$element.Value.StartsWith('-')) {{
|
||||
break
|
||||
}}
|
||||
$element.Value
|
||||
}}) -join ';'
|
||||
|
||||
$completions |
|
||||
?{{ $_ -like "$wordToComplete*" }} |
|
||||
Sort-Object |
|
||||
%{{ New-Object System.Management.Automation.CompletionResult $_, $_, 'ParameterValue', $_ }}
|
||||
}}
|
||||
$completions = @(switch ($command) {{{subcommands_cases}
|
||||
}})
|
||||
|
||||
$completions.Where{{ $_.CompletionText -like "$wordToComplete*" }} |
|
||||
Sort-Object -Property ListItemText
|
||||
}}
|
||||
"#,
|
||||
bin_names = bin_names,
|
||||
bin_name = bin_name,
|
||||
subcommands_detection_cases = subcommands_detection_cases,
|
||||
subcommands_cases = subcommands_cases
|
||||
);
|
||||
|
||||
|
@ -75,64 +57,83 @@ impl<'a, 'b> PowerShellGen<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
// Escape string inside single quotes
|
||||
fn escape_string(string: &str) -> String { string.replace("'", "''") }
|
||||
|
||||
fn get_tooltip<T : ToString>(help: Option<&str>, data: T) -> String {
|
||||
match help {
|
||||
Some(help) => escape_string(&help),
|
||||
_ => data.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_inner<'a, 'b, 'p>(
|
||||
p: &'p Parser<'a, 'b>,
|
||||
previous_command_name: &str,
|
||||
names: &mut Vec<&'p str>,
|
||||
) -> (String, String) {
|
||||
) -> String {
|
||||
debugln!("PowerShellGen::generate_inner;");
|
||||
let command_name = if previous_command_name.is_empty() {
|
||||
format!(
|
||||
"{}_{}",
|
||||
previous_command_name,
|
||||
&p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG)
|
||||
)
|
||||
p.meta.bin_name.as_ref().expect(INTERNAL_ERROR_MSG).clone()
|
||||
} else {
|
||||
format!("{}_{}", previous_command_name, &p.meta.name)
|
||||
};
|
||||
|
||||
let mut subcommands_detection_cases = if !names.contains(&&*p.meta.name) {
|
||||
names.push(&*p.meta.name);
|
||||
format!(
|
||||
r"
|
||||
'{0}' {{
|
||||
$command += '_{0}'
|
||||
break
|
||||
}}
|
||||
",
|
||||
&p.meta.name
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
format!("{};{}", previous_command_name, &p.meta.name)
|
||||
};
|
||||
|
||||
let mut completions = String::new();
|
||||
let preamble = String::from("\n [CompletionResult]::new(");
|
||||
|
||||
for option in p.opts() {
|
||||
if let Some(data) = option.s.short {
|
||||
let tooltip = get_tooltip(option.b.help, data);
|
||||
completions.push_str(&preamble);
|
||||
completions.push_str(format!("'-{}', '{}', {}, '{}')",
|
||||
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
|
||||
}
|
||||
if let Some(data) = option.s.long {
|
||||
let tooltip = get_tooltip(option.b.help, data);
|
||||
completions.push_str(&preamble);
|
||||
completions.push_str(format!("'--{}', '{}', {}, '{}')",
|
||||
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for flag in p.flags() {
|
||||
if let Some(data) = flag.s.short {
|
||||
let tooltip = get_tooltip(flag.b.help, data);
|
||||
completions.push_str(&preamble);
|
||||
completions.push_str(format!("'-{}', '{}', {}, '{}')",
|
||||
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
|
||||
}
|
||||
if let Some(data) = flag.s.long {
|
||||
let tooltip = get_tooltip(flag.b.help, data);
|
||||
completions.push_str(&preamble);
|
||||
completions.push_str(format!("'--{}', '{}', {}, '{}')",
|
||||
data, data, "[CompletionResultType]::ParameterName", tooltip).as_str());
|
||||
}
|
||||
}
|
||||
|
||||
for subcommand in &p.subcommands {
|
||||
completions.push_str(&format!("'{}', ", &subcommand.p.meta.name));
|
||||
}
|
||||
for short in shorts!(p) {
|
||||
completions.push_str(&format!("'-{}', ", short));
|
||||
}
|
||||
for long in longs!(p) {
|
||||
completions.push_str(&format!("'--{}', ", long));
|
||||
let data = &subcommand.p.meta.name;
|
||||
let tooltip = get_tooltip(subcommand.p.meta.about, data);
|
||||
completions.push_str(&preamble);
|
||||
completions.push_str(format!("'{}', '{}', {}, '{}')",
|
||||
data, data, "[CompletionResultType]::ParameterValue", tooltip).as_str());
|
||||
}
|
||||
|
||||
let mut subcommands_cases = format!(
|
||||
r"
|
||||
'{}' {{
|
||||
$completions = @({})
|
||||
}}
|
||||
",
|
||||
'{}' {{{}
|
||||
break
|
||||
}}",
|
||||
&command_name,
|
||||
completions.trim_right_matches(", ")
|
||||
completions
|
||||
);
|
||||
|
||||
for subcommand in &p.subcommands {
|
||||
let (subcommand_subcommands_detection_cases, subcommand_subcommands_cases) =
|
||||
let subcommand_subcommands_cases =
|
||||
generate_inner(&subcommand.p, &command_name, names);
|
||||
subcommands_detection_cases.push_str(&subcommand_subcommands_detection_cases);
|
||||
subcommands_cases.push_str(&subcommand_subcommands_cases);
|
||||
}
|
||||
|
||||
(subcommands_detection_cases, subcommands_cases)
|
||||
subcommands_cases
|
||||
}
|
||||
|
|
|
@ -31,10 +31,19 @@ impl<'a, 'b> ZshGen<'a, 'b> {
|
|||
"\
|
||||
#compdef {name}
|
||||
|
||||
autoload -U is-at-least
|
||||
|
||||
_{name}() {{
|
||||
typeset -A opt_args
|
||||
typeset -a _arguments_options
|
||||
local ret=1
|
||||
|
||||
if is-at-least 5.2; then
|
||||
_arguments_options=(-s -S -C)
|
||||
else
|
||||
_arguments_options=(-s -C)
|
||||
fi
|
||||
|
||||
local context curcontext=\"$curcontext\" state line
|
||||
{initial_args}
|
||||
{subcommands}
|
||||
|
@ -52,7 +61,7 @@ _{name} \"$@\"",
|
|||
}
|
||||
}
|
||||
|
||||
// Displays the positional args and commands of a subcommand
|
||||
// Displays the commands of a subcommand
|
||||
// (( $+functions[_[bin_name_underscore]_commands] )) ||
|
||||
// _[bin_name_underscore]_commands() {
|
||||
// local commands; commands=(
|
||||
|
@ -63,8 +72,8 @@ _{name} \"$@\"",
|
|||
// Where the following variables are present:
|
||||
// [bin_name_underscore]: The full space deliniated bin_name, where spaces have been replaced by
|
||||
// underscore characters
|
||||
// [arg_name]: The name of the positional arg or subcommand
|
||||
// [arg_help]: The help message of the arg or subcommand
|
||||
// [arg_name]: The name of the subcommand
|
||||
// [arg_help]: The help message of the subcommand
|
||||
// [bin_name]: The full space deliniated bin_name
|
||||
//
|
||||
// Here's a snippet from rustup:
|
||||
|
@ -94,7 +103,7 @@ _{bin_name_underscore}_commands() {{
|
|||
}}",
|
||||
bin_name_underscore = p.meta.bin_name.as_ref().unwrap().replace(" ", "__"),
|
||||
bin_name = p.meta.bin_name.as_ref().unwrap(),
|
||||
subcommands_and_args = subcommands_and_args_of(p)
|
||||
subcommands_and_args = subcommands_of(p)
|
||||
),
|
||||
];
|
||||
|
||||
|
@ -115,26 +124,26 @@ _{bin_name_underscore}_commands() {{
|
|||
}}",
|
||||
bin_name_underscore = bin_name.replace(" ", "__"),
|
||||
bin_name = bin_name,
|
||||
subcommands_and_args = subcommands_and_args_of(parser_of(p, bin_name))
|
||||
subcommands_and_args = subcommands_of(parser_of(p, bin_name))
|
||||
));
|
||||
}
|
||||
|
||||
ret.join("\n")
|
||||
}
|
||||
|
||||
// Generates subcommand and positional argument completions in form of
|
||||
// Generates subcommand completions in form of
|
||||
//
|
||||
// '[arg_name]:[arg_help]'
|
||||
//
|
||||
// Where:
|
||||
// [arg_name]: the argument or subcommand's name
|
||||
// [arg_help]: the help message of the argument or subcommand
|
||||
// [arg_name]: the subcommand's name
|
||||
// [arg_help]: the help message of the subcommand
|
||||
//
|
||||
// A snippet from rustup:
|
||||
// 'show:Show the active and installed toolchains'
|
||||
// 'update:Update Rust toolchains'
|
||||
fn subcommands_and_args_of(p: &Parser) -> String {
|
||||
debugln!("ZshGen::subcommands_and_args_of;");
|
||||
fn subcommands_of(p: &Parser) -> String {
|
||||
debugln!("ZshGen::subcommands_of;");
|
||||
let mut ret = vec![];
|
||||
fn add_sc(sc: &App, n: &str, ret: &mut Vec<String>) {
|
||||
debugln!("ZshGen::add_sc;");
|
||||
|
@ -153,10 +162,10 @@ fn subcommands_and_args_of(p: &Parser) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// First the subcommands
|
||||
// The subcommands
|
||||
for sc in p.subcommands() {
|
||||
debugln!(
|
||||
"ZshGen::subcommands_and_args_of:iter: subcommand={}",
|
||||
"ZshGen::subcommands_of:iter: subcommand={}",
|
||||
sc.p.meta.name
|
||||
);
|
||||
add_sc(sc, &sc.p.meta.name, &mut ret);
|
||||
|
@ -167,24 +176,6 @@ fn subcommands_and_args_of(p: &Parser) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
// Then the positional args
|
||||
for arg in p.positionals() {
|
||||
debugln!("ZshGen::subcommands_and_args_of:iter: arg={}", arg.b.name);
|
||||
let a = format!(
|
||||
"\"{name}:{help}\" \\",
|
||||
name = arg.b.name.to_ascii_uppercase(),
|
||||
help = arg.b
|
||||
.help
|
||||
.unwrap_or("")
|
||||
.replace("[", "\\[")
|
||||
.replace("]", "\\]")
|
||||
);
|
||||
|
||||
if !a.is_empty() {
|
||||
ret.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
ret.join("\n")
|
||||
}
|
||||
|
||||
|
@ -248,15 +239,18 @@ fn get_subcommands_of(p: &Parser) -> String {
|
|||
format!(
|
||||
"case $state in
|
||||
({name})
|
||||
curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$words[1]:\"
|
||||
case $line[1] in
|
||||
words=($line[{pos}] \"${{words[@]}}\")
|
||||
(( CURRENT += 1 ))
|
||||
curcontext=\"${{curcontext%:*:*}}:{name_hyphen}-command-$line[{pos}]:\"
|
||||
case $line[{pos}] in
|
||||
{subcommands}
|
||||
esac
|
||||
;;
|
||||
esac",
|
||||
name = p.meta.name,
|
||||
name_hyphen = p.meta.bin_name.as_ref().unwrap().replace(" ", "-"),
|
||||
subcommands = subcmds.join("\n")
|
||||
subcommands = subcmds.join("\n"),
|
||||
pos = p.positionals().len() + 1
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -268,8 +262,8 @@ fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
|
|||
&p.find_subcommand(sc).expect(INTERNAL_ERROR_MSG).p
|
||||
}
|
||||
|
||||
// Writes out the args section, which ends up being the flags and opts, and a jump to
|
||||
// another ZSH function if there are positional args or subcommands.
|
||||
// Writes out the args section, which ends up being the flags, opts and postionals, and a jump to
|
||||
// another ZSH function if there are subcommands.
|
||||
// The structer works like this:
|
||||
// ([conflicting_args]) [multiple] arg [takes_value] [[help]] [: :(possible_values)]
|
||||
// ^-- list '-v -h' ^--'*' ^--'+' ^-- list 'one two three'
|
||||
|
@ -280,8 +274,8 @@ fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
|
|||
// '(-h --help --verbose)-v[Enable verbose output]' \
|
||||
// '(-V -v --version --verbose --help)-h[Prints help information]' \
|
||||
// # ... snip for brevity
|
||||
// '1:: :_rustup_commands' \ # <-- displays positional args and subcommands
|
||||
// '*:: :->rustup' \ # <-- displays subcommand args and child subcommands
|
||||
// ':: :_rustup_commands' \ # <-- displays subcommands
|
||||
// '*::: :->rustup' \ # <-- displays subcommand args and child subcommands
|
||||
// && ret=0
|
||||
//
|
||||
// The args used for _arguments are as follows:
|
||||
|
@ -290,19 +284,20 @@ fn parser_of<'a, 'b>(p: &'b Parser<'a, 'b>, sc: &str) -> &'b Parser<'a, 'b> {
|
|||
// -S: Do not complete anything after '--' and treat those as argument values
|
||||
fn get_args_of(p: &Parser) -> String {
|
||||
debugln!("get_args_of;");
|
||||
let mut ret = vec![String::from("_arguments -s -S -C \\")];
|
||||
let mut ret = vec![String::from("_arguments \"${_arguments_options[@]}\" \\")];
|
||||
let opts = write_opts_of(p);
|
||||
let flags = write_flags_of(p);
|
||||
let sc_or_a = if p.has_subcommands() || p.has_positionals() {
|
||||
let positionals = write_positionals_of(p);
|
||||
let sc_or_a = if p.has_subcommands() {
|
||||
format!(
|
||||
"\"1:: :_{name}_commands\" \\",
|
||||
"\":: :_{name}_commands\" \\",
|
||||
name = p.meta.bin_name.as_ref().unwrap().replace(" ", "__")
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
let sc = if p.has_subcommands() {
|
||||
format!("\"*:: :->{name}\" \\", name = p.meta.name)
|
||||
format!("\"*::: :->{name}\" \\", name = p.meta.name)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
@ -313,6 +308,9 @@ fn get_args_of(p: &Parser) -> String {
|
|||
if !flags.is_empty() {
|
||||
ret.push(flags);
|
||||
}
|
||||
if !positionals.is_empty() {
|
||||
ret.push(positionals);
|
||||
}
|
||||
if !sc_or_a.is_empty() {
|
||||
ret.push(sc_or_a);
|
||||
}
|
||||
|
@ -324,8 +322,8 @@ fn get_args_of(p: &Parser) -> String {
|
|||
ret.join("\n")
|
||||
}
|
||||
|
||||
// Escape string inside single quotes and brackets
|
||||
fn escape_string(string: &str) -> String {
|
||||
// Escape help string inside single quotes and brackets
|
||||
fn escape_help(string: &str) -> String {
|
||||
string
|
||||
.replace("\\", "\\\\")
|
||||
.replace("'", "'\\''")
|
||||
|
@ -333,12 +331,22 @@ fn escape_string(string: &str) -> String {
|
|||
.replace("]", "\\]")
|
||||
}
|
||||
|
||||
// Escape value string inside single quotes and parentheses
|
||||
fn escape_value(string: &str) -> String {
|
||||
string
|
||||
.replace("\\", "\\\\")
|
||||
.replace("'", "'\\''")
|
||||
.replace("(", "\\(")
|
||||
.replace(")", "\\)")
|
||||
.replace(" ", "\\ ")
|
||||
}
|
||||
|
||||
fn write_opts_of(p: &Parser) -> String {
|
||||
debugln!("write_opts_of;");
|
||||
let mut ret = vec![];
|
||||
for o in p.opts() {
|
||||
debugln!("write_opts_of:iter: o={}", o.name());
|
||||
let help = o.help().map_or(String::new(), escape_string);
|
||||
let help = o.help().map_or(String::new(), escape_help);
|
||||
let mut conflicts = get_zsh_arg_conflicts!(p, o, INTERNAL_ERROR_MSG);
|
||||
conflicts = if conflicts.is_empty() {
|
||||
String::new()
|
||||
|
@ -352,7 +360,8 @@ fn write_opts_of(p: &Parser) -> String {
|
|||
""
|
||||
};
|
||||
let pv = if let Some(pv_vec) = o.possible_vals() {
|
||||
format!(": :({})", pv_vec.join(" "))
|
||||
format!(": :({})", pv_vec.iter().map(
|
||||
|v| escape_value(*v)).collect::<Vec<String>>().join(" "))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
@ -371,7 +380,7 @@ fn write_opts_of(p: &Parser) -> String {
|
|||
}
|
||||
if let Some(long) = o.long() {
|
||||
let l = format!(
|
||||
"'{conflicts}{multiple}--{arg}+[{help}]{possible_values}' \\",
|
||||
"'{conflicts}{multiple}--{arg}=[{help}]{possible_values}' \\",
|
||||
conflicts = conflicts,
|
||||
multiple = multiple,
|
||||
arg = long,
|
||||
|
@ -392,7 +401,7 @@ fn write_flags_of(p: &Parser) -> String {
|
|||
let mut ret = vec![];
|
||||
for f in p.flags() {
|
||||
debugln!("write_flags_of:iter: f={}", f.name());
|
||||
let help = f.help().map_or(String::new(), escape_string);
|
||||
let help = f.help().map_or(String::new(), escape_help);
|
||||
let mut conflicts = get_zsh_arg_conflicts!(p, f, INTERNAL_ERROR_MSG);
|
||||
conflicts = if conflicts.is_empty() {
|
||||
String::new()
|
||||
|
@ -434,3 +443,30 @@ fn write_flags_of(p: &Parser) -> String {
|
|||
|
||||
ret.join("\n")
|
||||
}
|
||||
|
||||
fn write_positionals_of(p: &Parser) -> String {
|
||||
debugln!("write_positionals_of;");
|
||||
let mut ret = vec![];
|
||||
for arg in p.positionals() {
|
||||
debugln!("write_positionals_of:iter: arg={}", arg.b.name);
|
||||
let a = format!(
|
||||
"'{optional}:{name}{help}:{action}' \\",
|
||||
optional = if !arg.b.is_set(ArgSettings::Required) { ":" } else { "" },
|
||||
name = arg.b.name,
|
||||
help = arg.b
|
||||
.help
|
||||
.map_or("".to_owned(), |v| " -- ".to_owned() + v)
|
||||
.replace("[", "\\[")
|
||||
.replace("]", "\\]"),
|
||||
action = arg.possible_vals().map_or("_files".to_owned(), |values| {
|
||||
format!("({})",
|
||||
values.iter().map(|v| escape_value(*v)).collect::<Vec<String>>().join(" "))
|
||||
})
|
||||
);
|
||||
|
||||
debugln!("write_positionals_of:iter: Wrote...{}", a);
|
||||
ret.push(a);
|
||||
}
|
||||
|
||||
ret.join("\n")
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@ use std::process;
|
|||
use std::result::Result as StdResult;
|
||||
|
||||
// Internal
|
||||
use args::{AnyArg, FlagBuilder};
|
||||
use args::AnyArg;
|
||||
use fmt::{ColorWhen, Colorizer, ColorizerOption};
|
||||
use suggestions;
|
||||
|
||||
/// Short hand for [`Result`] type
|
||||
///
|
||||
/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
|
||||
pub type Result<T> = StdResult<T, Error>;
|
||||
|
||||
|
@ -77,7 +78,7 @@ pub enum ErrorKind {
|
|||
|
||||
/// Occurs when the user provides an unrecognized [`SubCommand`] which either
|
||||
/// doesn't meet the threshold for being similar enough to an existing subcommand,
|
||||
/// or the 'sggestions' feature is disabled.
|
||||
/// or the 'suggestions' feature is disabled.
|
||||
/// Otherwise the more detailed [`InvalidSubcommand`] error is returned.
|
||||
///
|
||||
/// This error typically happens when passing additional subcommand names to the `help`
|
||||
|
@ -294,7 +295,7 @@ pub enum ErrorKind {
|
|||
/// Occurs when the user provides a value containing invalid UTF-8 for an argument and
|
||||
/// [`AppSettings::StrictUtf8`] is set.
|
||||
///
|
||||
/// # Platform Speicific
|
||||
/// # Platform Specific
|
||||
///
|
||||
/// Non-Windows platforms only (such as Linux, Unix, OSX, etc.)
|
||||
///
|
||||
|
@ -372,7 +373,7 @@ pub enum ErrorKind {
|
|||
/// Command Line Argument Parser Error
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
/// Formated error message
|
||||
/// Formatted error message
|
||||
pub message: String,
|
||||
/// The type of error
|
||||
pub kind: ErrorKind,
|
||||
|
@ -404,14 +405,13 @@ impl Error {
|
|||
pub fn write_to<W: Write>(&self, w: &mut W) -> io::Result<()> { write!(w, "{}", self.message) }
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn argument_conflict<'a, 'b, A, O, U>(
|
||||
arg: &A,
|
||||
pub fn argument_conflict<'a, 'b, O, U>(
|
||||
arg: &AnyArg,
|
||||
other: Option<O>,
|
||||
usage: U,
|
||||
color: ColorWhen,
|
||||
) -> Self
|
||||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
O: Into<String>,
|
||||
U: Display,
|
||||
{
|
||||
|
@ -444,9 +444,8 @@ impl Error {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn empty_value<'a, 'b, A, U>(arg: &A, usage: U, color: ColorWhen) -> Self
|
||||
pub fn empty_value<'a, 'b, U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self
|
||||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
U: Display,
|
||||
{
|
||||
let c = Colorizer::new(ColorizerOption {
|
||||
|
@ -470,17 +469,16 @@ impl Error {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn invalid_value<'a, 'b, B, G, A, U>(
|
||||
pub fn invalid_value<'a, 'b, B, G, U>(
|
||||
bad_val: B,
|
||||
good_vals: &[G],
|
||||
arg: &A,
|
||||
arg: &AnyArg,
|
||||
usage: U,
|
||||
color: ColorWhen,
|
||||
) -> Self
|
||||
where
|
||||
B: AsRef<str>,
|
||||
G: AsRef<str> + Display,
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
U: Display,
|
||||
{
|
||||
let c = Colorizer::new(ColorizerOption {
|
||||
|
@ -499,7 +497,7 @@ impl Error {
|
|||
Error {
|
||||
message: format!(
|
||||
"{} '{}' isn't a valid value for '{}'\n\t\
|
||||
[values: {}]\n\
|
||||
[possible values: {}]\n\
|
||||
{}\n\n\
|
||||
{}\n\n\
|
||||
For more information try {}",
|
||||
|
@ -660,10 +658,9 @@ impl Error {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn too_many_values<'a, 'b, V, A, U>(val: V, arg: &A, usage: U, color: ColorWhen) -> Self
|
||||
pub fn too_many_values<'a, 'b, V, U>(val: V, arg: &AnyArg, usage: U, color: ColorWhen) -> Self
|
||||
where
|
||||
V: AsRef<str> + Display + ToOwned,
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
U: Display,
|
||||
{
|
||||
let v = val.as_ref();
|
||||
|
@ -689,15 +686,14 @@ impl Error {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn too_few_values<'a, 'b, A, U>(
|
||||
arg: &A,
|
||||
pub fn too_few_values<'a, 'b, U>(
|
||||
arg: &AnyArg,
|
||||
min_vals: u64,
|
||||
curr_vals: usize,
|
||||
usage: U,
|
||||
color: ColorWhen,
|
||||
) -> Self
|
||||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
U: Display,
|
||||
{
|
||||
let c = Colorizer::new(ColorizerOption {
|
||||
|
@ -724,9 +720,7 @@ impl Error {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn value_validation<'a, 'b, A>(arg: Option<&A>, err: String, color: ColorWhen) -> Self
|
||||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
pub fn value_validation<'a, 'b>(arg: Option<&AnyArg>, err: String, color: ColorWhen) -> Self
|
||||
{
|
||||
let c = Colorizer::new(ColorizerOption {
|
||||
use_stderr: true,
|
||||
|
@ -750,13 +744,13 @@ impl Error {
|
|||
|
||||
#[doc(hidden)]
|
||||
pub fn value_validation_auto(err: String) -> Self {
|
||||
let n: Option<&FlagBuilder> = None;
|
||||
let n: Option<&AnyArg> = None;
|
||||
Error::value_validation(n, err, ColorWhen::Auto)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn wrong_number_of_values<'a, 'b, A, S, U>(
|
||||
arg: &A,
|
||||
pub fn wrong_number_of_values<'a, 'b, S, U>(
|
||||
arg: &AnyArg,
|
||||
num_vals: u64,
|
||||
curr_vals: usize,
|
||||
suffix: S,
|
||||
|
@ -764,7 +758,6 @@ impl Error {
|
|||
color: ColorWhen,
|
||||
) -> Self
|
||||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
S: Display,
|
||||
U: Display,
|
||||
{
|
||||
|
@ -792,9 +785,8 @@ impl Error {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn unexpected_multiple_usage<'a, 'b, A, U>(arg: &A, usage: U, color: ColorWhen) -> Self
|
||||
pub fn unexpected_multiple_usage<'a, 'b, U>(arg: &AnyArg, usage: U, color: ColorWhen) -> Self
|
||||
where
|
||||
A: AnyArg<'a, 'b> + Display,
|
||||
U: Display,
|
||||
{
|
||||
let c = Colorizer::new(ColorizerOption {
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
//!
|
||||
//! The following examples show a quick example of some of the very basic functionality of `clap`.
|
||||
//! For more advanced usage, such as requirements, conflicts, groups, multiple values and
|
||||
//! occurrences see the [documentation](https://docs.rs/clap/), [examples/](examples) directory of
|
||||
//! occurrences see the [documentation](https://docs.rs/clap/), [examples/](https://github.com/kbknapp/clap-rs/tree/master/examples) directory of
|
||||
//! this repository or the [video tutorials](https://www.youtube.com/playlist?list=PLza5oFLQGTl2Z5T8g1pRkIynR3E0_pc7U).
|
||||
//!
|
||||
//! **NOTE:** All of these examples are functionally the same, but show different styles in which to
|
||||
|
@ -366,13 +366,13 @@
|
|||
//! * **Red** Color: **NOT** included by default (must use cargo `features` to enable)
|
||||
//! * **Blue** Color: Dev dependency, only used while developing.
|
||||
//!
|
||||
//! ![clap dependencies](clap_dep_graph.png)
|
||||
//! ![clap dependencies](https://raw.githubusercontent.com/kbknapp/clap-rs/master/clap_dep_graph.png)
|
||||
//!
|
||||
//! ### More Information
|
||||
//!
|
||||
//! You can find complete documentation on the [docs.rs](https://docs.rs/clap/) for this project.
|
||||
//!
|
||||
//! You can also find usage examples in the [examples/](examples) directory of this repo.
|
||||
//! You can also find usage examples in the [examples/](https://github.com/kbknapp/clap-rs/tree/master/examples) directory of this repo.
|
||||
//!
|
||||
//! #### Video Tutorials
|
||||
//!
|
||||
|
@ -391,7 +391,7 @@
|
|||
//! `clap`. You can either add it to the [examples/](examples) directory, or file an issue and tell
|
||||
//! me. I'm all about giving credit where credit is due :)
|
||||
//!
|
||||
//! Please read [CONTRIBUTING.md](.github/CONTRIBUTING.md) before you start contributing.
|
||||
//! Please read [CONTRIBUTING.md](https://raw.githubusercontent.com/kbknapp/clap-rs/master/.github/CONTRIBUTING.md) before you start contributing.
|
||||
//!
|
||||
//!
|
||||
//! ### Testing Code
|
||||
|
@ -404,13 +404,13 @@
|
|||
//! ```
|
||||
//!
|
||||
//! Alternatively, if you have [`just`](https://github.com/casey/just) installed you can run the
|
||||
//! prebuilt recipies. *Not* using `just` is prfeclty fine as well, it simply bundles commands
|
||||
//! prebuilt recipies. *Not* using `just` is perfectly fine as well, it simply bundles commands
|
||||
//! automatically.
|
||||
//!
|
||||
//! For example, to test the code, as above simply run:
|
||||
//!
|
||||
//! ```text
|
||||
//! $ just run-tests`
|
||||
//! $ just run-tests
|
||||
//! ```
|
||||
//!
|
||||
//! From here on, I will lis the appropriate `cargo` command as well as the `just` command.
|
||||
|
@ -513,7 +513,7 @@
|
|||
//! this repository for more information.
|
||||
|
||||
#![crate_type = "lib"]
|
||||
#![doc(html_root_url = "https://docs.rs/clap/2.29.0")]
|
||||
#![doc(html_root_url = "https://docs.rs/clap/2.31.2")]
|
||||
#![deny(missing_docs, missing_debug_implementations, missing_copy_implementations, trivial_casts,
|
||||
unused_import_braces, unused_allocation)]
|
||||
// Lints we'd like to deny but are currently failing for upstream crates
|
||||
|
@ -528,7 +528,7 @@
|
|||
#![cfg_attr(feature = "lints", allow(doc_markdown))]
|
||||
#![cfg_attr(feature = "lints", allow(explicit_iter_loop))]
|
||||
|
||||
#[cfg(feature = "color")]
|
||||
#[cfg(all(feature = "color", not(target_os = "windows")))]
|
||||
extern crate ansi_term;
|
||||
#[cfg(feature = "color")]
|
||||
extern crate atty;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following example shows how to load a properly formatted YAML file to build an instnace
|
||||
/// The following example shows how to load a properly formatted YAML file to build an instance
|
||||
/// of an `App` struct.
|
||||
///
|
||||
/// ```ignore
|
||||
|
@ -607,10 +607,10 @@ macro_rules! app_from_crate {
|
|||
/// * Three dots (`...`) sets [`Arg::multiple(true)`]
|
||||
/// * Angled brackets after either a short or long will set [`Arg::value_name`] and
|
||||
/// `Arg::required(true)` such as `--config <FILE>` = `Arg::value_name("FILE")` and
|
||||
/// `Arg::required(true)
|
||||
/// `Arg::required(true)`
|
||||
/// * Square brackets after either a short or long will set [`Arg::value_name`] and
|
||||
/// `Arg::required(false)` such as `--config [FILE]` = `Arg::value_name("FILE")` and
|
||||
/// `Arg::required(false)
|
||||
/// `Arg::required(false)`
|
||||
/// * There are short hand syntaxes for Arg methods that accept booleans
|
||||
/// * A plus sign will set that method to `true` such as `+required` = `Arg::required(true)`
|
||||
/// * An exclamation will set that method to `false` such as `!required` = `Arg::required(false)`
|
||||
|
@ -853,15 +853,15 @@ macro_rules! write_nspaces {
|
|||
}
|
||||
|
||||
// convenience macro for remove an item from a vec
|
||||
macro_rules! vec_remove_all {
|
||||
($vec:expr, $to_rem:expr) => {
|
||||
debugln!("vec_remove_all! to_rem={:?}", $to_rem);
|
||||
for i in (0 .. $vec.len()).rev() {
|
||||
let should_remove = $to_rem.any(|name| name == &$vec[i]);
|
||||
if should_remove { $vec.swap_remove(i); }
|
||||
}
|
||||
};
|
||||
}
|
||||
//macro_rules! vec_remove_all {
|
||||
// ($vec:expr, $to_rem:expr) => {
|
||||
// debugln!("vec_remove_all! to_rem={:?}", $to_rem);
|
||||
// for i in (0 .. $vec.len()).rev() {
|
||||
// let should_remove = $to_rem.any(|name| name == &$vec[i]);
|
||||
// if should_remove { $vec.swap_remove(i); }
|
||||
// }
|
||||
// };
|
||||
//}
|
||||
macro_rules! find_from {
|
||||
($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
|
||||
let mut ret = None;
|
||||
|
@ -892,36 +892,49 @@ macro_rules! find_from {
|
|||
}};
|
||||
}
|
||||
|
||||
macro_rules! find_name_from {
|
||||
($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
|
||||
let mut ret = None;
|
||||
for k in $matcher.arg_names() {
|
||||
if let Some(f) = find_by_name!($_self, k, flags, iter) {
|
||||
if let Some(ref v) = f.$from() {
|
||||
if v.contains($arg_name) {
|
||||
ret = Some(f.b.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(o) = find_by_name!($_self, k, opts, iter) {
|
||||
if let Some(ref v) = o.$from() {
|
||||
if v.contains(&$arg_name) {
|
||||
ret = Some(o.b.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(pos) = find_by_name!($_self, k, positionals, values) {
|
||||
if let Some(ref v) = pos.$from() {
|
||||
if v.contains($arg_name) {
|
||||
ret = Some(pos.b.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ret
|
||||
}};
|
||||
}
|
||||
//macro_rules! find_name_from {
|
||||
// ($_self:expr, $arg_name:expr, $from:ident, $matcher:expr) => {{
|
||||
// let mut ret = None;
|
||||
// for k in $matcher.arg_names() {
|
||||
// if let Some(f) = find_by_name!($_self, k, flags, iter) {
|
||||
// if let Some(ref v) = f.$from() {
|
||||
// if v.contains($arg_name) {
|
||||
// ret = Some(f.b.name);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if let Some(o) = find_by_name!($_self, k, opts, iter) {
|
||||
// if let Some(ref v) = o.$from() {
|
||||
// if v.contains(&$arg_name) {
|
||||
// ret = Some(o.b.name);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if let Some(pos) = find_by_name!($_self, k, positionals, values) {
|
||||
// if let Some(ref v) = pos.$from() {
|
||||
// if v.contains($arg_name) {
|
||||
// ret = Some(pos.b.name);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// ret
|
||||
// }};
|
||||
//}
|
||||
|
||||
|
||||
macro_rules! find_any_by_name {
|
||||
($p:expr, $name:expr) => {
|
||||
{
|
||||
fn as_trait_obj<'a, 'b, T: AnyArg<'a, 'b>>(x: &T) -> &AnyArg<'a, 'b> { x }
|
||||
find_by_name!($p, $name, flags, iter).map(as_trait_obj).or(
|
||||
find_by_name!($p, $name, opts, iter).map(as_trait_obj).or(
|
||||
find_by_name!($p, $name, positionals, values).map(as_trait_obj)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finds an arg by name
|
||||
macro_rules! find_by_name {
|
||||
($p:expr, $name:expr, $what:ident, $how:ident) => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
|
||||
use INVALID_UTF8;
|
||||
use std::ffi::OsStr;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[cfg(not(any(target_os = "windows", target_arch = "wasm32")))]
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
|
||||
pub trait OsStrExt3 {
|
||||
fn from_bytes(b: &[u8]) -> &Self;
|
||||
fn as_bytes(&self) -> &[u8];
|
||||
|
@ -22,19 +22,25 @@ pub trait OsStrExt2 {
|
|||
fn split(&self, b: u8) -> OsSplit;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[cfg(any(target_os = "windows", target_arch = "wasm32"))]
|
||||
impl OsStrExt3 for OsStr {
|
||||
fn from_bytes(b: &[u8]) -> &Self {
|
||||
use std::mem;
|
||||
unsafe { mem::transmute(b) }
|
||||
}
|
||||
fn as_bytes(&self) -> &[u8] { self.to_str().map(|s| s.as_bytes()).expect(INVALID_UTF8) }
|
||||
fn as_bytes(&self) -> &[u8] {
|
||||
self.to_str().map(|s| s.as_bytes()).expect(INVALID_UTF8)
|
||||
}
|
||||
}
|
||||
|
||||
impl OsStrExt2 for OsStr {
|
||||
fn starts_with(&self, s: &[u8]) -> bool { self.as_bytes().starts_with(s) }
|
||||
fn starts_with(&self, s: &[u8]) -> bool {
|
||||
self.as_bytes().starts_with(s)
|
||||
}
|
||||
|
||||
fn is_empty_(&self) -> bool { self.as_bytes().is_empty() }
|
||||
fn is_empty_(&self) -> bool {
|
||||
self.as_bytes().is_empty()
|
||||
}
|
||||
|
||||
fn contains_byte(&self, byte: u8) -> bool {
|
||||
for b in self.as_bytes() {
|
||||
|
@ -82,7 +88,9 @@ impl OsStrExt2 for OsStr {
|
|||
)
|
||||
}
|
||||
|
||||
fn len_(&self) -> usize { self.as_bytes().len() }
|
||||
fn len_(&self) -> usize {
|
||||
self.as_bytes().len()
|
||||
}
|
||||
|
||||
fn split(&self, b: u8) -> OsSplit {
|
||||
OsSplit {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
{"files":{".editorconfig":"d87dec5ba57378b3c32bbf67526e434f7ac4f44d8b1acc7f44b6d8e7ee6919ad",".travis.yml":"10ee132e43d2a5e3c304a4970adfec8939014403c32d178728a56c8cb18046ee","CHANGELOG.md":"5f3e55f36768b2d2e40ee54702d6298175b7736e9b8918ca03592011f053c25f","Cargo.toml":"ff9b4b5bd80560ed36b14313261763d321cfb9e3f212896ec565fb872e26c3f0","LICENSE":"9b60c5838cbe7953b84bb2206835cb2bc898802da4c449e4442cf23e3ad4841e","README.md":"1778e287c6788197322a352721414f7ceab0c84ee0918513bb9f5382d3e40bc6","appveyor.yml":"b41eae9798a9bb250f6046509d9bbd6e63bac9ad2655d342b3d9c8975584f0c0","dev":"498932c7296d8abeb9e432e797a579787a85a052fa514325b873c4fe3200ec67","src/lib.rs":"a79164ec2334c477ca9d25e10c161e0e9bb8ae8670eb22976948c2a574b40985","tests/lib.rs":"43c707f9c9d35fafa07b34f61418ac877687daba5a0790d87e47b3d2c8bad274"},"package":"b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"}
|
|
@ -0,0 +1,13 @@
|
|||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_brace_style = K&R
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
max_line_length = 80
|
||||
trim_trailing_whitespace = true
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
language: rust
|
||||
|
||||
rust:
|
||||
- nightly
|
||||
- beta
|
||||
- stable
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
# Change Log
|
||||
This project attempts to adhere to [Semantic Versioning](http://semver.org).
|
||||
|
||||
## [0.6.0] - (2016-12-26)
|
||||
### Added
|
||||
- Add optimal string alignment distance
|
||||
|
||||
### Fixed
|
||||
- Fix Damerau-Levenshtein implementation (previous implementation was actually
|
||||
optimal string alignment; see this [Damerau-Levenshtein explanation])
|
||||
|
||||
## [0.5.2] - (2016-11-21)
|
||||
### Changed
|
||||
- Remove Cargo generated documentation in favor of a [docs.rs] link
|
||||
|
||||
## [0.5.1] - (2016-08-23)
|
||||
### Added
|
||||
- Add Cargo generated documentation
|
||||
|
||||
### Fixed
|
||||
- Fix panic when Jaro or Jaro-Winkler are given strings both with a length of
|
||||
one
|
||||
|
||||
## [0.5.0] - (2016-08-11)
|
||||
### Changed
|
||||
- Make Hamming faster (thanks @IBUzPE9) when the two strings have the same
|
||||
length but slower when they have different lengths
|
||||
|
||||
## [0.4.1] - (2016-04-18)
|
||||
### Added
|
||||
- Add Vagrant setup for development
|
||||
- Add AppVeyor configuration for Windows CI
|
||||
|
||||
### Fixed
|
||||
- Fix metrics when given strings with multibyte characters (thanks @WanzenBug)
|
||||
|
||||
## [0.4.0] - (2015-06-10)
|
||||
### Added
|
||||
- For each metric, add a function that takes a vector of strings and returns a
|
||||
vector of results (thanks @ovarene)
|
||||
|
||||
## [0.3.0] - (2015-04-30)
|
||||
### Changed
|
||||
- Remove usage of unstable Rust features
|
||||
|
||||
## [0.2.5] - (2015-04-24)
|
||||
### Fixed
|
||||
- Remove unnecessary `Float` import from doc tests
|
||||
|
||||
## [0.2.4] - (2015-04-15)
|
||||
### Fixed
|
||||
- Remove unused `core` feature flag
|
||||
|
||||
## [0.2.3] - (2015-04-01)
|
||||
### Fixed
|
||||
- Remove now unnecessary `Float` import
|
||||
|
||||
## [0.2.2] - (2015-03-29)
|
||||
### Fixed
|
||||
- Remove usage of `char_at` (marked as unstable)
|
||||
|
||||
## [0.2.1] - (2015-02-20)
|
||||
### Fixed
|
||||
- Update bit vector import to match Rust update
|
||||
|
||||
## [0.2.0] - (2015-02-19)
|
||||
### Added
|
||||
- Implement Damerau-Levenshtein
|
||||
- Add tests in docs
|
||||
|
||||
## [0.1.1] - (2015-02-10)
|
||||
### Added
|
||||
- Configure Travis for CI
|
||||
- Add rustdoc comments
|
||||
|
||||
### Fixed
|
||||
- Limit Jaro-Winkler return value to a maximum of 1.0
|
||||
- Fix float comparsions in tests
|
||||
|
||||
## [0.1.0] - (2015-02-09)
|
||||
### Added
|
||||
- Implement Hamming, Jaro, Jaro-Winkler, and Levenshtein
|
||||
|
||||
[Unreleased]: https://github.com/dguo/strsim-rs/compare/0.6.0...HEAD
|
||||
[0.6.0]: https://github.com/dguo/strsim-rs/compare/0.5.2...0.6.0
|
||||
[0.5.2]: https://github.com/dguo/strsim-rs/compare/0.5.1...0.5.2
|
||||
[0.5.1]: https://github.com/dguo/strsim-rs/compare/0.5.0...0.5.1
|
||||
[0.5.0]: https://github.com/dguo/strsim-rs/compare/0.4.1...0.5.0
|
||||
[0.4.1]: https://github.com/dguo/strsim-rs/compare/0.4.0...0.4.1
|
||||
[0.4.0]: https://github.com/dguo/strsim-rs/compare/0.3.0...0.4.0
|
||||
[0.3.0]: https://github.com/dguo/strsim-rs/compare/0.2.5...0.3.0
|
||||
[0.2.5]: https://github.com/dguo/strsim-rs/compare/0.2.4...0.2.5
|
||||
[0.2.4]: https://github.com/dguo/strsim-rs/compare/0.2.3...0.2.4
|
||||
[0.2.3]: https://github.com/dguo/strsim-rs/compare/0.2.2...0.2.3
|
||||
[0.2.2]: https://github.com/dguo/strsim-rs/compare/0.2.1...0.2.2
|
||||
[0.2.1]: https://github.com/dguo/strsim-rs/compare/0.2.0...0.2.1
|
||||
[0.2.0]: https://github.com/dguo/strsim-rs/compare/0.1.1...0.2.0
|
||||
[0.1.1]: https://github.com/dguo/strsim-rs/compare/0.1.0...0.1.1
|
||||
[0.1.0]: https://github.com/dguo/strsim-rs/compare/fabad4...0.1.0
|
||||
[docs.rs]: https://docs.rs/strsim/
|
||||
[Damerau-Levenshtein explanation]:
|
||||
http://scarcitycomputing.blogspot.com/2013/04/damerau-levenshtein-edit-distance.html
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
|
||||
name = "strsim"
|
||||
version = "0.6.0"
|
||||
authors = ["Danny Guo <dannyguo91@gmail.com>"]
|
||||
description = """
|
||||
Implementations of string similarity metrics.
|
||||
Includes Hamming, Levenshtein, Damerau-Levenshtein, Jaro, and Jaro-Winkler.
|
||||
"""
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
keywords = ["string", "similarity", "Hamming",
|
||||
"Levenshtein", "Jaro"]
|
||||
homepage = "https://github.com/dguo/strsim-rs"
|
||||
repository = "https://github.com/dguo/strsim-rs"
|
||||
documentation = "http://dannyguo.com/strsim-rs/"
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Danny Guo
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# strsim-rs [![Crates.io](https://img.shields.io/crates/v/strsim.svg)](https://crates.io/crates/strsim) [![Crates.io](https://img.shields.io/crates/l/strsim.svg?maxAge=2592000)](https://github.com/dguo/strsim-rs/blob/master/LICENSE) [![Linux build status](https://travis-ci.org/dguo/strsim-rs.svg?branch=master)](https://travis-ci.org/dguo/strsim-rs) [![Windows build status](https://ci.appveyor.com/api/projects/status/ggue6i785618a39w?svg=true)](https://ci.appveyor.com/project/dguo/strsim-rs)
|
||||
|
||||
[Rust](https://www.rust-lang.org) implementations of [string similarity metrics]:
|
||||
- [Hamming]
|
||||
- [Levenshtein]
|
||||
- [Optimal string alignment]
|
||||
- [Damerau-Levenshtein]
|
||||
- [Jaro and Jaro-Winkler] - this implementation of Jaro-Winkler does not limit the common prefix length
|
||||
|
||||
### Installation
|
||||
```toml
|
||||
# Cargo.toml
|
||||
[dependencies]
|
||||
strsim = "0.6.0"
|
||||
```
|
||||
|
||||
### [Documentation](https://docs.rs/strsim/)
|
||||
You can change the version in the url to see the documentation for an older
|
||||
version in the
|
||||
[changelog](https://github.com/dguo/strsim-rs/blob/master/CHANGELOG.md).
|
||||
|
||||
### Usage
|
||||
```rust
|
||||
extern crate strsim;
|
||||
|
||||
use strsim::{hamming, levenshtein, osa_distance, damerau_levenshtein, jaro,
|
||||
jaro_winkler, levenshtein_against_vec, osa_distance_against_vec,
|
||||
damerau_levenshtein_against_vec, jaro_against_vec,
|
||||
jaro_winkler_against_vec};
|
||||
|
||||
fn main() {
|
||||
match hamming("hamming", "hammers") {
|
||||
Ok(distance) => assert_eq!(3, distance),
|
||||
Err(why) => panic!("{:?}", why)
|
||||
}
|
||||
|
||||
assert_eq!(3, levenshtein("kitten", "sitting"));
|
||||
|
||||
assert_eq!(3, osa_distance("ac", "cba"));
|
||||
|
||||
assert_eq!(2, damerau_levenshtein("ac", "cba"));
|
||||
|
||||
assert!((0.392 - jaro("Friedrich Nietzsche", "Jean-Paul Sartre")).abs() <
|
||||
0.001);
|
||||
|
||||
assert!((0.911 - jaro_winkler("cheeseburger", "cheese fries")).abs() <
|
||||
0.001);
|
||||
|
||||
// get vectors of values back
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
|
||||
assert_eq!(levenshtein_against_vec("test", &v),
|
||||
vec![0, 1, 2, 3, 4, 2, 3]);
|
||||
|
||||
assert_eq!(osa_distance_against_vec("test", &v),
|
||||
vec![0, 1, 2, 3, 4, 1, 3]);
|
||||
|
||||
assert_eq!(damerau_levenshtein_against_vec("test", &v),
|
||||
vec![0, 1, 2, 3, 4, 1, 2]);
|
||||
|
||||
let jaro_distances = jaro_against_vec("test", &v);
|
||||
let jaro_expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
let jaro_delta: f64 = jaro_distances.iter()
|
||||
.zip(jaro_expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(jaro_delta < 0.0001);
|
||||
|
||||
let jaro_winkler_distances = jaro_winkler_against_vec("test", &v);
|
||||
let jaro_winkler_expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
let jaro_winkler_delta = jaro_winkler_distances.iter()
|
||||
.zip(jaro_winkler_expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(jaro_winkler_delta < 0.0001);
|
||||
}
|
||||
```
|
||||
|
||||
### Development
|
||||
If you don't want to install Rust itself, you can install [Docker], and run
|
||||
`$ ./dev`. This should bring up a temporary container from which you can run
|
||||
[cargo] commands.
|
||||
|
||||
### License
|
||||
[MIT](https://github.com/dguo/strsim-rs/blob/master/LICENSE)
|
||||
|
||||
[string similarity metrics]:http://en.wikipedia.org/wiki/String_metric
|
||||
[Damerau-Levenshtein]:http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
|
||||
[Jaro and Jaro-Winkler]:http://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance
|
||||
[Levenshtein]:http://en.wikipedia.org/wiki/Levenshtein_distance
|
||||
[Hamming]:http://en.wikipedia.org/wiki/Hamming_distance
|
||||
[Optimal string alignment]:https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance#Optimal_string_alignment_distance
|
||||
[Docker]:https://docs.docker.com/engine/installation/
|
||||
[cargo]:https://github.com/rust-lang/cargo
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
install:
|
||||
- ps: Start-FileDownload 'https://static.rust-lang.org/dist/rust-beta-x86_64-pc-windows-gnu.exe'
|
||||
- rust-beta-x86_64-pc-windows-gnu.exe /VERYSILENT /NORESTART /DIR="C:\Program Files (x86)\Rust"
|
||||
- SET PATH=%PATH%;C:\Program Files (x86)\Rust\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo build
|
||||
- cargo test --verbose
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
from subprocess import call
|
||||
import sys
|
||||
|
||||
try:
|
||||
call(['docker', 'run', '-it', '--rm', '-v', os.getcwd() + ':/source',
|
||||
'jimmycuadra/rust:1.14.0'])
|
||||
except OSError:
|
||||
print('Please install Docker.', file=sys.stderr)
|
||||
|
|
@ -0,0 +1,911 @@
|
|||
//! This library implements string similarity metrics.
|
||||
|
||||
use std::char;
|
||||
use std::cmp::{max, min};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum StrSimError {
|
||||
DifferentLengthArgs
|
||||
}
|
||||
|
||||
pub type HammingResult = Result<usize, StrSimError>;
|
||||
|
||||
/// Calculates the number of positions in the two strings where the characters
|
||||
/// differ. Returns an error if the strings have different lengths.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::hamming;
|
||||
///
|
||||
/// match hamming("hamming", "hammers") {
|
||||
/// Ok(distance) => assert_eq!(3, distance),
|
||||
/// Err(why) => panic!("{:?}", why)
|
||||
/// }
|
||||
/// ```
|
||||
pub fn hamming(a: &str, b: &str) -> HammingResult {
|
||||
let (mut ita, mut itb, mut count) = (a.chars(), b.chars(), 0);
|
||||
loop {
|
||||
match (ita.next(), itb.next()){
|
||||
(Some(x), Some(y)) => if x != y { count += 1 },
|
||||
(None, None) => return Ok(count),
|
||||
_ => return Err(StrSimError::DifferentLengthArgs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the Jaro similarity between two strings. The returned value
|
||||
/// is between 0.0 and 1.0 (higher value means more similar).
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::jaro;
|
||||
///
|
||||
/// assert!((0.392 - jaro("Friedrich Nietzsche", "Jean-Paul Sartre")).abs() <
|
||||
/// 0.001);
|
||||
/// ```
|
||||
pub fn jaro(a: &str, b: &str) -> f64 {
|
||||
if a == b { return 1.0; }
|
||||
|
||||
let a_len = a.chars().count();
|
||||
let b_len = b.chars().count();
|
||||
|
||||
// The check for lengths of one here is to prevent integer overflow when
|
||||
// calculating the search range.
|
||||
if a_len == 0 || b_len == 0 || (a_len == 1 && b_len == 1) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let search_range = (max(a_len, b_len) / 2) - 1;
|
||||
|
||||
let mut b_consumed = Vec::with_capacity(b_len);
|
||||
for _ in 0..b_len {
|
||||
b_consumed.push(false);
|
||||
}
|
||||
let mut matches = 0.0;
|
||||
|
||||
let mut transpositions = 0.0;
|
||||
let mut b_match_index = 0;
|
||||
|
||||
for (i, a_char) in a.chars().enumerate() {
|
||||
let min_bound =
|
||||
// prevent integer wrapping
|
||||
if i > search_range {
|
||||
max(0, i - search_range)
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let max_bound = min(b_len - 1, i + search_range);
|
||||
|
||||
if min_bound > max_bound {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (j, b_char) in b.chars().enumerate() {
|
||||
if min_bound <= j && j <= max_bound && a_char == b_char &&
|
||||
!b_consumed[j] {
|
||||
b_consumed[j] = true;
|
||||
matches += 1.0;
|
||||
|
||||
if j < b_match_index {
|
||||
transpositions += 1.0;
|
||||
}
|
||||
b_match_index = j;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches == 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
(1.0 / 3.0) * ((matches / a_len as f64) +
|
||||
(matches / b_len as f64) +
|
||||
((matches - transpositions) / matches))
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the Jaro distance between a string and each string in a vector.
|
||||
/// Returns a vector of corresponding values between 0.0 and 1.0 (higher value
|
||||
/// means more similar).
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::jaro_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = jaro_against_vec("test", &v);
|
||||
/// let expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
/// let delta: f64 = result.iter()
|
||||
/// .zip(expected.iter())
|
||||
/// .map(|(x, y)| (x - y).abs() as f64)
|
||||
/// .fold(0.0, |x, y| x + y as f64);
|
||||
/// assert!(delta.abs() < 0.0001);
|
||||
/// ```
|
||||
pub fn jaro_against_vec(a: &str, v: &[&str]) -> Vec<f64> {
|
||||
v.iter().map(|b| jaro(a, b)).collect()
|
||||
}
|
||||
|
||||
/// Like Jaro but gives a boost to strings that have a common prefix.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::jaro_winkler;
|
||||
///
|
||||
/// assert!((0.911 - jaro_winkler("cheeseburger", "cheese fries")).abs() <
|
||||
/// 0.001);
|
||||
/// ```
|
||||
pub fn jaro_winkler(a: &str, b: &str) -> f64 {
|
||||
let jaro_distance = jaro(a, b);
|
||||
|
||||
// Don't limit the length of the common prefix
|
||||
let prefix_length = a.chars()
|
||||
.zip(b.chars())
|
||||
.take_while(|&(a_char, b_char)| a_char == b_char)
|
||||
.count();
|
||||
|
||||
let jaro_winkler_distance =
|
||||
jaro_distance + (0.1 * prefix_length as f64 * (1.0 - jaro_distance));
|
||||
|
||||
if jaro_winkler_distance <= 1.0 {
|
||||
jaro_winkler_distance
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the Jaro-Winkler distances between a string and each string
|
||||
/// in a vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::jaro_winkler_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = jaro_winkler_against_vec("test", &v);
|
||||
/// let expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
/// let delta: f64 = result.iter()
|
||||
/// .zip(expected.iter())
|
||||
/// .map(|(x, y)| (x - y).abs() as f64)
|
||||
/// .fold(0.0, |x, y| x + y as f64);
|
||||
/// assert!(delta.abs() < 0.0001);
|
||||
/// ```
|
||||
pub fn jaro_winkler_against_vec(a: &str, v: &[&str]) -> Vec<f64> {
|
||||
v.iter().map(|b| jaro_winkler(a, b)).collect()
|
||||
}
|
||||
|
||||
/// Calculates the minimum number of insertions, deletions, and substitutions
|
||||
/// required to change one string into the other.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::levenshtein;
|
||||
///
|
||||
/// assert_eq!(3, levenshtein("kitten", "sitting"));
|
||||
/// ```
|
||||
pub fn levenshtein(a: &str, b: &str) -> usize {
|
||||
let a_len = a.chars().count();
|
||||
let b_len = b.chars().count();
|
||||
if a == b { return 0; }
|
||||
else if a_len == 0 { return b_len; }
|
||||
else if b_len == 0 { return a_len; }
|
||||
|
||||
let mut prev_distances: Vec<usize> = Vec::with_capacity(b_len + 1);
|
||||
let mut curr_distances: Vec<usize> = Vec::with_capacity(b_len + 1);
|
||||
|
||||
for i in 0..(b_len + 1) {
|
||||
prev_distances.push(i);
|
||||
curr_distances.push(0);
|
||||
}
|
||||
|
||||
for (i, a_char) in a.chars().enumerate() {
|
||||
curr_distances[0] = i + 1;
|
||||
|
||||
for (j, b_char) in b.chars().enumerate() {
|
||||
let cost = if a_char == b_char { 0 } else { 1 };
|
||||
curr_distances[j + 1] = min(curr_distances[j] + 1,
|
||||
min(prev_distances[j + 1] + 1,
|
||||
prev_distances[j] + cost));
|
||||
}
|
||||
|
||||
prev_distances.clone_from(&curr_distances);
|
||||
}
|
||||
|
||||
curr_distances[b_len]
|
||||
}
|
||||
|
||||
/// Calculates the Levenshtein distance between a string and each string in a
|
||||
/// vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::levenshtein_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = levenshtein_against_vec("test", &v);
|
||||
/// let expected = vec![0, 1, 2, 3, 4, 2];
|
||||
/// assert_eq!(expected, result);
|
||||
/// ```
|
||||
pub fn levenshtein_against_vec(a: &str, v: &[&str]) -> Vec<usize> {
|
||||
v.iter().map(|b| levenshtein(a, b)).collect()
|
||||
}
|
||||
|
||||
/// Like Levenshtein but allows for adjacent transpositions. Each substring can
|
||||
/// only be edited once.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::osa_distance;
|
||||
///
|
||||
/// assert_eq!(3, osa_distance("ab", "bca"));
|
||||
/// ```
|
||||
pub fn osa_distance(a: &str, b: &str) -> usize {
|
||||
let a_len = a.chars().count();
|
||||
let b_len = b.chars().count();
|
||||
if a == b { return 0; }
|
||||
else if a_len == 0 { return b_len; }
|
||||
else if b_len == 0 { return a_len; }
|
||||
|
||||
let mut prev_two_distances: Vec<usize> = Vec::with_capacity(b_len + 1);
|
||||
let mut prev_distances: Vec<usize> = Vec::with_capacity(b_len + 1);
|
||||
let mut curr_distances: Vec<usize> = Vec::with_capacity(b_len + 1);
|
||||
|
||||
let mut prev_a_char = char::MAX;
|
||||
let mut prev_b_char = char::MAX;
|
||||
|
||||
for i in 0..(b_len + 1) {
|
||||
prev_two_distances.push(i);
|
||||
prev_distances.push(i);
|
||||
curr_distances.push(0);
|
||||
}
|
||||
|
||||
for (i, a_char) in a.chars().enumerate() {
|
||||
curr_distances[0] = i + 1;
|
||||
|
||||
for (j, b_char) in b.chars().enumerate() {
|
||||
let cost = if a_char == b_char { 0 } else { 1 };
|
||||
curr_distances[j + 1] = min(curr_distances[j] + 1,
|
||||
min(prev_distances[j + 1] + 1,
|
||||
prev_distances[j] + cost));
|
||||
if i > 0 && j > 0 && a_char != b_char &&
|
||||
a_char == prev_b_char && b_char == prev_a_char {
|
||||
curr_distances[j + 1] = min(curr_distances[j + 1],
|
||||
prev_two_distances[j - 1] + 1);
|
||||
}
|
||||
|
||||
prev_b_char = b_char;
|
||||
}
|
||||
|
||||
prev_two_distances.clone_from(&prev_distances);
|
||||
prev_distances.clone_from(&curr_distances);
|
||||
prev_a_char = a_char;
|
||||
}
|
||||
|
||||
curr_distances[b_len]
|
||||
|
||||
}
|
||||
|
||||
/// Calculates the optimal string alignment distance between a string and each
|
||||
/// string in a vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::osa_distance_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = osa_distance_against_vec("test", &v);
|
||||
/// let expected = vec![0, 1, 2, 3, 4, 1];
|
||||
/// assert_eq!(expected, result);
|
||||
/// ```
|
||||
pub fn osa_distance_against_vec(a: &str, v: &[&str]) -> Vec<usize> {
|
||||
v.iter().map(|b| osa_distance(a, b)).collect()
|
||||
}
|
||||
|
||||
/// Like optimal string alignment, but substrings can be edited an unlimited
|
||||
/// number of times, and the triangle inequality holds.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::damerau_levenshtein;
|
||||
///
|
||||
/// assert_eq!(2, damerau_levenshtein("ab", "bca"));
|
||||
/// ```
|
||||
pub fn damerau_levenshtein(a: &str, b: &str) -> usize {
|
||||
if a == b { return 0; }
|
||||
|
||||
let a_chars: Vec<char> = a.chars().collect();
|
||||
let b_chars: Vec<char> = b.chars().collect();
|
||||
let a_len = a_chars.len();
|
||||
let b_len = b_chars.len();
|
||||
|
||||
if a_len == 0 { return b_len; }
|
||||
if b_len == 0 { return a_len; }
|
||||
|
||||
let mut distances = vec![vec![0; b_len + 2]; a_len + 2];
|
||||
let max_distance = a_len + b_len;
|
||||
distances[0][0] = max_distance;
|
||||
|
||||
for i in 0..(a_len + 1) {
|
||||
distances[i + 1][0] = max_distance;
|
||||
distances[i + 1][1] = i;
|
||||
}
|
||||
|
||||
for j in 0..(b_len + 1) {
|
||||
distances[0][j + 1] = max_distance;
|
||||
distances[1][j + 1] = j;
|
||||
}
|
||||
|
||||
let mut chars: HashMap<char, usize> = HashMap::new();
|
||||
|
||||
for i in 1..(a_len + 1) {
|
||||
let mut db = 0;
|
||||
|
||||
for j in 1..(b_len + 1) {
|
||||
let k = match chars.get(&b_chars[j - 1]) {
|
||||
Some(value) => value.clone(),
|
||||
None => 0
|
||||
};
|
||||
|
||||
let l = db;
|
||||
|
||||
let mut cost = 1;
|
||||
if a_chars[i - 1] == b_chars[j - 1] {
|
||||
cost = 0;
|
||||
db = j;
|
||||
}
|
||||
|
||||
let substitution_cost = distances[i][j] + cost;
|
||||
let insertion_cost = distances[i][j + 1] + 1;
|
||||
let deletion_cost = distances[i + 1][j] + 1;
|
||||
let transposition_cost = distances[k][l] + (i - k - 1) + 1 +
|
||||
(j - l - 1);
|
||||
|
||||
distances[i + 1][j + 1] = min(substitution_cost,
|
||||
min(insertion_cost,
|
||||
min(deletion_cost,
|
||||
transposition_cost)));
|
||||
}
|
||||
|
||||
chars.insert(a_chars[i - 1], i);
|
||||
}
|
||||
|
||||
distances[a_len + 1][b_len + 1]
|
||||
}
|
||||
|
||||
/// Calculates the Damerau-Levenshtein distance between a string and each string
|
||||
/// in a vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::damerau_levenshtein_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = damerau_levenshtein_against_vec("test", &v);
|
||||
/// let expected = vec![0, 1, 2, 3, 4, 1];
|
||||
/// assert_eq!(expected, result);
|
||||
/// ```
|
||||
pub fn damerau_levenshtein_against_vec(a: &str, v: &[&str]) -> Vec<usize> {
|
||||
v.iter().map(|b| damerau_levenshtein(a, b)).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn hamming_empty() {
|
||||
match hamming("", "") {
|
||||
Ok(distance) => { assert_eq!(0, distance); },
|
||||
Err(why) => { panic!("{:?}", why); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hamming_same() {
|
||||
match hamming("hamming", "hamming") {
|
||||
Ok(distance) => { assert_eq!(0, distance); },
|
||||
Err(why) => { panic!("{:?}", why); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hamming_diff() {
|
||||
match hamming("hamming", "hammers") {
|
||||
Ok(distance) => { assert_eq!(3, distance); },
|
||||
Err(why) => { panic!("{:?}", why); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hamming_diff_multibyte() {
|
||||
match hamming("hamming", "h香mmüng") {
|
||||
Ok(distance) => { assert_eq!(2, distance); },
|
||||
Err(why) => { panic!("{:?}", why); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hamming_unequal_length() {
|
||||
match hamming("ham", "hamming") {
|
||||
Ok(_) => { panic!(); },
|
||||
Err(why) => { assert_eq!(why, StrSimError::DifferentLengthArgs); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hamming_names() {
|
||||
match hamming("Friedrich Nietzs", "Jean-Paul Sartre") {
|
||||
Ok(distance) => { assert_eq!(14, distance); },
|
||||
Err(why) => { panic!("{:?}", why); }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_both_empty() {
|
||||
assert_eq!(1.0, jaro("", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_first_empty() {
|
||||
assert_eq!(0.0, jaro("", "jaro"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_second_empty() {
|
||||
assert_eq!(0.0, jaro("distance", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_same() {
|
||||
assert_eq!(1.0, jaro("jaro", "jaro"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_multibyte() {
|
||||
assert!((0.818 - jaro("testabctest", "testöঙ香test")) < 0.001);
|
||||
assert!((0.818 - jaro("testöঙ香test", "testabctest")) < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_diff_short() {
|
||||
assert!((0.767 - jaro("dixon", "dicksonx")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_diff_one_character() {
|
||||
assert_eq!(0.0, jaro("a", "b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_diff_one_and_two() {
|
||||
assert!((0.83 - jaro("a", "ab")).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_diff_two_and_one() {
|
||||
assert!((0.83 - jaro("ab", "a")).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_diff_no_transposition() {
|
||||
assert!((0.822 - jaro("dwayne", "duane")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_diff_with_transposition() {
|
||||
assert!((0.944 - jaro("martha", "marhta")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_names() {
|
||||
assert!((0.392 - jaro("Friedrich Nietzsche",
|
||||
"Jean-Paul Sartre")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_both_empty() {
|
||||
assert_eq!(1.0, jaro_winkler("", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_first_empty() {
|
||||
assert_eq!(0.0, jaro_winkler("", "jaro-winkler"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_second_empty() {
|
||||
assert_eq!(0.0, jaro_winkler("distance", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_same() {
|
||||
assert_eq!(1.0, jaro_winkler("Jaro-Winkler", "Jaro-Winkler"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_multibyte() {
|
||||
assert!((0.89 - jaro_winkler("testabctest", "testöঙ香test")).abs() <
|
||||
0.001);
|
||||
assert!((0.89 - jaro_winkler("testöঙ香test", "testabctest")).abs() <
|
||||
0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_diff_short() {
|
||||
assert!((0.813 - jaro_winkler("dixon", "dicksonx")).abs() < 0.001);
|
||||
assert!((0.813 - jaro_winkler("dicksonx", "dixon")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_diff_one_character() {
|
||||
assert_eq!(0.0, jaro_winkler("a", "b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_diff_no_transposition() {
|
||||
assert!((0.840 - jaro_winkler("dwayne", "duane")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_diff_with_transposition() {
|
||||
assert!((0.961 - jaro_winkler("martha", "marhta")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_names() {
|
||||
assert!((0.562 - jaro_winkler("Friedrich Nietzsche",
|
||||
"Fran-Paul Sartre")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_long_prefix() {
|
||||
assert!((0.911 - jaro_winkler("cheeseburger", "cheese fries")).abs() <
|
||||
0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_more_names() {
|
||||
assert!((0.868 - jaro_winkler("Thorkel", "Thorgier")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_length_of_one() {
|
||||
assert!((0.738 - jaro_winkler("Dinsdale", "D")).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_very_long_prefix() {
|
||||
assert!((1.0 - jaro_winkler("thequickbrownfoxjumpedoverx",
|
||||
"thequickbrownfoxjumpedovery")).abs() <
|
||||
0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_empty() {
|
||||
assert_eq!(0, levenshtein("", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_same() {
|
||||
assert_eq!(0, levenshtein("levenshtein", "levenshtein"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_diff_short() {
|
||||
assert_eq!(3, levenshtein("kitten", "sitting"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_diff_with_space() {
|
||||
assert_eq!(5, levenshtein("hello, world", "bye, world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_diff_multibyte() {
|
||||
assert_eq!(3, levenshtein("öঙ香", "abc"));
|
||||
assert_eq!(3, levenshtein("abc", "öঙ香"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_diff_longer() {
|
||||
let a = "The quick brown fox jumped over the angry dog.";
|
||||
let b = "Lorem ipsum dolor sit amet, dicta latine an eam.";
|
||||
assert_eq!(37, levenshtein(a, b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_first_empty() {
|
||||
assert_eq!(7, levenshtein("", "sitting"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_second_empty() {
|
||||
assert_eq!(6, levenshtein("kitten", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_empty() {
|
||||
assert_eq!(0, osa_distance("", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_same() {
|
||||
assert_eq!(0, osa_distance("damerau", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_first_empty() {
|
||||
assert_eq!(7, osa_distance("", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_second_empty() {
|
||||
assert_eq!(7, osa_distance("damerau", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff() {
|
||||
assert_eq!(3, osa_distance("ca", "abc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff_short() {
|
||||
assert_eq!(3, osa_distance("damerau", "aderua"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff_reversed() {
|
||||
assert_eq!(3, osa_distance("aderua", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff_multibyte() {
|
||||
assert_eq!(3, osa_distance("öঙ香", "abc"));
|
||||
assert_eq!(3, osa_distance("abc", "öঙ香"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff_unequal_length() {
|
||||
assert_eq!(6, osa_distance("damerau", "aderuaxyz"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff_unequal_length_reversed() {
|
||||
assert_eq!(6, osa_distance("aderuaxyz", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff_comedians() {
|
||||
assert_eq!(5, osa_distance("Stewart", "Colbert"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_many_transpositions() {
|
||||
assert_eq!(4, osa_distance("abcdefghijkl", "bacedfgihjlk"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_diff_longer() {
|
||||
let a = "The quick brown fox jumped over the angry dog.";
|
||||
let b = "Lehem ipsum dolor sit amet, dicta latine an eam.";
|
||||
assert_eq!(36, osa_distance(a, b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_beginning_transposition() {
|
||||
assert_eq!(1, osa_distance("foobar", "ofobar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_end_transposition() {
|
||||
assert_eq!(1, osa_distance("specter", "spectre"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_restricted_edit() {
|
||||
assert_eq!(4, osa_distance("a cat", "an abct"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_empty() {
|
||||
assert_eq!(0, damerau_levenshtein("", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_same() {
|
||||
assert_eq!(0, damerau_levenshtein("damerau", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_first_empty() {
|
||||
assert_eq!(7, damerau_levenshtein("", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_second_empty() {
|
||||
assert_eq!(7, damerau_levenshtein("damerau", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff() {
|
||||
assert_eq!(2, damerau_levenshtein("ca", "abc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff_short() {
|
||||
assert_eq!(3, damerau_levenshtein("damerau", "aderua"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff_reversed() {
|
||||
assert_eq!(3, damerau_levenshtein("aderua", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff_multibyte() {
|
||||
assert_eq!(3, damerau_levenshtein("öঙ香", "abc"));
|
||||
assert_eq!(3, damerau_levenshtein("abc", "öঙ香"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff_unequal_length() {
|
||||
assert_eq!(6, damerau_levenshtein("damerau", "aderuaxyz"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff_unequal_length_reversed() {
|
||||
assert_eq!(6, damerau_levenshtein("aderuaxyz", "damerau"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff_comedians() {
|
||||
assert_eq!(5, damerau_levenshtein("Stewart", "Colbert"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_many_transpositions() {
|
||||
assert_eq!(4, damerau_levenshtein("abcdefghijkl", "bacedfgihjlk"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_diff_longer() {
|
||||
let a = "The quick brown fox jumped over the angry dog.";
|
||||
let b = "Lehem ipsum dolor sit amet, dicta latine an eam.";
|
||||
assert_eq!(36, damerau_levenshtein(a, b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_beginning_transposition() {
|
||||
assert_eq!(1, damerau_levenshtein("foobar", "ofobar"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_end_transposition() {
|
||||
assert_eq!(1, damerau_levenshtein("specter", "spectre"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_unrestricted_edit() {
|
||||
assert_eq!(3, damerau_levenshtein("a cat", "an abct"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected: Vec<usize> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_one() {
|
||||
let v = vec!["testy"];
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected = vec![1];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 2];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected: Vec<usize> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_one() {
|
||||
let v = vec!["etst"];
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected = vec![1];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tsvet"];
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 3];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected: Vec<usize> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_one() {
|
||||
let v = vec!["etst"];
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected = vec![1];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tsvet"];
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 2];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
fn equal_float_vecs(a: Vec<f64>, b: Vec<f64>) -> bool {
|
||||
let delta: f64 = a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
delta < 0.0001
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected: Vec<f64> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_one() {
|
||||
let v = vec!["test1"];
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected = vec![0.93333];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected: Vec<f64> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_one() {
|
||||
let v = vec!["test123"];
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected = vec![0.914286];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
extern crate strsim;
|
||||
|
||||
use strsim::{hamming, levenshtein, osa_distance, damerau_levenshtein, jaro,
|
||||
jaro_winkler, levenshtein_against_vec, osa_distance_against_vec,
|
||||
damerau_levenshtein_against_vec, jaro_against_vec,
|
||||
jaro_winkler_against_vec};
|
||||
|
||||
#[test]
|
||||
fn hamming_works() {
|
||||
match hamming("hamming", "hammers") {
|
||||
Ok(distance) => assert_eq!(3, distance),
|
||||
Err(why) => panic!("{:?}", why)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_works() {
|
||||
assert_eq!(3, levenshtein("kitten", "sitting"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_works() {
|
||||
assert_eq!(3, osa_distance("ac", "cba"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_works() {
|
||||
assert_eq!(2, damerau_levenshtein("ac", "cba"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_works() {
|
||||
assert!((0.392 - jaro("Friedrich Nietzsche", "Jean-Paul Sartre")).abs() <
|
||||
0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_works() {
|
||||
assert!((0.911 - jaro_winkler("cheeseburger", "cheese fries")).abs() <
|
||||
0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 2, 3];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 1, 3];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 1, 2];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
let delta: f64 = result.iter()
|
||||
.zip(expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(delta.abs() < 0.0001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
let delta: f64 = result.iter()
|
||||
.zip(expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(delta.abs() < 0.0001);
|
||||
}
|
|
@ -1 +1 @@
|
|||
{"files":{".editorconfig":"d87dec5ba57378b3c32bbf67526e434f7ac4f44d8b1acc7f44b6d8e7ee6919ad",".travis.yml":"10ee132e43d2a5e3c304a4970adfec8939014403c32d178728a56c8cb18046ee","CHANGELOG.md":"5f3e55f36768b2d2e40ee54702d6298175b7736e9b8918ca03592011f053c25f","Cargo.toml":"ff9b4b5bd80560ed36b14313261763d321cfb9e3f212896ec565fb872e26c3f0","LICENSE":"9b60c5838cbe7953b84bb2206835cb2bc898802da4c449e4442cf23e3ad4841e","README.md":"1778e287c6788197322a352721414f7ceab0c84ee0918513bb9f5382d3e40bc6","appveyor.yml":"b41eae9798a9bb250f6046509d9bbd6e63bac9ad2655d342b3d9c8975584f0c0","dev":"498932c7296d8abeb9e432e797a579787a85a052fa514325b873c4fe3200ec67","src/lib.rs":"a79164ec2334c477ca9d25e10c161e0e9bb8ae8670eb22976948c2a574b40985","tests/lib.rs":"43c707f9c9d35fafa07b34f61418ac877687daba5a0790d87e47b3d2c8bad274"},"package":"b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"}
|
||||
{"files":{".editorconfig":"d87dec5ba57378b3c32bbf67526e434f7ac4f44d8b1acc7f44b6d8e7ee6919ad",".travis.yml":"10ee132e43d2a5e3c304a4970adfec8939014403c32d178728a56c8cb18046ee","CHANGELOG.md":"1472e9bc914215d1c63d842e409e0b93d23a8b62b411ce09b7e3e5b2de212100","Cargo.toml":"41ef74c41992b1750d53de7878e1e200a36add38741303a98aa3e1321462097f","LICENSE":"1738b51502ae831fb59ffbeb22ebdd90bf17e5c72fe57c00b47552415f133fd8","README.md":"9cc6cae9758534e8cf3a0376f73983935fb7968463cd324bbef06328659f5030","appveyor.yml":"b41eae9798a9bb250f6046509d9bbd6e63bac9ad2655d342b3d9c8975584f0c0","dev":"5bd26dc2c86f777627abe96c5992b6c45e6b5dea52f42b7107fa5c106abe2ab4","src/lib.rs":"14b7916423df616e73974e984468584481bf84a8f7d24a0a3c392d48c5c711d2","tests/lib.rs":"eb2f829a86b54481ca97e17414fb1e4cec7c5e1cb27ead5913eea87d61f7f29f"},"package":"bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"}
|
|
@ -1,13 +1,28 @@
|
|||
# Change Log
|
||||
This project attempts to adhere to [Semantic Versioning](http://semver.org).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.7.0] - (2018-01-17)
|
||||
### Changed
|
||||
- Faster Levenshtein implementation (thanks @wdv4758h)
|
||||
|
||||
### Removed
|
||||
- Remove the "against_vec" functions. They are one-liners now, so they don't
|
||||
seem to add enough value to justify making the API larger. I didn't find
|
||||
anybody using them when I skimmed through a GitHub search. If you do use them,
|
||||
you can change the calls to something like:
|
||||
```rust
|
||||
let distances = strings.iter().map(|a| jaro(target, a)).collect();
|
||||
```
|
||||
|
||||
## [0.6.0] - (2016-12-26)
|
||||
### Added
|
||||
- Add optimal string alignment distance
|
||||
|
||||
### Fixed
|
||||
- Fix Damerau-Levenshtein implementation (previous implementation was actually
|
||||
optimal string alignment; see this [Damerau-Levenshtein explanation])
|
||||
optimal string alignment; see this [Damerau-Levenshtein explanation])
|
||||
|
||||
## [0.5.2] - (2016-11-21)
|
||||
### Changed
|
||||
|
@ -19,12 +34,12 @@ optimal string alignment; see this [Damerau-Levenshtein explanation])
|
|||
|
||||
### Fixed
|
||||
- Fix panic when Jaro or Jaro-Winkler are given strings both with a length of
|
||||
one
|
||||
one
|
||||
|
||||
## [0.5.0] - (2016-08-11)
|
||||
### Changed
|
||||
- Make Hamming faster (thanks @IBUzPE9) when the two strings have the same
|
||||
length but slower when they have different lengths
|
||||
length but slower when they have different lengths
|
||||
|
||||
## [0.4.1] - (2016-04-18)
|
||||
### Added
|
||||
|
@ -81,7 +96,8 @@ vector of results (thanks @ovarene)
|
|||
### Added
|
||||
- Implement Hamming, Jaro, Jaro-Winkler, and Levenshtein
|
||||
|
||||
[Unreleased]: https://github.com/dguo/strsim-rs/compare/0.6.0...HEAD
|
||||
[Unreleased]: https://github.com/dguo/strsim-rs/compare/0.7.0...HEAD
|
||||
[0.7.0]: https://github.com/dguo/strsim-rs/compare/0.6.0...0.7.0
|
||||
[0.6.0]: https://github.com/dguo/strsim-rs/compare/0.5.2...0.6.0
|
||||
[0.5.2]: https://github.com/dguo/strsim-rs/compare/0.5.1...0.5.2
|
||||
[0.5.1]: https://github.com/dguo/strsim-rs/compare/0.5.0...0.5.1
|
||||
|
@ -100,4 +116,3 @@ vector of results (thanks @ovarene)
|
|||
[docs.rs]: https://docs.rs/strsim/
|
||||
[Damerau-Levenshtein explanation]:
|
||||
http://scarcitycomputing.blogspot.com/2013/04/damerau-levenshtein-edit-distance.html
|
||||
|
||||
|
|
|
@ -1,17 +1,28 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g. crates.io) dependencies
|
||||
#
|
||||
# If you believe there's an error in this file please file an
|
||||
# issue against the rust-lang/cargo repository. If you're
|
||||
# editing this file be aware that the upstream Cargo.toml
|
||||
# will likely look very different (and much more reasonable)
|
||||
|
||||
[package]
|
||||
|
||||
name = "strsim"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
authors = ["Danny Guo <dannyguo91@gmail.com>"]
|
||||
description = """
|
||||
Implementations of string similarity metrics.
|
||||
Includes Hamming, Levenshtein, Damerau-Levenshtein, Jaro, and Jaro-Winkler.
|
||||
"""
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
keywords = ["string", "similarity", "Hamming",
|
||||
"Levenshtein", "Jaro"]
|
||||
description = "Implementations of string similarity metrics.\nIncludes Hamming, Levenshtein, OSA, Damerau-Levenshtein, Jaro, and Jaro-Winkler.\n"
|
||||
homepage = "https://github.com/dguo/strsim-rs"
|
||||
documentation = "https://docs.rs/strsim/"
|
||||
readme = "README.md"
|
||||
keywords = ["string", "similarity", "Hamming", "Levenshtein", "Jaro"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/dguo/strsim-rs"
|
||||
documentation = "http://dannyguo.com/strsim-rs/"
|
||||
[badges.travis-ci]
|
||||
repository = "dguo/strsim-rs"
|
||||
|
||||
[badges.appveyor]
|
||||
repository = "dguo/strsim-rs"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Danny Guo
|
||||
Copyright (c) 2016 Titus Wormer <tituswormer@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
```toml
|
||||
# Cargo.toml
|
||||
[dependencies]
|
||||
strsim = "0.6.0"
|
||||
strsim = "0.7.0"
|
||||
```
|
||||
|
||||
### [Documentation](https://docs.rs/strsim/)
|
||||
|
@ -24,9 +24,7 @@ version in the
|
|||
extern crate strsim;
|
||||
|
||||
use strsim::{hamming, levenshtein, osa_distance, damerau_levenshtein, jaro,
|
||||
jaro_winkler, levenshtein_against_vec, osa_distance_against_vec,
|
||||
damerau_levenshtein_against_vec, jaro_against_vec,
|
||||
jaro_winkler_against_vec};
|
||||
jaro_winkler};
|
||||
|
||||
fn main() {
|
||||
match hamming("hamming", "hammers") {
|
||||
|
@ -45,41 +43,12 @@ fn main() {
|
|||
|
||||
assert!((0.911 - jaro_winkler("cheeseburger", "cheese fries")).abs() <
|
||||
0.001);
|
||||
|
||||
// get vectors of values back
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
|
||||
assert_eq!(levenshtein_against_vec("test", &v),
|
||||
vec![0, 1, 2, 3, 4, 2, 3]);
|
||||
|
||||
assert_eq!(osa_distance_against_vec("test", &v),
|
||||
vec![0, 1, 2, 3, 4, 1, 3]);
|
||||
|
||||
assert_eq!(damerau_levenshtein_against_vec("test", &v),
|
||||
vec![0, 1, 2, 3, 4, 1, 2]);
|
||||
|
||||
let jaro_distances = jaro_against_vec("test", &v);
|
||||
let jaro_expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
let jaro_delta: f64 = jaro_distances.iter()
|
||||
.zip(jaro_expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(jaro_delta < 0.0001);
|
||||
|
||||
let jaro_winkler_distances = jaro_winkler_against_vec("test", &v);
|
||||
let jaro_winkler_expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
let jaro_winkler_delta = jaro_winkler_distances.iter()
|
||||
.zip(jaro_winkler_expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(jaro_winkler_delta < 0.0001);
|
||||
}
|
||||
```
|
||||
|
||||
### Development
|
||||
If you don't want to install Rust itself, you can install [Docker], and run
|
||||
`$ ./dev`. This should bring up a temporary container from which you can run
|
||||
[cargo] commands.
|
||||
If you don't want to install Rust itself, you can run `$ ./dev` for a
|
||||
development CLI if you have [Docker] installed.
|
||||
|
||||
### License
|
||||
[MIT](https://github.com/dguo/strsim-rs/blob/master/LICENSE)
|
||||
|
@ -91,5 +60,3 @@ If you don't want to install Rust itself, you can install [Docker], and run
|
|||
[Hamming]:http://en.wikipedia.org/wiki/Hamming_distance
|
||||
[Optimal string alignment]:https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance#Optimal_string_alignment_distance
|
||||
[Docker]:https://docs.docker.com/engine/installation/
|
||||
[cargo]:https://github.com/rust-lang/cargo
|
||||
|
||||
|
|
|
@ -1,14 +1,41 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
#!/usr/bin/env python3
|
||||
# ./dev --help
|
||||
|
||||
import argparse
|
||||
import os
|
||||
from subprocess import call
|
||||
from subprocess import run
|
||||
import sys
|
||||
|
||||
try:
|
||||
call(['docker', 'run', '-it', '--rm', '-v', os.getcwd() + ':/source',
|
||||
'jimmycuadra/rust:1.14.0'])
|
||||
except OSError:
|
||||
print('Please install Docker.', file=sys.stderr)
|
||||
parser = argparse.ArgumentParser(prog='./dev')
|
||||
subparsers = parser.add_subparsers(metavar='<command>', title='commands')
|
||||
command = [
|
||||
'docker', 'run', '-it', '--rm', '-v', os.getcwd() + ':/src:cached',
|
||||
'-w=/src', 'rust:1.21.0'
|
||||
]
|
||||
|
||||
def cargo(args, remaining):
|
||||
sys.exit(run(command + ['cargo'] + remaining or []).returncode)
|
||||
|
||||
parser_cargo = subparsers.add_parser('cargo', help='run a cargo command')
|
||||
parser_cargo.set_defaults(func=cargo)
|
||||
|
||||
def sh(args, remaining):
|
||||
sys.exit(run(command + ['bash']).returncode)
|
||||
|
||||
parser_sh = subparsers.add_parser('sh', help='bring up a shell')
|
||||
parser_sh.set_defaults(func=sh)
|
||||
|
||||
def test(args, remaining):
|
||||
sys.exit(run(command + ['cargo', 'test']).returncode)
|
||||
|
||||
parser_test = subparsers.add_parser('test', help='run tests')
|
||||
parser_test.set_defaults(func=test)
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
args, remaining = parser.parse_known_args()
|
||||
try:
|
||||
args.func(args, remaining)
|
||||
except FileNotFoundError:
|
||||
sys.exit('Please install Docker.')
|
||||
else:
|
||||
parser.print_help()
|
||||
|
|
|
@ -105,26 +105,6 @@ pub fn jaro(a: &str, b: &str) -> f64 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calculates the Jaro distance between a string and each string in a vector.
|
||||
/// Returns a vector of corresponding values between 0.0 and 1.0 (higher value
|
||||
/// means more similar).
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::jaro_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = jaro_against_vec("test", &v);
|
||||
/// let expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
/// let delta: f64 = result.iter()
|
||||
/// .zip(expected.iter())
|
||||
/// .map(|(x, y)| (x - y).abs() as f64)
|
||||
/// .fold(0.0, |x, y| x + y as f64);
|
||||
/// assert!(delta.abs() < 0.0001);
|
||||
/// ```
|
||||
pub fn jaro_against_vec(a: &str, v: &[&str]) -> Vec<f64> {
|
||||
v.iter().map(|b| jaro(a, b)).collect()
|
||||
}
|
||||
|
||||
/// Like Jaro but gives a boost to strings that have a common prefix.
|
||||
///
|
||||
/// ```
|
||||
|
@ -152,25 +132,6 @@ pub fn jaro_winkler(a: &str, b: &str) -> f64 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calculates the Jaro-Winkler distances between a string and each string
|
||||
/// in a vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::jaro_winkler_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = jaro_winkler_against_vec("test", &v);
|
||||
/// let expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
/// let delta: f64 = result.iter()
|
||||
/// .zip(expected.iter())
|
||||
/// .map(|(x, y)| (x - y).abs() as f64)
|
||||
/// .fold(0.0, |x, y| x + y as f64);
|
||||
/// assert!(delta.abs() < 0.0001);
|
||||
/// ```
|
||||
pub fn jaro_winkler_against_vec(a: &str, v: &[&str]) -> Vec<f64> {
|
||||
v.iter().map(|b| jaro_winkler(a, b)).collect()
|
||||
}
|
||||
|
||||
/// Calculates the minimum number of insertions, deletions, and substitutions
|
||||
/// required to change one string into the other.
|
||||
///
|
||||
|
@ -180,49 +141,34 @@ pub fn jaro_winkler_against_vec(a: &str, v: &[&str]) -> Vec<f64> {
|
|||
/// assert_eq!(3, levenshtein("kitten", "sitting"));
|
||||
/// ```
|
||||
pub fn levenshtein(a: &str, b: &str) -> usize {
|
||||
if a == b { return 0; }
|
||||
|
||||
let a_len = a.chars().count();
|
||||
let b_len = b.chars().count();
|
||||
if a == b { return 0; }
|
||||
else if a_len == 0 { return b_len; }
|
||||
else if b_len == 0 { return a_len; }
|
||||
|
||||
let mut prev_distances: Vec<usize> = Vec::with_capacity(b_len + 1);
|
||||
let mut curr_distances: Vec<usize> = Vec::with_capacity(b_len + 1);
|
||||
if a_len == 0 { return b_len; }
|
||||
if b_len == 0 { return a_len; }
|
||||
|
||||
for i in 0..(b_len + 1) {
|
||||
prev_distances.push(i);
|
||||
curr_distances.push(0);
|
||||
}
|
||||
let mut cache: Vec<usize> = (1..b_len+1).collect();
|
||||
|
||||
let mut result = 0;
|
||||
let mut distance_a;
|
||||
let mut distance_b;
|
||||
|
||||
for (i, a_char) in a.chars().enumerate() {
|
||||
curr_distances[0] = i + 1;
|
||||
result = i;
|
||||
distance_b = i;
|
||||
|
||||
for (j, b_char) in b.chars().enumerate() {
|
||||
let cost = if a_char == b_char { 0 } else { 1 };
|
||||
curr_distances[j + 1] = min(curr_distances[j] + 1,
|
||||
min(prev_distances[j + 1] + 1,
|
||||
prev_distances[j] + cost));
|
||||
distance_a = distance_b + cost;
|
||||
distance_b = cache[j];
|
||||
result = min(result + 1, min(distance_a, distance_b + 1));
|
||||
cache[j] = result;
|
||||
}
|
||||
|
||||
prev_distances.clone_from(&curr_distances);
|
||||
}
|
||||
|
||||
curr_distances[b_len]
|
||||
}
|
||||
|
||||
/// Calculates the Levenshtein distance between a string and each string in a
|
||||
/// vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::levenshtein_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = levenshtein_against_vec("test", &v);
|
||||
/// let expected = vec![0, 1, 2, 3, 4, 2];
|
||||
/// assert_eq!(expected, result);
|
||||
/// ```
|
||||
pub fn levenshtein_against_vec(a: &str, v: &[&str]) -> Vec<usize> {
|
||||
v.iter().map(|b| levenshtein(a, b)).collect()
|
||||
result
|
||||
}
|
||||
|
||||
/// Like Levenshtein but allows for adjacent transpositions. Each substring can
|
||||
|
@ -279,21 +225,6 @@ pub fn osa_distance(a: &str, b: &str) -> usize {
|
|||
|
||||
}
|
||||
|
||||
/// Calculates the optimal string alignment distance between a string and each
|
||||
/// string in a vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::osa_distance_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = osa_distance_against_vec("test", &v);
|
||||
/// let expected = vec![0, 1, 2, 3, 4, 1];
|
||||
/// assert_eq!(expected, result);
|
||||
/// ```
|
||||
pub fn osa_distance_against_vec(a: &str, v: &[&str]) -> Vec<usize> {
|
||||
v.iter().map(|b| osa_distance(a, b)).collect()
|
||||
}
|
||||
|
||||
/// Like optimal string alignment, but substrings can be edited an unlimited
|
||||
/// number of times, and the triangle inequality holds.
|
||||
///
|
||||
|
@ -364,21 +295,6 @@ pub fn damerau_levenshtein(a: &str, b: &str) -> usize {
|
|||
distances[a_len + 1][b_len + 1]
|
||||
}
|
||||
|
||||
/// Calculates the Damerau-Levenshtein distance between a string and each string
|
||||
/// in a vector. Returns a vector of corresponding values.
|
||||
///
|
||||
/// ```
|
||||
/// use strsim::damerau_levenshtein_against_vec;
|
||||
///
|
||||
/// let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
/// let result = damerau_levenshtein_against_vec("test", &v);
|
||||
/// let expected = vec![0, 1, 2, 3, 4, 1];
|
||||
/// assert_eq!(expected, result);
|
||||
/// ```
|
||||
pub fn damerau_levenshtein_against_vec(a: &str, v: &[&str]) -> Vec<usize> {
|
||||
v.iter().map(|b| damerau_levenshtein(a, b)).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -779,133 +695,4 @@ mod tests {
|
|||
fn damerau_levenshtein_unrestricted_edit() {
|
||||
assert_eq!(3, damerau_levenshtein("a cat", "an abct"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected: Vec<usize> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_one() {
|
||||
let v = vec!["testy"];
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected = vec![1];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 2];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected: Vec<usize> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_one() {
|
||||
let v = vec!["etst"];
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected = vec![1];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tsvet"];
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 3];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected: Vec<usize> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_one() {
|
||||
let v = vec!["etst"];
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected = vec![1];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tsvet"];
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 2];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
fn equal_float_vecs(a: Vec<f64>, b: Vec<f64>) -> bool {
|
||||
let delta: f64 = a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
delta < 0.0001
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected: Vec<f64> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_one() {
|
||||
let v = vec!["test1"];
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected = vec![0.93333];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_empty() {
|
||||
let v = Vec::new();
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected: Vec<f64> = Vec::new();
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_one() {
|
||||
let v = vec!["test123"];
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected = vec![0.914286];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_many() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
assert!(equal_float_vecs(result, expected));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
extern crate strsim;
|
||||
|
||||
use strsim::{hamming, levenshtein, osa_distance, damerau_levenshtein, jaro,
|
||||
jaro_winkler, levenshtein_against_vec, osa_distance_against_vec,
|
||||
damerau_levenshtein_against_vec, jaro_against_vec,
|
||||
jaro_winkler_against_vec};
|
||||
jaro_winkler};
|
||||
|
||||
#[test]
|
||||
fn hamming_works() {
|
||||
|
@ -39,51 +37,3 @@ fn jaro_winkler_works() {
|
|||
assert!((0.911 - jaro_winkler("cheeseburger", "cheese fries")).abs() <
|
||||
0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn levenshtein_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
let result = levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 2, 3];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osa_distance_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
let result = osa_distance_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 1, 3];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn damerau_levenshtein_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset", "tsvet"];
|
||||
let result = damerau_levenshtein_against_vec("test", &v);
|
||||
let expected = vec![0, 1, 2, 3, 4, 1, 2];
|
||||
assert_eq!(expected, result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.933333, 0.888889, 0.857143, 0.0, 0.916667];
|
||||
let delta: f64 = result.iter()
|
||||
.zip(expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(delta.abs() < 0.0001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn jaro_winkler_against_vec_works() {
|
||||
let v = vec!["test", "test1", "test12", "test123", "", "tset"];
|
||||
let result = jaro_winkler_against_vec("test", &v);
|
||||
let expected = vec![1.0, 0.96, 0.933333, 0.914286, 0.0, 0.925];
|
||||
let delta: f64 = result.iter()
|
||||
.zip(expected.iter())
|
||||
.map(|(x, y)| (x - y).abs() as f64)
|
||||
.fold(0.0, |x, y| x + y as f64);
|
||||
assert!(delta.abs() < 0.0001);
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче