Initial vendor packages

Signed-off-by: Valentin Popov <valentin@popov.link>
This commit is contained in:
2024-01-08 01:21:28 +04:00
parent 5ecd8cf2cb
commit 1b6a04ca55
7309 changed files with 2160054 additions and 0 deletions

1
vendor/textwrap/.cargo-checksum.json vendored Normal file
View File

@ -0,0 +1 @@
{"files":{"CHANGELOG.md":"747d890e4263b4e6da0515431d8937ae14f37727b5c63553c8711d73ab3e200f","Cargo.lock":"97320491b399949fbe3391b141ea3157bb5b5353d7b4a9a2182464da0ec5fa5a","Cargo.toml":"dd021e90e9d1618c6535ae34e00c4303b301d0f3ad0c6886cf36b844b49ca0da","LICENSE":"ce93600c49fbb3e14df32efe752264644f6a2f8e08a735ba981725799e5309ef","README.md":"7711269f7654bdd11c1a183c4f4213cfe63416c55a393c270d57e7bee19de4ac","rustfmt.toml":"02637ad90caa19885b25b1ce8230657b3703402775d9db83687a3f55de567509","src/core.rs":"533b1516778553d01b6e3ec5962877b38605545a5041cbfffdd265a93e7de3af","src/indentation.rs":"49896f6e166f08a6f57f71a5d1bf706342c25e8410aa301fad590e001eb49799","src/lib.rs":"19eed9364c90486114155e7da1ec2221645bff0a3aecdf88f0933809720afb43","src/word_separators.rs":"0931ef25d1340b4295cb4f1ff075de26d8dee48eafcc96d9ed11eab66f74d28f","src/word_splitters.rs":"5a3a601414433227aff009d721fa60a94a28a0c7501b54bbbecedda9a2add3ba","src/wrap_algorithms.rs":"277216193595d85d464d3888d79c937a2265f89b8cf51077e078ff1aa1281dbf","src/wrap_algorithms/optimal_fit.rs":"5ae852cdafbd7926042ee0336dae30b88ba62322604f3040d49ed8b9380e68d4","tests/indent.rs":"51f977db11632a32fafecf86af88413d51238fe6efcf18ec52fac89133714278","tests/version-numbers.rs":"9e964f58dbdf051fc6fe0d6542ab312d3e95f26c3fd14bce84449bb625e45761"},"package":"b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"}

574
vendor/textwrap/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,574 @@
# Changelog
This file lists the most important changes made in each release of
`textwrap`.
## Version 0.15.2 (2022-10-24)
This release is identical to 0.15.0 and is only there to give people a
way to install crates which depend on the yanked 0.15.1 release. See
[#484](https://github.com/mgeisler/textwrap/issues/484) for details.
## Version 0.15.0 (2022-02-27)
This is a major feature release with two main changes:
* [#421](https://github.com/mgeisler/textwrap/pull/421): Use `f64`
instead of `usize` for fragment widths.
This fixes problems with overflows in the internal computations of
`wrap_optimal_fit` when fragments (words) or line lenghts had
extreme values, such as `usize::MAX`.
* [#438](https://github.com/mgeisler/textwrap/pull/438): Simplify
`Options` by removing generic type parameters.
This change removes the new generic parameters introduced in version
0.14, as well as the original `WrapSplitter` parameter which has
been present since very early versions.
The result is a simplification of function and struct signatures
across the board. So what used to be
```rust
let options: Options<
wrap_algorithms::FirstFit,
word_separators::AsciiSpace,
word_splitters::HyphenSplitter,
> = Options::new(80);
```
if types are fully written out, is now simply
```rust
let options: Options<'_> = Options::new(80);
```
The anonymous lifetime represent the lifetime of the
`initial_indent` and `subsequent_indent` strings. The change is
nearly performance neutral (a 1-2% regression).
Smaller improvements and changes:
* [#404](https://github.com/mgeisler/textwrap/pull/404): Make
documentation for short last-line penalty more precise.
* [#405](https://github.com/mgeisler/textwrap/pull/405): Cleanup and
simplify `Options` docstring.
* [#411](https://github.com/mgeisler/textwrap/pull/411): Default to
`OptimalFit` in interactive example.
* [#415](https://github.com/mgeisler/textwrap/pull/415): Add demo
program to help compute binary sizes.
* [#423](https://github.com/mgeisler/textwrap/pull/423): Add fuzz
tests with fully arbitrary fragments.
* [#424](https://github.com/mgeisler/textwrap/pull/424): Change
`wrap_optimal_fit` penalties to non-negative numbers.
* [#430](https://github.com/mgeisler/textwrap/pull/430): Add
`debug-words` example.
* [#432](https://github.com/mgeisler/textwrap/pull/432): Use precise
dependency versions in Cargo.toml.
## Version 0.14.2 (2021-06-27)
The 0.14.1 release included more changes than intended and has been
yanked. The change intended for 0.14.1 is now included in 0.14.2.
## Version 0.14.1 (2021-06-26)
This release fixes a panic reported by @Makoto, thanks!
* [#391](https://github.com/mgeisler/textwrap/pull/391): Fix panic in
`find_words` due to string access outside of a character boundary.
## Version 0.14.0 (2021-06-05)
This is a major feature release which makes Textwrap more configurable
and flexible. The high-level API of `textwrap::wrap` and
`textwrap::fill` remains unchanged, but low-level structs have moved
around.
The biggest change is the introduction of new generic type parameters
to the `Options` struct. These parameters lets you statically
configure the wrapping algorithm, the word separator, and the word
splitter. If you previously spelled out the full type for `Options`,
you now need to take the extra type parameters into account. This
means that
```rust
let options: Options<HyphenSplitter> = Options::new(80);
```
changes to
```rust
let options: Options<
wrap_algorithms::FirstFit,
word_separators::AsciiSpace,
word_splitters::HyphenSplitter,
> = Options::new(80);
```
This is quite a mouthful, so we suggest using type inferrence where
possible. You wont see any chance if you call `wrap` directly with a
width or with an `Options` value constructed on the fly. Please open
an issue if this causes problems for you!
### New `WordSeparator` Trait
* [#332](https://github.com/mgeisler/textwrap/pull/332): Add
`WordSeparator` trait to allow customizing how words are found in a
line of text. Until now, Textwrap would always assume that words are
separated by ASCII space characters. You can now customize this as
needed.
* [#313](https://github.com/mgeisler/textwrap/pull/313): Add support
for using the Unicode line breaking algorithm to find words. This is
done by adding a second implementation of the new `WordSeparator`
trait. The implementation uses the unicode-linebreak crate, which is
a new optional dependency.
With this, Textwrap can be used with East-Asian languages such as
Chinese or Japanese where there are no spaces between words.
Breaking a long sequence of emojis is another example where line
breaks might be wanted even if there are no whitespace to be found.
Feedback would be appreciated for this feature.
### Indent
* [#353](https://github.com/mgeisler/textwrap/pull/353): Trim trailing
whitespace from `prefix` in `indent`.
Before, empty lines would get no prefix added. Now, empty lines have
a trimmed prefix added. This little trick makes `indent` much more
useful since you can now safely indent with `"# "` without creating
trailing whitespace in the output due to the trailing whitespace in
your prefix.
* [#354](https://github.com/mgeisler/textwrap/pull/354): Make `indent`
about 20% faster by preallocating the output string.
### Documentation
* [#308](https://github.com/mgeisler/textwrap/pull/308): Document
handling of leading and trailing whitespace when wrapping text.
### WebAssembly Demo
* [#310](https://github.com/mgeisler/textwrap/pull/310): Thanks to
WebAssembly, you can now try out Textwrap directly in your browser.
Please try it out: https://mgeisler.github.io/textwrap/.
### New Generic Parameters
* [#331](https://github.com/mgeisler/textwrap/pull/331): Remove outer
boxing from `Options`.
* [#357](https://github.com/mgeisler/textwrap/pull/357): Replace
`core::WrapAlgorithm` enum with a `wrap_algorithms::WrapAlgorithm`
trait. This allows for arbitrary wrapping algorithms to be plugged
into the library.
* [#358](https://github.com/mgeisler/textwrap/pull/358): Switch
wrapping functions to use a slice for `line_widths`.
* [#368](https://github.com/mgeisler/textwrap/pull/368): Move
`WordSeparator` and `WordSplitter` traits to separate modules.
Before, Textwrap had several top-level structs such as
`NoHyphenation` and `HyphenSplitter`. These implementations of
`WordSplitter` now lives in a dedicated `word_splitters` module.
Similarly, we have a new `word_separators` module for
implementations of `WordSeparator`.
* [#369](https://github.com/mgeisler/textwrap/pull/369): Rename
`Options::splitter` to `Options::word_splitter` for consistency with
the other fields backed by traits.
## Version 0.13.4 (2021-02-23)
This release removes `println!` statements which was left behind in
`unfill` by mistake.
* [#296](https://github.com/mgeisler/textwrap/pull/296): Improve house
building example with more comments.
* [#297](https://github.com/mgeisler/textwrap/pull/297): Remove debug
prints in the new `unfill` function.
## Version 0.13.3 (2021-02-20)
This release contains a bugfix for `indent` and improved handling of
emojis. Weve also added a new function for formatting text in columns
and functions for reformatting already wrapped text.
* [#276](https://github.com/mgeisler/textwrap/pull/276): Extend
`core::display_width` to handle emojis when the unicode-width Cargo
feature is disabled.
* [#279](https://github.com/mgeisler/textwrap/pull/279): Make `indent`
preserve existing newlines in the input string. Before,
`indent("foo", "")` would return `"foo\n"` by mistake. It now
returns `"foo"` instead.
* [#281](https://github.com/mgeisler/textwrap/pull/281): Ensure all
`Options` fields have examples.
* [#282](https://github.com/mgeisler/textwrap/pull/282): Add a
`wrap_columns` function.
* [#294](https://github.com/mgeisler/textwrap/pull/294): Add new
`unfill` and `refill` functions.
## Version 0.13.2 (2020-12-30)
This release primarily makes all dependencies optional. This makes it
possible to slim down textwrap as needed.
* [#254](https://github.com/mgeisler/textwrap/pull/254): `impl
WordSplitter` for `Box<T> where T: WordSplitter`.
* [#255](https://github.com/mgeisler/textwrap/pull/255): Use command
line arguments as initial text in interactive example.
* [#256](https://github.com/mgeisler/textwrap/pull/256): Introduce
fuzz tests for `wrap_optimal_fit` and `wrap_first_fit`.
* [#260](https://github.com/mgeisler/textwrap/pull/260): Make the
unicode-width dependency optional.
* [#261](https://github.com/mgeisler/textwrap/pull/261): Make the
smawk dependency optional.
## Version 0.13.1 (2020-12-10)
This is a bugfix release which fixes a regression in 0.13.0. The bug
meant that colored text was wrapped incorrectly.
* [#245](https://github.com/mgeisler/textwrap/pull/245): Support
deleting a word with Ctrl-Backspace in the interactive demo.
* [#246](https://github.com/mgeisler/textwrap/pull/246): Show build
type (debug/release) in interactive demo.
* [#249](https://github.com/mgeisler/textwrap/pull/249): Correctly
compute width while skipping over ANSI escape sequences.
## Version 0.13.0 (2020-12-05)
This is a major release which rewrites the core logic, adds many new
features, and fixes a couple of bugs. Most programs which use
`textwrap` stays the same, incompatibilities and upgrade notes are
given below.
Clone the repository and run the following to explore the new features
in an interactive demo (Linux only):
```sh
$ cargo run --example interactive --all-features
```
### Bug Fixes
#### Rewritten core wrapping algorithm
* [#221](https://github.com/mgeisler/textwrap/pull/221): Reformulate
wrapping in terms of words with whitespace and penalties.
The core wrapping algorithm has been completely rewritten. This fixed
bugs and simplified the code, while also making it possible to use
`textwrap` outside the context of the terminal.
As part of this, trailing whitespace is now discarded consistently
from wrapped lines. Before we would inconsistently remove whitespace
at the end of wrapped lines, except for the last. Leading whitespace
is still preserved.
### New Features
#### Optimal-fit wrapping
* [#234](https://github.com/mgeisler/textwrap/pull/234): Introduce
wrapping using an optimal-fit algorithm.
This release adds support for new wrapping algorithm which finds a
globally optimal set of line breaks, taking certain penalties into
account. As an example, the old algorithm would produce
"To be, or"
"not to be:"
"that is"
"the"
"question"
Notice how the fourth line with “the” is very short. The new algorithm
shortens the previous lines slightly to produce fewer short lines:
"To be,"
"or not to"
"be: that"
"is the"
"question"
Use the new `textwrap::core::WrapAlgorithm` enum to select between the
new and old algorithm. By default, the new algorithm is used.
The optimal-fit algorithm is inspired by the line breaking algorithm
used in TeX, described in the 1981 article [_Breaking Paragraphs into
Lines_](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf) by
Knuth and Plass.
#### In-place wrapping
* [#226](https://github.com/mgeisler/textwrap/pull/226): Add a
`fill_inplace` function.
When the text you want to fill is already a temporary `String`, you
can now mutate it in-place with `fill_inplace`:
```rust
let mut greeting = format!("Greetings {}, welcome to the game! You have {} lives left.",
player.name, player.lives);
fill_inplace(&mut greeting, line_width);
```
This is faster than calling `fill` and it will reuse the memory
already allocated for the string.
### Changed Features
#### `Wrapper` is replaced with `Options`
* [#213](https://github.com/mgeisler/textwrap/pull/213): Simplify API
with only top-level functions.
* [#215](https://github.com/mgeisler/textwrap/pull/215): Reintroducing
the type parameter on `Options` (previously known as `Wrapper`).
* [#219](https://github.com/mgeisler/textwrap/pull/219): Allow using
trait objects with `fill` & `wrap`.
* [#227](https://github.com/mgeisler/textwrap/pull/227): Replace
`WrapOptions` with `Into<Options>`.
The `Wrapper` struct held the options (line width, indentation, etc)
for wrapping text. It was also the entry point for actually wrapping
the text via its methods such as `wrap`, `wrap_iter`,
`into_wrap_iter`, and `fill` methods.
The struct has been replaced by a simpler `Options` struct which only
holds options. The `Wrapper` methods are gone, their job has been
taken over by the top-level `wrap` and `fill` functions. The signature
of these functions have changed from
```rust
fn fill(s: &str, width: usize) -> String;
fn wrap(s: &str, width: usize) -> Vec<Cow<'_, str>>;
```
to the more general
```rust
fn fill<'a, S, Opt>(text: &str, options: Opt) -> String
where
S: WordSplitter,
Opt: Into<Options<'a, S>>;
fn wrap<'a, S, Opt>(text: &str, options: Opt) -> Vec<Cow<'_, str>>
where
S: WordSplitter,
Opt: Into<Options<'a, S>>;
```
The `Into<Options<'a, S>` bound allows you to pass an `usize` (which
is interpreted as the line width) *and* a full `Options` object. This
allows the new functions to work like the old, plus you can now fully
customize the behavior of the wrapping via `Options` when needed.
Code that call `textwrap::wrap` or `textwrap::fill` can remain
unchanged. Code that calls into `Wrapper::wrap` or `Wrapper::fill`
will need to be update. This is a mechanical change, please see
[#213](https://github.com/mgeisler/textwrap/pull/213) for examples.
Thanks to @CryptJar and @Koxiat for their support in the PRs above!
### Removed Features
* The `wrap_iter` and `into_wrap_iter` methods are gone. This means
that lazy iteration is no longer supported: you always get all
wrapped lines back as a `Vec`. This was done to simplify the code
and to support the optimal-fit algorithm.
The first-fit algorithm could still be implemented in an incremental
fashion. Please let us know if this is important to you.
### Other Changes
* [#206](https://github.com/mgeisler/textwrap/pull/206): Change
`Wrapper.splitter` from `T: WordSplitter` to `Box<dyn
WordSplitter>`.
* [#216](https://github.com/mgeisler/textwrap/pull/216): Forbid the
use of unsafe code.
## Version 0.12.1 (2020-07-03)
This is a bugfix release.
* Fixed [#176][issue-176]: Mention compile-time wrapping by linking to
the [`textwrap-macros` crate].
* Fixed [#193][issue-193]: Wrapping with `break_words(false)` was
broken and would cause extra whitespace to be inserted when words
were longer than the line width.
## Version 0.12.0 (2020-06-26)
The code has been updated to the [Rust 2018 edition][rust-2018] and
each new release of `textwrap` will only support the latest stable
version of Rust. Trying to support older Rust versions is a fool's
errand: our dependencies keep releasing new patch versions that
require newer and newer versions of Rust.
The `term_size` feature has been replaced by `terminal_size`. The API
is unchanged, it is just the name of the Cargo feature that changed.
The `hyphenation` feature now only embeds the hyphenation patterns for
US-English. This slims down the dependency.
* Fixed [#140][issue-140]: Ignore ANSI escape sequences.
* Fixed [#158][issue-158]: Unintended wrapping when using external splitter.
* Fixed [#177][issue-177]: Update examples to the 2018 edition.
## Version 0.11.0 (2018-12-09)
Due to our dependencies bumping their minimum supported version of
Rust, the minimum version of Rust we test against is now 1.22.0.
* Merged [#141][issue-141]: Fix `dedent` handling of empty lines and
trailing newlines. Thanks @bbqsrc!
* Fixed [#151][issue-151]: Release of version with hyphenation 0.7.
## Version 0.10.0 (2018-04-28)
Due to our dependencies bumping their minimum supported version of
Rust, the minimum version of Rust we test against is now 1.17.0.
* Fixed [#99][issue-99]: Word broken even though it would fit on line.
* Fixed [#107][issue-107]: Automatic hyphenation is off by one.
* Fixed [#122][issue-122]: Take newlines into account when wrapping.
* Fixed [#129][issue-129]: Panic on string with em-dash.
## Version 0.9.0 (2017-10-05)
The dependency on `term_size` is now optional, and by default this
feature is not enabled. This is a *breaking change* for users of
`Wrapper::with_termwidth`. Enable the `term_size` feature to restore
the old functionality.
Added a regression test for the case where `width` is set to
`usize::MAX`, thanks @Fraser999! All public structs now implement
`Debug`, thanks @hcpl!
* Fixed [#101][issue-101]: Make `term_size` an optional dependency.
## Version 0.8.0 (2017-09-04)
The `Wrapper` stuct is now generic over the type of word splitter
being used. This means less boxing and a nicer API. The
`Wrapper::word_splitter` method has been removed. This is a *breaking
API change* if you used the method to change the word splitter.
The `Wrapper` struct has two new methods that will wrap the input text
lazily: `Wrapper::wrap_iter` and `Wrapper::into_wrap_iter`. Use those
if you will be iterating over the wrapped lines one by one.
* Fixed [#59][issue-59]: `wrap` could return an iterator. Thanks
@hcpl!
* Fixed [#81][issue-81]: Set `html_root_url`.
## Version 0.7.0 (2017-07-20)
Version 0.7.0 changes the return type of `Wrapper::wrap` from
`Vec<String>` to `Vec<Cow<'a, str>>`. This means that the output lines
borrow data from the input string. This is a *breaking API change* if
you relied on the exact return type of `Wrapper::wrap`. Callers of the
`textwrap::fill` convenience function will see no breakage.
The above change and other optimizations makes version 0.7.0 roughly
15-30% faster than version 0.6.0.
The `squeeze_whitespace` option has been removed since it was
complicating the above optimization. Let us know if this option is
important for you so we can provide a work around.
* Fixed [#58][issue-58]: Add a "fast_wrap" function.
* Fixed [#61][issue-61]: Documentation errors.
## Version 0.6.0 (2017-05-22)
Version 0.6.0 adds builder methods to `Wrapper` for easy one-line
initialization and configuration:
```rust
let wrapper = Wrapper::new(60).break_words(false);
```
It also add a new `NoHyphenation` word splitter that will never split
words, not even at existing hyphens.
* Fixed [#28][issue-28]: Support not squeezing whitespace.
## Version 0.5.0 (2017-05-15)
Version 0.5.0 has *breaking API changes*. However, this only affects
code using the hyphenation feature. The feature is now optional, so
you will first need to enable the `hyphenation` feature as described
above. Afterwards, please change your code from
```rust
wrapper.corpus = Some(&corpus);
```
to
```rust
wrapper.splitter = Box::new(corpus);
```
Other changes include optimizations, so version 0.5.0 is roughly
10-15% faster than version 0.4.0.
* Fixed [#19][issue-19]: Add support for finding terminal size.
* Fixed [#25][issue-25]: Handle words longer than `self.width`.
* Fixed [#26][issue-26]: Support custom indentation.
* Fixed [#36][issue-36]: Support building without `hyphenation`.
* Fixed [#39][issue-39]: Respect non-breaking spaces.
## Version 0.4.0 (2017-01-24)
Documented complexities and tested these via `cargo bench`.
* Fixed [#13][issue-13]: Immediatedly add word if it fits.
* Fixed [#14][issue-14]: Avoid splitting on initial hyphens.
## Version 0.3.0 (2017-01-07)
Added support for automatic hyphenation.
## Version 0.2.0 (2016-12-28)
Introduced `Wrapper` struct. Added support for wrapping on hyphens.
## Version 0.1.0 (2016-12-17)
First public release with support for wrapping strings on whitespace.
[rust-2018]: https://doc.rust-lang.org/edition-guide/rust-2018/
[`textwrap-macros` crate]: https://crates.io/crates/textwrap-macros
[issue-13]: https://github.com/mgeisler/textwrap/issues/13
[issue-14]: https://github.com/mgeisler/textwrap/issues/14
[issue-19]: https://github.com/mgeisler/textwrap/issues/19
[issue-25]: https://github.com/mgeisler/textwrap/issues/25
[issue-26]: https://github.com/mgeisler/textwrap/issues/26
[issue-28]: https://github.com/mgeisler/textwrap/issues/28
[issue-36]: https://github.com/mgeisler/textwrap/issues/36
[issue-39]: https://github.com/mgeisler/textwrap/issues/39
[issue-58]: https://github.com/mgeisler/textwrap/issues/58
[issue-59]: https://github.com/mgeisler/textwrap/issues/59
[issue-61]: https://github.com/mgeisler/textwrap/issues/61
[issue-81]: https://github.com/mgeisler/textwrap/issues/81
[issue-99]: https://github.com/mgeisler/textwrap/issues/99
[issue-101]: https://github.com/mgeisler/textwrap/issues/101
[issue-107]: https://github.com/mgeisler/textwrap/issues/107
[issue-122]: https://github.com/mgeisler/textwrap/issues/122
[issue-129]: https://github.com/mgeisler/textwrap/issues/129
[issue-140]: https://github.com/mgeisler/textwrap/issues/140
[issue-141]: https://github.com/mgeisler/textwrap/issues/141
[issue-151]: https://github.com/mgeisler/textwrap/issues/151
[issue-158]: https://github.com/mgeisler/textwrap/issues/158
[issue-176]: https://github.com/mgeisler/textwrap/issues/176
[issue-177]: https://github.com/mgeisler/textwrap/issues/177
[issue-193]: https://github.com/mgeisler/textwrap/issues/193

966
vendor/textwrap/Cargo.lock generated vendored Normal file
View File

@ -0,0 +1,966 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"lazy_static",
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"bitflags",
"textwrap 0.11.0",
"unicode-width",
]
[[package]]
name = "criterion"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f"
dependencies = [
"atty",
"cast",
"clap",
"criterion-plot",
"csv",
"itertools",
"lazy_static",
"num-traits",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_cbor",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac"
dependencies = [
"cfg-if",
]
[[package]]
name = "csv"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
dependencies = [
"bstr",
"csv-core",
"itoa 0.4.8",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "either"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "form_urlencoded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
"percent-encoding",
]
[[package]]
name = "fst"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ab85b9b05e3978cc9a9cf8fea7f01b494e1a09ed3037e16ba39edc7a29eb61a"
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "half"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hyphenation"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcf4dd4c44ae85155502a52c48739c8a48185d1449fff1963cffee63c28a50f0"
dependencies = [
"bincode",
"fst",
"hyphenation_commons",
"pocket-resources",
"serde",
]
[[package]]
name = "hyphenation_commons"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5febe7a2ade5c7d98eb8b75f946c046b335324b06a14ea0998271504134c05bf"
dependencies = [
"fst",
"serde",
]
[[package]]
name = "idna"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "lipsum"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8451846f1f337e44486666989fbce40be804da139d5a4477d6b88ece5dc69f4"
dependencies = [
"rand",
"rand_chacha",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "once_cell"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "oorandom"
version = "11.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "percent-encoding"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "plotters"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
dependencies = [
"plotters-backend",
]
[[package]]
name = "pocket-resources"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c135f38778ad324d9e9ee68690bac2c1a51f340fdf96ca13e2ab3914eb2e51d8"
[[package]]
name = "ppv-lite86"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro2"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pulldown-cmark"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8"
dependencies = [
"bitflags",
"memchr",
"unicase",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "ryu"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "semver"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4"
[[package]]
name = "serde"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_cbor"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5"
dependencies = [
"half",
"serde",
]
[[package]]
name = "serde_derive"
version = "1.0.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45"
dependencies = [
"itoa 1.0.4",
"ryu",
"serde",
]
[[package]]
name = "smawk"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]]
name = "syn"
version = "1.0.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "terminal_size"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.15.2"
dependencies = [
"criterion",
"hyphenation",
"lipsum",
"smawk",
"terminal_size",
"termion",
"unic-emoji-char",
"unicode-linebreak",
"unicode-width",
"version-sync",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
"serde",
]
[[package]]
name = "unic-char-property"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
dependencies = [
"unic-char-range",
]
[[package]]
name = "unic-char-range"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
[[package]]
name = "unic-common"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
[[package]]
name = "unic-emoji-char"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d"
dependencies = [
"unic-char-property",
"unic-char-range",
"unic-ucd-version",
]
[[package]]
name = "unic-ucd-version"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
dependencies = [
"unic-common",
]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "unicode-linebreak"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137"
dependencies = [
"hashbrown",
"regex",
]
[[package]]
name = "unicode-normalization"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "url"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
[[package]]
name = "version-sync"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d0801cec07737d88cb900e6419f6f68733867f90b3faaa837e84692e101bf0"
dependencies = [
"proc-macro2",
"pulldown-cmark",
"regex",
"semver",
"syn",
"toml",
"url",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "web-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

106
vendor/textwrap/Cargo.toml vendored Normal file
View File

@ -0,0 +1,106 @@
# 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 are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
name = "textwrap"
version = "0.15.2"
authors = ["Martin Geisler <martin@geisler.net>"]
exclude = [
".github/",
".gitignore",
"benches/",
"examples/",
"fuzz/",
"images/",
]
description = "Powerful library for word wrapping, indenting, and dedenting strings"
documentation = "https://docs.rs/textwrap/"
readme = "README.md"
keywords = [
"text",
"formatting",
"wrap",
"typesetting",
"hyphenation",
]
categories = [
"text-processing",
"command-line-interface",
]
license = "MIT"
repository = "https://github.com/mgeisler/textwrap"
[package.metadata.docs.rs]
all-features = true
[[example]]
name = "hyphenation"
path = "examples/hyphenation.rs"
required-features = ["hyphenation"]
[[example]]
name = "termwidth"
path = "examples/termwidth.rs"
required-features = ["terminal_size"]
[[bench]]
name = "linear"
path = "benches/linear.rs"
harness = false
[[bench]]
name = "indent"
path = "benches/indent.rs"
harness = false
[dependencies.hyphenation]
version = "0.8.4"
features = ["embed_en-us"]
optional = true
[dependencies.smawk]
version = "0.3.1"
optional = true
[dependencies.terminal_size]
version = "0.1.17"
optional = true
[dependencies.unicode-linebreak]
version = "0.1.2"
optional = true
[dependencies.unicode-width]
version = "0.1.9"
optional = true
[dev-dependencies.criterion]
version = "0.3.5"
[dev-dependencies.lipsum]
version = "0.8.0"
[dev-dependencies.unic-emoji-char]
version = "0.9.0"
[dev-dependencies.version-sync]
version = "0.9.4"
[features]
default = [
"unicode-linebreak",
"unicode-width",
"smawk",
]
[target."cfg(unix)".dev-dependencies.termion]
version = "1.5.6"

21
vendor/textwrap/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Martin Geisler
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.

176
vendor/textwrap/README.md vendored Normal file
View File

@ -0,0 +1,176 @@
# Textwrap
[![](https://github.com/mgeisler/textwrap/workflows/build/badge.svg)][build-status]
[![](https://codecov.io/gh/mgeisler/textwrap/branch/master/graph/badge.svg)][codecov]
[![](https://img.shields.io/crates/v/textwrap.svg)][crates-io]
[![](https://docs.rs/textwrap/badge.svg)][api-docs]
Textwrap is a library for wrapping and indenting text. It is most
often used by command-line programs to format dynamic output nicely so
it looks good in a terminal. You can also use Textwrap to wrap text
set in a proportional font—such as text used to generate PDF files, or
drawn on a [HTML5 canvas using WebAssembly][wasm-demo].
## Usage
To use the textwrap crate, add this to your `Cargo.toml` file:
```toml
[dependencies]
textwrap = "0.15"
```
By default, this enables word wrapping with support for Unicode
strings. Extra features can be enabled with Cargo features—and the
Unicode support can be disabled if needed. This allows you slim down
the library and so you will only pay for the features you actually
use.
Please see the [_Cargo Features_ in the crate
documentation](https://docs.rs/textwrap/#cargo-features) for a full
list of the available features as well as their impact on the size of
your binary.
## Documentation
**[API documentation][api-docs]**
## Getting Started
Word wrapping is easy using the `wrap` and `fill` functions:
```rust
#[cfg(feature = "smawk")] {
let text = "textwrap: an efficient and powerful library for wrapping text.";
assert_eq!(
textwrap::wrap(text, 28),
vec![
"textwrap: an efficient",
"and powerful library for",
"wrapping text.",
]
);
}
```
Sharp-eyed readers will notice that the first line is 22 columns wide.
So why is the word “and” put in the second line when there is space
for it in the first line?
The explanation is that textwrap does not just wrap text one line at a
time. Instead, it uses an optimal-fit algorithm which looks ahead and
chooses line breaks which minimize the gaps left at ends of lines.
This is controlled with the `smawk` Cargo feature, which is why the
example is wrapped in the `cfg`-block.
Without look-ahead, the first line would be longer and the text would
look like this:
```rust
#[cfg(not(feature = "smawk"))] {
let text = "textwrap: an efficient and powerful library for wrapping text.";
assert_eq!(
textwrap::wrap(text, 28),
vec![
"textwrap: an efficient and",
"powerful library for",
"wrapping text.",
]
);
}
```
The second line is now shorter and the text is more ragged. The kind
of wrapping can be configured via `Options::wrap_algorithm`.
If you enable the `hyphenation` Cargo feature, you get support for
automatic hyphenation for [about 70 languages][patterns] via
high-quality TeX hyphenation patterns.
Your program must load the hyphenation pattern and configure
`Options::word_splitter` to use it:
```rust
#[cfg(feature = "hyphenation")] {
use hyphenation::{Language, Load, Standard};
use textwrap::{fill, Options, WordSplitter};
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let options = textwrap::Options::new(28).word_splitter(WordSplitter::Hyphenation(dictionary));
let text = "textwrap: an efficient and powerful library for wrapping text.";
assert_eq!(
textwrap::wrap(text, &options),
vec![
"textwrap: an efficient and",
"powerful library for wrap-",
"ping text."
]
);
}
```
The US-English hyphenation patterns are embedded when you enable the
`hyphenation` feature. They are licensed under a [permissive
license][en-us license] and take up about 88 KB in your binary. If you
need hyphenation for other languages, you need to download a
[precompiled `.bincode` file][bincode] and load it yourself. Please
see the [`hyphenation` documentation] for details.
## Wrapping Strings at Compile Time
If your strings are known at compile time, please take a look at the
procedural macros from the [`textwrap-macros` crate].
## Examples
The library comes with [a
collection](https://github.com/mgeisler/textwrap/tree/master/examples)
of small example programs that shows various features.
If you want to see Textwrap in action right away, then take a look at
[`examples/wasm/`], which shows how to wrap sans-serif, serif, and
monospace text. It uses WebAssembly and is automatically deployed to
https://mgeisler.github.io/textwrap/.
For the command-line examples, youre invited to clone the repository
and try them out for yourself! Of special note is
[`examples/interactive.rs`]. This is a demo program which demonstrates
most of the available features: you can enter text and adjust the
width at which it is wrapped interactively. You can also adjust the
`Options` used to see the effect of different `WordSplitter`s and wrap
algorithms.
Run the demo with
```sh
$ cargo run --example interactive
```
The demo needs a Linux terminal to function.
## Release History
Please see the [CHANGELOG file] for details on the changes made in
each release.
## License
Textwrap can be distributed according to the [MIT license][mit].
Contributions will be accepted under the same license.
[crates-io]: https://crates.io/crates/textwrap
[build-status]: https://github.com/mgeisler/textwrap/actions?query=workflow%3Abuild+branch%3Amaster
[codecov]: https://codecov.io/gh/mgeisler/textwrap
[wasm-demo]: https://mgeisler.github.io/textwrap/
[`textwrap-macros` crate]: https://crates.io/crates/textwrap-macros
[`hyphenation` example]: https://github.com/mgeisler/textwrap/blob/master/examples/hyphenation.rs
[`termwidth` example]: https://github.com/mgeisler/textwrap/blob/master/examples/termwidth.rs
[patterns]: https://github.com/tapeinosyne/hyphenation/tree/master/patterns-tex
[en-us license]: https://github.com/hyphenation/tex-hyphen/blob/master/hyph-utf8/tex/generic/hyph-utf8/patterns/tex/hyph-en-us.tex
[bincode]: https://github.com/tapeinosyne/hyphenation/tree/master/dictionaries
[`hyphenation` documentation]: http://docs.rs/hyphenation
[`examples/wasm/`]: https://github.com/mgeisler/textwrap/tree/master/examples/wasm
[`examples/interactive.rs`]: https://github.com/mgeisler/textwrap/tree/master/examples/interactive.rs
[api-docs]: https://docs.rs/textwrap/
[CHANGELOG file]: https://github.com/mgeisler/textwrap/blob/master/CHANGELOG.md
[mit]: LICENSE

1
vendor/textwrap/rustfmt.toml vendored Normal file
View File

@ -0,0 +1 @@
imports_granularity = "Module"

433
vendor/textwrap/src/core.rs vendored Normal file
View File

@ -0,0 +1,433 @@
//! Building blocks for advanced wrapping functionality.
//!
//! The functions and structs in this module can be used to implement
//! advanced wrapping functionality when the [`wrap`](super::wrap) and
//! [`fill`](super::fill) function don't do what you want.
//!
//! In general, you want to follow these steps when wrapping
//! something:
//!
//! 1. Split your input into [`Fragment`]s. These are abstract blocks
//! of text or content which can be wrapped into lines. See
//! [`WordSeparator`](crate::word_separators::WordSeparator) for
//! how to do this for text.
//!
//! 2. Potentially split your fragments into smaller pieces. This
//! allows you to implement things like hyphenation. If you use the
//! `Word` type, you can use [`WordSplitter`](crate::WordSplitter)
//! enum for this.
//!
//! 3. Potentially break apart fragments that are still too large to
//! fit on a single line. This is implemented in [`break_words`].
//!
//! 4. Finally take your fragments and put them into lines. There are
//! two algorithms for this in the
//! [`wrap_algorithms`](crate::wrap_algorithms) module:
//! [`wrap_optimal_fit`](crate::wrap_algorithms::wrap_optimal_fit)
//! and [`wrap_first_fit`](crate::wrap_algorithms::wrap_first_fit).
//! The former produces better line breaks, the latter is faster.
//!
//! 5. Iterate through the slices returned by the wrapping functions
//! and construct your lines of output.
//!
//! Please [open an issue](https://github.com/mgeisler/textwrap/) if
//! the functionality here is not sufficient or if you have ideas for
//! improving it. We would love to hear from you!
/// The CSI or “Control Sequence Introducer” introduces an ANSI escape
/// sequence. This is typically used for colored text and will be
/// ignored when computing the text width.
const CSI: (char, char) = ('\x1b', '[');
/// The final bytes of an ANSI escape sequence must be in this range.
const ANSI_FINAL_BYTE: std::ops::RangeInclusive<char> = '\x40'..='\x7e';
/// Skip ANSI escape sequences. The `ch` is the current `char`, the
/// `chars` provide the following characters. The `chars` will be
/// modified if `ch` is the start of an ANSI escape sequence.
#[inline]
pub(crate) fn skip_ansi_escape_sequence<I: Iterator<Item = char>>(ch: char, chars: &mut I) -> bool {
if ch == CSI.0 && chars.next() == Some(CSI.1) {
// We have found the start of an ANSI escape code, typically
// used for colored terminal text. We skip until we find a
// "final byte" in the range 0x400x7E.
for ch in chars {
if ANSI_FINAL_BYTE.contains(&ch) {
return true;
}
}
}
false
}
#[cfg(feature = "unicode-width")]
#[inline]
fn ch_width(ch: char) -> usize {
unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0)
}
/// First character which [`ch_width`] will classify as double-width.
/// Please see [`display_width`].
#[cfg(not(feature = "unicode-width"))]
const DOUBLE_WIDTH_CUTOFF: char = '\u{1100}';
#[cfg(not(feature = "unicode-width"))]
#[inline]
fn ch_width(ch: char) -> usize {
if ch < DOUBLE_WIDTH_CUTOFF {
1
} else {
2
}
}
/// Compute the display width of `text` while skipping over ANSI
/// escape sequences.
///
/// # Examples
///
/// ```
/// use textwrap::core::display_width;
///
/// assert_eq!(display_width("Café Plain"), 10);
/// assert_eq!(display_width("\u{1b}[31mCafé Rouge\u{1b}[0m"), 10);
/// ```
///
/// **Note:** When the `unicode-width` Cargo feature is disabled, the
/// width of a `char` is determined by a crude approximation which
/// simply counts chars below U+1100 as 1 column wide, and all other
/// characters as 2 columns wide. With the feature enabled, function
/// will correctly deal with [combining characters] in their
/// decomposed form (see [Unicode equivalence]).
///
/// An example of a decomposed character is “é”, which can be
/// decomposed into: “e” followed by a combining acute accent: “◌́”.
/// Without the `unicode-width` Cargo feature, every `char` below
/// U+1100 has a width of 1. This includes the combining accent:
///
/// ```
/// use textwrap::core::display_width;
///
/// assert_eq!(display_width("Cafe Plain"), 10);
/// #[cfg(feature = "unicode-width")]
/// assert_eq!(display_width("Cafe\u{301} Plain"), 10);
/// #[cfg(not(feature = "unicode-width"))]
/// assert_eq!(display_width("Cafe\u{301} Plain"), 11);
/// ```
///
/// ## Emojis and CJK Characters
///
/// Characters such as emojis and [CJK characters] used in the
/// Chinese, Japanese, and Korean langauges are seen as double-width,
/// even if the `unicode-width` feature is disabled:
///
/// ```
/// use textwrap::core::display_width;
///
/// assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
/// assert_eq!(display_width("你好"), 4); // “Nǐ hǎo” or “Hello” in Chinese
/// ```
///
/// # Limitations
///
/// The displayed width of a string cannot always be computed from the
/// string alone. This is because the width depends on the rendering
/// engine used. This is particularly visible with [emoji modifier
/// sequences] where a base emoji is modified with, e.g., skin tone or
/// hair color modifiers. It is up to the rendering engine to detect
/// this and to produce a suitable emoji.
///
/// A simple example is “❤️”, which consists of “❤” (U+2764: Black
/// Heart Symbol) followed by U+FE0F (Variation Selector-16). By
/// itself, “❤” is a black heart, but if you follow it with the
/// variant selector, you may get a wider red heart.
///
/// A more complex example would be “👨‍🦰” which should depict a man
/// with red hair. Here the computed width is too large — and the
/// width differs depending on the use of the `unicode-width` feature:
///
/// ```
/// use textwrap::core::display_width;
///
/// assert_eq!("👨‍🦰".chars().collect::<Vec<char>>(), ['\u{1f468}', '\u{200d}', '\u{1f9b0}']);
/// #[cfg(feature = "unicode-width")]
/// assert_eq!(display_width("👨‍🦰"), 4);
/// #[cfg(not(feature = "unicode-width"))]
/// assert_eq!(display_width("👨‍🦰"), 6);
/// ```
///
/// This happens because the grapheme consists of three code points:
/// “👨” (U+1F468: Man), Zero Width Joiner (U+200D), and “🦰”
/// (U+1F9B0: Red Hair). You can see them above in the test. With
/// `unicode-width` enabled, the ZWJ is correctly seen as having zero
/// width, without it is counted as a double-width character.
///
/// ## Terminal Support
///
/// Modern browsers typically do a great job at combining characters
/// as shown above, but terminals often struggle more. As an example,
/// Gnome Terminal version 3.38.1, shows “❤️” as a big red heart, but
/// shows "👨‍🦰" as “👨🦰”.
///
/// [combining characters]: https://en.wikipedia.org/wiki/Combining_character
/// [Unicode equivalence]: https://en.wikipedia.org/wiki/Unicode_equivalence
/// [CJK characters]: https://en.wikipedia.org/wiki/CJK_characters
/// [emoji modifier sequences]: https://unicode.org/emoji/charts/full-emoji-modifiers.html
pub fn display_width(text: &str) -> usize {
let mut chars = text.chars();
let mut width = 0;
while let Some(ch) = chars.next() {
if skip_ansi_escape_sequence(ch, &mut chars) {
continue;
}
width += ch_width(ch);
}
width
}
/// A (text) fragment denotes the unit which we wrap into lines.
///
/// Fragments represent an abstract _word_ plus the _whitespace_
/// following the word. In case the word falls at the end of the line,
/// the whitespace is dropped and a so-called _penalty_ is inserted
/// instead (typically `"-"` if the word was hyphenated).
///
/// For wrapping purposes, the precise content of the word, the
/// whitespace, and the penalty is irrelevant. All we need to know is
/// the displayed width of each part, which this trait provides.
pub trait Fragment: std::fmt::Debug {
/// Displayed width of word represented by this fragment.
fn width(&self) -> f64;
/// Displayed width of the whitespace that must follow the word
/// when the word is not at the end of a line.
fn whitespace_width(&self) -> f64;
/// Displayed width of the penalty that must be inserted if the
/// word falls at the end of a line.
fn penalty_width(&self) -> f64;
}
/// A piece of wrappable text, including any trailing whitespace.
///
/// A `Word` is an example of a [`Fragment`], so it has a width,
/// trailing whitespace, and potentially a penalty item.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Word<'a> {
/// Word content.
pub word: &'a str,
/// Whitespace to insert if the word does not fall at the end of a line.
pub whitespace: &'a str,
/// Penalty string to insert if the word falls at the end of a line.
pub penalty: &'a str,
// Cached width in columns.
pub(crate) width: usize,
}
impl std::ops::Deref for Word<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.word
}
}
impl<'a> Word<'a> {
/// Construct a `Word` from a string.
///
/// A trailing stretch of `' '` is automatically taken to be the
/// whitespace part of the word.
pub fn from(word: &str) -> Word<'_> {
let trimmed = word.trim_end_matches(' ');
Word {
word: trimmed,
width: display_width(trimmed),
whitespace: &word[trimmed.len()..],
penalty: "",
}
}
/// Break this word into smaller words with a width of at most
/// `line_width`. The whitespace and penalty from this `Word` is
/// added to the last piece.
///
/// # Examples
///
/// ```
/// use textwrap::core::Word;
/// assert_eq!(
/// Word::from("Hello! ").break_apart(3).collect::<Vec<_>>(),
/// vec![Word::from("Hel"), Word::from("lo! ")]
/// );
/// ```
pub fn break_apart<'b>(&'b self, line_width: usize) -> impl Iterator<Item = Word<'a>> + 'b {
let mut char_indices = self.word.char_indices();
let mut offset = 0;
let mut width = 0;
std::iter::from_fn(move || {
while let Some((idx, ch)) = char_indices.next() {
if skip_ansi_escape_sequence(ch, &mut char_indices.by_ref().map(|(_, ch)| ch)) {
continue;
}
if width > 0 && width + ch_width(ch) > line_width {
let word = Word {
word: &self.word[offset..idx],
width: width,
whitespace: "",
penalty: "",
};
offset = idx;
width = ch_width(ch);
return Some(word);
}
width += ch_width(ch);
}
if offset < self.word.len() {
let word = Word {
word: &self.word[offset..],
width: width,
whitespace: self.whitespace,
penalty: self.penalty,
};
offset = self.word.len();
return Some(word);
}
None
})
}
}
impl Fragment for Word<'_> {
#[inline]
fn width(&self) -> f64 {
self.width as f64
}
// We assume the whitespace consist of ' ' only. This allows us to
// compute the display width in constant time.
#[inline]
fn whitespace_width(&self) -> f64 {
self.whitespace.len() as f64
}
// We assume the penalty is `""` or `"-"`. This allows us to
// compute the display width in constant time.
#[inline]
fn penalty_width(&self) -> f64 {
self.penalty.len() as f64
}
}
/// Forcibly break words wider than `line_width` into smaller words.
///
/// This simply calls [`Word::break_apart`] on words that are too
/// wide. This means that no extra `'-'` is inserted, the word is
/// simply broken into smaller pieces.
pub fn break_words<'a, I>(words: I, line_width: usize) -> Vec<Word<'a>>
where
I: IntoIterator<Item = Word<'a>>,
{
let mut shortened_words = Vec::new();
for word in words {
if word.width() > line_width as f64 {
shortened_words.extend(word.break_apart(line_width));
} else {
shortened_words.push(word);
}
}
shortened_words
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "unicode-width")]
use unicode_width::UnicodeWidthChar;
#[test]
fn skip_ansi_escape_sequence_works() {
let blue_text = "\u{1b}[34mHello\u{1b}[0m";
let mut chars = blue_text.chars();
let ch = chars.next().unwrap();
assert!(skip_ansi_escape_sequence(ch, &mut chars));
assert_eq!(chars.next(), Some('H'));
}
#[test]
fn emojis_have_correct_width() {
use unic_emoji_char::is_emoji;
// Emojis in the Basic Latin (ASCII) and Latin-1 Supplement
// blocks all have a width of 1 column. This includes
// characters such as '#' and '©'.
for ch in '\u{1}'..'\u{FF}' {
if is_emoji(ch) {
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
#[cfg(feature = "unicode-width")]
assert_eq!(ch.width().unwrap(), 1, "char: {}", desc);
#[cfg(not(feature = "unicode-width"))]
assert_eq!(ch_width(ch), 1, "char: {}", desc);
}
}
// Emojis in the remaining blocks of the Basic Multilingual
// Plane (BMP), in the Supplementary Multilingual Plane (SMP),
// and in the Supplementary Ideographic Plane (SIP), are all 1
// or 2 columns wide when unicode-width is used, and always 2
// columns wide otherwise. This includes all of our favorite
// emojis such as 😊.
for ch in '\u{FF}'..'\u{2FFFF}' {
if is_emoji(ch) {
let desc = format!("{:?} U+{:04X}", ch, ch as u32);
#[cfg(feature = "unicode-width")]
assert!(ch.width().unwrap() <= 2, "char: {}", desc);
#[cfg(not(feature = "unicode-width"))]
assert_eq!(ch_width(ch), 2, "char: {}", desc);
}
}
// The remaining planes contain almost no assigned code points
// and thus also no emojis.
}
#[test]
fn display_width_works() {
assert_eq!("Café Plain".len(), 11); // “é” is two bytes
assert_eq!(display_width("Café Plain"), 10);
assert_eq!(display_width("\u{1b}[31mCafé Rouge\u{1b}[0m"), 10);
}
#[test]
fn display_width_narrow_emojis() {
#[cfg(feature = "unicode-width")]
assert_eq!(display_width(""), 1);
// The ⁉ character is above DOUBLE_WIDTH_CUTOFF.
#[cfg(not(feature = "unicode-width"))]
assert_eq!(display_width(""), 2);
}
#[test]
fn display_width_narrow_emojis_variant_selector() {
#[cfg(feature = "unicode-width")]
assert_eq!(display_width("\u{fe0f}"), 1);
// The variant selector-16 is also counted.
#[cfg(not(feature = "unicode-width"))]
assert_eq!(display_width("\u{fe0f}"), 4);
}
#[test]
fn display_width_emojis() {
assert_eq!(display_width("😂😭🥺🤣✨😍🙏🥰😊🔥"), 20);
}
}

347
vendor/textwrap/src/indentation.rs vendored Normal file
View File

@ -0,0 +1,347 @@
//! Functions related to adding and removing indentation from lines of
//! text.
//!
//! The functions here can be used to uniformly indent or dedent
//! (unindent) word wrapped lines of text.
/// Indent each line by the given prefix.
///
/// # Examples
///
/// ```
/// use textwrap::indent;
///
/// assert_eq!(indent("First line.\nSecond line.\n", " "),
/// " First line.\n Second line.\n");
/// ```
///
/// When indenting, trailing whitespace is stripped from the prefix.
/// This means that empty lines remain empty afterwards:
///
/// ```
/// use textwrap::indent;
///
/// assert_eq!(indent("First line.\n\n\nSecond line.\n", " "),
/// " First line.\n\n\n Second line.\n");
/// ```
///
/// Notice how `"\n\n\n"` remained as `"\n\n\n"`.
///
/// This feature is useful when you want to indent text and have a
/// space between your prefix and the text. In this case, you _don't_
/// want a trailing space on empty lines:
///
/// ```
/// use textwrap::indent;
///
/// assert_eq!(indent("foo = 123\n\nprint(foo)\n", "# "),
/// "# foo = 123\n#\n# print(foo)\n");
/// ```
///
/// Notice how `"\n\n"` became `"\n#\n"` instead of `"\n# \n"` which
/// would have trailing whitespace.
///
/// Leading and trailing whitespace coming from the text itself is
/// kept unchanged:
///
/// ```
/// use textwrap::indent;
///
/// assert_eq!(indent(" \t Foo ", "->"), "-> \t Foo ");
/// ```
pub fn indent(s: &str, prefix: &str) -> String {
// We know we'll need more than s.len() bytes for the output, but
// without counting '\n' characters (which is somewhat slow), we
// don't know exactly how much. However, we can preemptively do
// the first doubling of the output size.
let mut result = String::with_capacity(2 * s.len());
let trimmed_prefix = prefix.trim_end();
for (idx, line) in s.split_terminator('\n').enumerate() {
if idx > 0 {
result.push('\n');
}
if line.trim().is_empty() {
result.push_str(trimmed_prefix);
} else {
result.push_str(prefix);
}
result.push_str(line);
}
if s.ends_with('\n') {
// split_terminator will have eaten the final '\n'.
result.push('\n');
}
result
}
/// Removes common leading whitespace from each line.
///
/// This function will look at each non-empty line and determine the
/// maximum amount of whitespace that can be removed from all lines:
///
/// ```
/// use textwrap::dedent;
///
/// assert_eq!(dedent("
/// 1st line
/// 2nd line
/// 3rd line
/// "), "
/// 1st line
/// 2nd line
/// 3rd line
/// ");
/// ```
pub fn dedent(s: &str) -> String {
let mut prefix = "";
let mut lines = s.lines();
// We first search for a non-empty line to find a prefix.
for line in &mut lines {
let mut whitespace_idx = line.len();
for (idx, ch) in line.char_indices() {
if !ch.is_whitespace() {
whitespace_idx = idx;
break;
}
}
// Check if the line had anything but whitespace
if whitespace_idx < line.len() {
prefix = &line[..whitespace_idx];
break;
}
}
// We then continue looking through the remaining lines to
// possibly shorten the prefix.
for line in &mut lines {
let mut whitespace_idx = line.len();
for ((idx, a), b) in line.char_indices().zip(prefix.chars()) {
if a != b {
whitespace_idx = idx;
break;
}
}
// Check if the line had anything but whitespace and if we
// have found a shorter prefix
if whitespace_idx < line.len() && whitespace_idx < prefix.len() {
prefix = &line[..whitespace_idx];
}
}
// We now go over the lines a second time to build the result.
let mut result = String::new();
for line in s.lines() {
if line.starts_with(&prefix) && line.chars().any(|c| !c.is_whitespace()) {
let (_, tail) = line.split_at(prefix.len());
result.push_str(tail);
}
result.push('\n');
}
if result.ends_with('\n') && !s.ends_with('\n') {
let new_len = result.len() - 1;
result.truncate(new_len);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn indent_empty() {
assert_eq!(indent("\n", " "), "\n");
}
#[test]
#[rustfmt::skip]
fn indent_nonempty() {
let text = [
" foo\n",
"bar\n",
" baz\n",
].join("");
let expected = [
"// foo\n",
"// bar\n",
"// baz\n",
].join("");
assert_eq!(indent(&text, "// "), expected);
}
#[test]
#[rustfmt::skip]
fn indent_empty_line() {
let text = [
" foo",
"bar",
"",
" baz",
].join("\n");
let expected = [
"// foo",
"// bar",
"//",
"// baz",
].join("\n");
assert_eq!(indent(&text, "// "), expected);
}
#[test]
fn dedent_empty() {
assert_eq!(dedent(""), "");
}
#[test]
#[rustfmt::skip]
fn dedent_multi_line() {
let x = [
" foo",
" bar",
" baz",
].join("\n");
let y = [
" foo",
"bar",
" baz"
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_empty_line() {
let x = [
" foo",
" bar",
" ",
" baz"
].join("\n");
let y = [
" foo",
"bar",
"",
" baz"
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_blank_line() {
let x = [
" foo",
"",
" bar",
" foo",
" bar",
" baz",
].join("\n");
let y = [
"foo",
"",
" bar",
" foo",
" bar",
" baz",
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_whitespace_line() {
let x = [
" foo",
" ",
" bar",
" foo",
" bar",
" baz",
].join("\n");
let y = [
"foo",
"",
" bar",
" foo",
" bar",
" baz",
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_mixed_whitespace() {
let x = [
"\tfoo",
" bar",
].join("\n");
let y = [
"\tfoo",
" bar",
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_tabbed_whitespace() {
let x = [
"\t\tfoo",
"\t\t\tbar",
].join("\n");
let y = [
"foo",
"\tbar",
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_mixed_tabbed_whitespace() {
let x = [
"\t \tfoo",
"\t \t\tbar",
].join("\n");
let y = [
"foo",
"\tbar",
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_mixed_tabbed_whitespace2() {
let x = [
"\t \tfoo",
"\t \tbar",
].join("\n");
let y = [
"\tfoo",
" \tbar",
].join("\n");
assert_eq!(dedent(&x), y);
}
#[test]
#[rustfmt::skip]
fn dedent_preserve_no_terminating_newline() {
let x = [
" foo",
" bar",
].join("\n");
let y = [
"foo",
" bar",
].join("\n");
assert_eq!(dedent(&x), y);
}
}

1847
vendor/textwrap/src/lib.rs vendored Normal file

File diff suppressed because it is too large Load Diff

428
vendor/textwrap/src/word_separators.rs vendored Normal file
View File

@ -0,0 +1,428 @@
//! Functionality for finding words.
//!
//! In order to wrap text, we need to know where the legal break
//! points are, i.e., where the words of the text are. This means that
//! we need to define what a "word" is.
//!
//! A simple approach is to simply split the text on whitespace, but
//! this does not work for East-Asian languages such as Chinese or
//! Japanese where there are no spaces between words. Breaking a long
//! sequence of emojis is another example where line breaks might be
//! wanted even if there are no whitespace to be found.
//!
//! The [`WordSeparator`] trait is responsible for determining where
//! there words are in a line of text. Please refer to the trait and
//! the structs which implement it for more information.
#[cfg(feature = "unicode-linebreak")]
use crate::core::skip_ansi_escape_sequence;
use crate::core::Word;
/// Describes where words occur in a line of text.
///
/// The simplest approach is say that words are separated by one or
/// more ASCII spaces (`' '`). This works for Western languages
/// without emojis. A more complex approach is to use the Unicode line
/// breaking algorithm, which finds break points in non-ASCII text.
///
/// The line breaks occur between words, please see
/// [`WordSplitter`](crate::WordSplitter) for options of how to handle
/// hyphenation of individual words.
///
/// # Examples
///
/// ```
/// use textwrap::core::Word;
/// use textwrap::WordSeparator::AsciiSpace;
///
/// let words = AsciiSpace.find_words("Hello World!").collect::<Vec<_>>();
/// assert_eq!(words, vec![Word::from("Hello "), Word::from("World!")]);
/// ```
#[derive(Clone, Copy)]
pub enum WordSeparator {
/// Find words by splitting on runs of `' '` characters.
///
/// # Examples
///
/// ```
/// use textwrap::core::Word;
/// use textwrap::WordSeparator::AsciiSpace;
///
/// let words = AsciiSpace.find_words("Hello World!").collect::<Vec<_>>();
/// assert_eq!(words, vec![Word::from("Hello "),
/// Word::from("World!")]);
/// ```
AsciiSpace,
/// Split `line` into words using Unicode break properties.
///
/// This word separator uses the Unicode line breaking algorithm
/// described in [Unicode Standard Annex
/// #14](https://www.unicode.org/reports/tr14/) to find legal places
/// to break lines. There is a small difference in that the U+002D
/// (Hyphen-Minus) and U+00AD (Soft Hyphen) dont create a line break:
/// to allow a line break at a hyphen, use
/// [`WordSplitter::HyphenSplitter`](crate::WordSplitter::HyphenSplitter).
/// Soft hyphens are not currently supported.
///
/// # Examples
///
/// Unlike [`WordSeparator::AsciiSpace`], the Unicode line
/// breaking algorithm will find line break opportunities between
/// some characters with no intervening whitespace:
///
/// ```
/// #[cfg(feature = "unicode-linebreak")] {
/// use textwrap::core::Word;
/// use textwrap::WordSeparator::UnicodeBreakProperties;
///
/// assert_eq!(UnicodeBreakProperties.find_words("Emojis: 😂😍").collect::<Vec<_>>(),
/// vec![Word::from("Emojis: "),
/// Word::from("😂"),
/// Word::from("😍")]);
///
/// assert_eq!(UnicodeBreakProperties.find_words("CJK: 你好").collect::<Vec<_>>(),
/// vec![Word::from("CJK: "),
/// Word::from("你"),
/// Word::from("好")]);
/// }
/// ```
///
/// A U+2060 (Word Joiner) character can be inserted if you want to
/// manually override the defaults and keep the characters together:
///
/// ```
/// #[cfg(feature = "unicode-linebreak")] {
/// use textwrap::core::Word;
/// use textwrap::WordSeparator::UnicodeBreakProperties;
///
/// assert_eq!(UnicodeBreakProperties.find_words("Emojis: 😂\u{2060}😍").collect::<Vec<_>>(),
/// vec![Word::from("Emojis: "),
/// Word::from("😂\u{2060}😍")]);
/// }
/// ```
///
/// The Unicode line breaking algorithm will also automatically
/// suppress break breaks around certain punctuation characters::
///
/// ```
/// #[cfg(feature = "unicode-linebreak")] {
/// use textwrap::core::Word;
/// use textwrap::WordSeparator::UnicodeBreakProperties;
///
/// assert_eq!(UnicodeBreakProperties.find_words("[ foo ] bar !").collect::<Vec<_>>(),
/// vec![Word::from("[ foo ] "),
/// Word::from("bar !")]);
/// }
/// ```
#[cfg(feature = "unicode-linebreak")]
UnicodeBreakProperties,
/// Find words using a custom word separator
Custom(fn(line: &str) -> Box<dyn Iterator<Item = Word<'_>> + '_>),
}
impl std::fmt::Debug for WordSeparator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WordSeparator::AsciiSpace => f.write_str("AsciiSpace"),
#[cfg(feature = "unicode-linebreak")]
WordSeparator::UnicodeBreakProperties => f.write_str("UnicodeBreakProperties"),
WordSeparator::Custom(_) => f.write_str("Custom(...)"),
}
}
}
impl WordSeparator {
// This function should really return impl Iterator<Item = Word>, but
// this isn't possible until Rust supports higher-kinded types:
// https://github.com/rust-lang/rfcs/blob/master/text/1522-conservative-impl-trait.md
/// Find all words in `line`.
pub fn find_words<'a>(&self, line: &'a str) -> Box<dyn Iterator<Item = Word<'a>> + 'a> {
match self {
WordSeparator::AsciiSpace => find_words_ascii_space(line),
#[cfg(feature = "unicode-linebreak")]
WordSeparator::UnicodeBreakProperties => find_words_unicode_break_properties(line),
WordSeparator::Custom(func) => func(line),
}
}
}
fn find_words_ascii_space<'a>(line: &'a str) -> Box<dyn Iterator<Item = Word<'a>> + 'a> {
let mut start = 0;
let mut in_whitespace = false;
let mut char_indices = line.char_indices();
Box::new(std::iter::from_fn(move || {
// for (idx, ch) in char_indices does not work, gives this
// error:
//
// > cannot move out of `char_indices`, a captured variable in
// > an `FnMut` closure
#[allow(clippy::while_let_on_iterator)]
while let Some((idx, ch)) = char_indices.next() {
if in_whitespace && ch != ' ' {
let word = Word::from(&line[start..idx]);
start = idx;
in_whitespace = ch == ' ';
return Some(word);
}
in_whitespace = ch == ' ';
}
if start < line.len() {
let word = Word::from(&line[start..]);
start = line.len();
return Some(word);
}
None
}))
}
// Strip all ANSI escape sequences from `text`.
#[cfg(feature = "unicode-linebreak")]
fn strip_ansi_escape_sequences(text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut chars = text.chars();
while let Some(ch) = chars.next() {
if skip_ansi_escape_sequence(ch, &mut chars) {
continue;
}
result.push(ch);
}
result
}
/// Soft hyphen, also knows as a “shy hyphen”. Should show up as -
/// if a line is broken at this point, and otherwise be invisible.
/// Textwrap does not currently support breaking words at soft
/// hyphens.
#[cfg(feature = "unicode-linebreak")]
const SHY: char = '\u{00ad}';
/// Find words in line. ANSI escape sequences are ignored in `line`.
#[cfg(feature = "unicode-linebreak")]
fn find_words_unicode_break_properties<'a>(
line: &'a str,
) -> Box<dyn Iterator<Item = Word<'a>> + 'a> {
// Construct an iterator over (original index, stripped index)
// tuples. We find the Unicode linebreaks on a stripped string,
// but we need the original indices so we can form words based on
// the original string.
let mut last_stripped_idx = 0;
let mut char_indices = line.char_indices();
let mut idx_map = std::iter::from_fn(move || match char_indices.next() {
Some((orig_idx, ch)) => {
let stripped_idx = last_stripped_idx;
if !skip_ansi_escape_sequence(ch, &mut char_indices.by_ref().map(|(_, ch)| ch)) {
last_stripped_idx += ch.len_utf8();
}
Some((orig_idx, stripped_idx))
}
None => None,
});
let stripped = strip_ansi_escape_sequences(line);
let mut opportunities = unicode_linebreak::linebreaks(&stripped)
.filter(|(idx, _)| {
#[allow(clippy::match_like_matches_macro)]
match &stripped[..*idx].chars().next_back() {
// We suppress breaks at - since we want to control
// this via the WordSplitter.
Some('-') => false,
// Soft hyphens are currently not supported since we
// require all `Word` fragments to be continuous in
// the input string.
Some(SHY) => false,
// Other breaks should be fine!
_ => true,
}
})
.collect::<Vec<_>>()
.into_iter();
// Remove final break opportunity, we will add it below using
// &line[start..]; This ensures that we correctly include a
// trailing ANSI escape sequence.
opportunities.next_back();
let mut start = 0;
Box::new(std::iter::from_fn(move || {
#[allow(clippy::while_let_on_iterator)]
while let Some((idx, _)) = opportunities.next() {
if let Some((orig_idx, _)) = idx_map.find(|&(_, stripped_idx)| stripped_idx == idx) {
let word = Word::from(&line[start..orig_idx]);
start = orig_idx;
return Some(word);
}
}
if start < line.len() {
let word = Word::from(&line[start..]);
start = line.len();
return Some(word);
}
None
}))
}
#[cfg(test)]
mod tests {
use super::WordSeparator::*;
use super::*;
// Like assert_eq!, but the left expression is an iterator.
macro_rules! assert_iter_eq {
($left:expr, $right:expr) => {
assert_eq!($left.collect::<Vec<_>>(), $right);
};
}
fn to_words<'a>(words: Vec<&'a str>) -> Vec<Word<'a>> {
words.into_iter().map(|w: &str| Word::from(&w)).collect()
}
macro_rules! test_find_words {
($ascii_name:ident,
$unicode_name:ident,
$([ $line:expr, $ascii_words:expr, $unicode_words:expr ]),+) => {
#[test]
fn $ascii_name() {
$(
let expected_words = to_words($ascii_words.to_vec());
let actual_words = WordSeparator::AsciiSpace
.find_words($line)
.collect::<Vec<_>>();
assert_eq!(actual_words, expected_words, "Line: {:?}", $line);
)+
}
#[test]
#[cfg(feature = "unicode-linebreak")]
fn $unicode_name() {
$(
let expected_words = to_words($unicode_words.to_vec());
let actual_words = WordSeparator::UnicodeBreakProperties
.find_words($line)
.collect::<Vec<_>>();
assert_eq!(actual_words, expected_words, "Line: {:?}", $line);
)+
}
};
}
test_find_words!(ascii_space_empty, unicode_empty, ["", [], []]);
test_find_words!(
ascii_single_word,
unicode_single_word,
["foo", ["foo"], ["foo"]]
);
test_find_words!(
ascii_two_words,
unicode_two_words,
["foo bar", ["foo ", "bar"], ["foo ", "bar"]]
);
test_find_words!(
ascii_multiple_words,
unicode_multiple_words,
["foo bar", ["foo ", "bar"], ["foo ", "bar"]],
["x y z", ["x ", "y ", "z"], ["x ", "y ", "z"]]
);
test_find_words!(
ascii_only_whitespace,
unicode_only_whitespace,
[" ", [" "], [" "]],
[" ", [" "], [" "]]
);
test_find_words!(
ascii_inter_word_whitespace,
unicode_inter_word_whitespace,
["foo bar", ["foo ", "bar"], ["foo ", "bar"]]
);
test_find_words!(
ascii_trailing_whitespace,
unicode_trailing_whitespace,
["foo ", ["foo "], ["foo "]]
);
test_find_words!(
ascii_leading_whitespace,
unicode_leading_whitespace,
[" foo", [" ", "foo"], [" ", "foo"]]
);
test_find_words!(
ascii_multi_column_char,
unicode_multi_column_char,
["\u{1f920}", ["\u{1f920}"], ["\u{1f920}"]] // cowboy emoji 🤠
);
test_find_words!(
ascii_hyphens,
unicode_hyphens,
["foo-bar", ["foo-bar"], ["foo-bar"]],
["foo- bar", ["foo- ", "bar"], ["foo- ", "bar"]],
["foo - bar", ["foo ", "- ", "bar"], ["foo ", "- ", "bar"]],
["foo -bar", ["foo ", "-bar"], ["foo ", "-bar"]]
);
test_find_words!(
ascii_newline,
unicode_newline,
["foo\nbar", ["foo\nbar"], ["foo\n", "bar"]]
);
test_find_words!(
ascii_tab,
unicode_tab,
["foo\tbar", ["foo\tbar"], ["foo\t", "bar"]]
);
test_find_words!(
ascii_non_breaking_space,
unicode_non_breaking_space,
["foo\u{00A0}bar", ["foo\u{00A0}bar"], ["foo\u{00A0}bar"]]
);
#[test]
#[cfg(unix)]
fn find_words_colored_text() {
use termion::color::{Blue, Fg, Green, Reset};
let green_hello = format!("{}Hello{} ", Fg(Green), Fg(Reset));
let blue_world = format!("{}World!{}", Fg(Blue), Fg(Reset));
assert_iter_eq!(
AsciiSpace.find_words(&format!("{}{}", green_hello, blue_world)),
vec![Word::from(&green_hello), Word::from(&blue_world)]
);
#[cfg(feature = "unicode-linebreak")]
assert_iter_eq!(
UnicodeBreakProperties.find_words(&format!("{}{}", green_hello, blue_world)),
vec![Word::from(&green_hello), Word::from(&blue_world)]
);
}
#[test]
fn find_words_color_inside_word() {
let text = "foo\u{1b}[0m\u{1b}[32mbar\u{1b}[0mbaz";
assert_iter_eq!(AsciiSpace.find_words(&text), vec![Word::from(text)]);
#[cfg(feature = "unicode-linebreak")]
assert_iter_eq!(
UnicodeBreakProperties.find_words(&text),
vec![Word::from(text)]
);
}
}

314
vendor/textwrap/src/word_splitters.rs vendored Normal file
View File

@ -0,0 +1,314 @@
//! Word splitting functionality.
//!
//! To wrap text into lines, long words sometimes need to be split
//! across lines. The [`WordSplitter`] enum defines this
//! functionality.
use crate::core::{display_width, Word};
/// The `WordSplitter` enum describes where words can be split.
///
/// If the textwrap crate has been compiled with the `hyphenation`
/// Cargo feature enabled, you will find a
/// [`WordSplitter::Hyphenation`] variant. Use this struct for
/// language-aware hyphenation:
///
/// ```
/// #[cfg(feature = "hyphenation")] {
/// use hyphenation::{Language, Load, Standard};
/// use textwrap::{wrap, Options, WordSplitter};
///
/// let text = "Oxidation is the loss of electrons.";
/// let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
/// let options = Options::new(8).word_splitter(WordSplitter::Hyphenation(dictionary));
/// assert_eq!(wrap(text, &options), vec!["Oxida-",
/// "tion is",
/// "the loss",
/// "of elec-",
/// "trons."]);
/// }
/// ```
///
/// Please see the documentation for the [hyphenation] crate for more
/// details.
///
/// [hyphenation]: https://docs.rs/hyphenation/
#[derive(Clone)]
pub enum WordSplitter {
/// Use this as a [`Options.word_splitter`] to avoid any kind of
/// hyphenation:
///
/// ```
/// use textwrap::{wrap, Options, WordSplitter};
///
/// let options = Options::new(8).word_splitter(WordSplitter::NoHyphenation);
/// assert_eq!(wrap("foo bar-baz", &options),
/// vec!["foo", "bar-baz"]);
/// ```
///
/// [`Options.word_splitter`]: super::Options::word_splitter
NoHyphenation,
/// `HyphenSplitter` is the default `WordSplitter` used by
/// [`Options::new`](super::Options::new). It will split words on
/// existing hyphens in the word.
///
/// It will only use hyphens that are surrounded by alphanumeric
/// characters, which prevents a word like `"--foo-bar"` from
/// being split into `"--"` and `"foo-bar"`.
///
/// # Examples
///
/// ```
/// use textwrap::WordSplitter;
///
/// assert_eq!(WordSplitter::HyphenSplitter.split_points("--foo-bar"),
/// vec![6]);
/// ```
HyphenSplitter,
/// Use a custom function as the word splitter.
///
/// This varian lets you implement a custom word splitter using
/// your own function.
///
/// # Examples
///
/// ```
/// use textwrap::WordSplitter;
///
/// fn split_at_underscore(word: &str) -> Vec<usize> {
/// word.match_indices('_').map(|(idx, _)| idx + 1).collect()
/// }
///
/// let word_splitter = WordSplitter::Custom(split_at_underscore);
/// assert_eq!(word_splitter.split_points("a_long_identifier"),
/// vec![2, 7]);
/// ```
Custom(fn(word: &str) -> Vec<usize>),
/// A hyphenation dictionary can be used to do language-specific
/// hyphenation using patterns from the [hyphenation] crate.
///
/// **Note:** Only available when the `hyphenation` Cargo feature is
/// enabled.
///
/// [hyphenation]: https://docs.rs/hyphenation/
#[cfg(feature = "hyphenation")]
Hyphenation(hyphenation::Standard),
}
impl std::fmt::Debug for WordSplitter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WordSplitter::NoHyphenation => f.write_str("NoHyphenation"),
WordSplitter::HyphenSplitter => f.write_str("HyphenSplitter"),
WordSplitter::Custom(_) => f.write_str("Custom(...)"),
#[cfg(feature = "hyphenation")]
WordSplitter::Hyphenation(dict) => write!(f, "Hyphenation({})", dict.language()),
}
}
}
impl PartialEq<WordSplitter> for WordSplitter {
fn eq(&self, other: &WordSplitter) -> bool {
match (self, other) {
(WordSplitter::NoHyphenation, WordSplitter::NoHyphenation) => true,
(WordSplitter::HyphenSplitter, WordSplitter::HyphenSplitter) => true,
#[cfg(feature = "hyphenation")]
(WordSplitter::Hyphenation(this_dict), WordSplitter::Hyphenation(other_dict)) => {
this_dict.language() == other_dict.language()
}
(_, _) => false,
}
}
}
impl WordSplitter {
/// Return all possible indices where `word` can be split.
///
/// The indices are in the range `0..word.len()`. They point to
/// the index _after_ the split point, i.e., after `-` if
/// splitting on hyphens. This way, `word.split_at(idx)` will
/// break the word into two well-formed pieces.
///
/// # Examples
///
/// ```
/// use textwrap::WordSplitter;
/// assert_eq!(WordSplitter::NoHyphenation.split_points("cannot-be-split"), vec![]);
/// assert_eq!(WordSplitter::HyphenSplitter.split_points("can-be-split"), vec![4, 7]);
/// assert_eq!(WordSplitter::Custom(|word| vec![word.len()/2]).split_points("middle"), vec![3]);
/// ```
pub fn split_points(&self, word: &str) -> Vec<usize> {
match self {
WordSplitter::NoHyphenation => Vec::new(),
WordSplitter::HyphenSplitter => {
let mut splits = Vec::new();
for (idx, _) in word.match_indices('-') {
// We only use hyphens that are surrounded by alphanumeric
// characters. This is to avoid splitting on repeated hyphens,
// such as those found in --foo-bar.
let prev = word[..idx].chars().next_back();
let next = word[idx + 1..].chars().next();
if prev.filter(|ch| ch.is_alphanumeric()).is_some()
&& next.filter(|ch| ch.is_alphanumeric()).is_some()
{
splits.push(idx + 1); // +1 due to width of '-'.
}
}
splits
}
WordSplitter::Custom(splitter_func) => splitter_func(word),
#[cfg(feature = "hyphenation")]
WordSplitter::Hyphenation(dictionary) => {
use hyphenation::Hyphenator;
dictionary.hyphenate(word).breaks
}
}
}
}
/// Split words into smaller words according to the split points given
/// by `word_splitter`.
///
/// Note that we split all words, regardless of their length. This is
/// to more cleanly separate the business of splitting (including
/// automatic hyphenation) from the business of word wrapping.
pub fn split_words<'a, I>(
words: I,
word_splitter: &'a WordSplitter,
) -> impl Iterator<Item = Word<'a>>
where
I: IntoIterator<Item = Word<'a>>,
{
words.into_iter().flat_map(move |word| {
let mut prev = 0;
let mut split_points = word_splitter.split_points(&word).into_iter();
std::iter::from_fn(move || {
if let Some(idx) = split_points.next() {
let need_hyphen = !word[..idx].ends_with('-');
let w = Word {
word: &word.word[prev..idx],
width: display_width(&word[prev..idx]),
whitespace: "",
penalty: if need_hyphen { "-" } else { "" },
};
prev = idx;
return Some(w);
}
if prev < word.word.len() || prev == 0 {
let w = Word {
word: &word.word[prev..],
width: display_width(&word[prev..]),
whitespace: word.whitespace,
penalty: word.penalty,
};
prev = word.word.len() + 1;
return Some(w);
}
None
})
})
}
#[cfg(test)]
mod tests {
use super::*;
// Like assert_eq!, but the left expression is an iterator.
macro_rules! assert_iter_eq {
($left:expr, $right:expr) => {
assert_eq!($left.collect::<Vec<_>>(), $right);
};
}
#[test]
fn split_words_no_words() {
assert_iter_eq!(split_words(vec![], &WordSplitter::HyphenSplitter), vec![]);
}
#[test]
fn split_words_empty_word() {
assert_iter_eq!(
split_words(vec![Word::from(" ")], &WordSplitter::HyphenSplitter),
vec![Word::from(" ")]
);
}
#[test]
fn split_words_single_word() {
assert_iter_eq!(
split_words(vec![Word::from("foobar")], &WordSplitter::HyphenSplitter),
vec![Word::from("foobar")]
);
}
#[test]
fn split_words_hyphen_splitter() {
assert_iter_eq!(
split_words(vec![Word::from("foo-bar")], &WordSplitter::HyphenSplitter),
vec![Word::from("foo-"), Word::from("bar")]
);
}
#[test]
fn split_words_no_hyphenation() {
assert_iter_eq!(
split_words(vec![Word::from("foo-bar")], &WordSplitter::NoHyphenation),
vec![Word::from("foo-bar")]
);
}
#[test]
fn split_words_adds_penalty() {
let fixed_split_point = |_: &str| vec![3];
assert_iter_eq!(
split_words(
vec![Word::from("foobar")].into_iter(),
&WordSplitter::Custom(fixed_split_point)
),
vec![
Word {
word: "foo",
width: 3,
whitespace: "",
penalty: "-"
},
Word {
word: "bar",
width: 3,
whitespace: "",
penalty: ""
}
]
);
assert_iter_eq!(
split_words(
vec![Word::from("fo-bar")].into_iter(),
&WordSplitter::Custom(fixed_split_point)
),
vec![
Word {
word: "fo-",
width: 3,
whitespace: "",
penalty: ""
},
Word {
word: "bar",
width: 3,
whitespace: "",
penalty: ""
}
]
);
}
}

381
vendor/textwrap/src/wrap_algorithms.rs vendored Normal file
View File

@ -0,0 +1,381 @@
//! Word wrapping algorithms.
//!
//! After a text has been broken into words (or [`Fragment`]s), one
//! now has to decide how to break the fragments into lines. The
//! simplest algorithm for this is implemented by [`wrap_first_fit`]:
//! it uses no look-ahead and simply adds fragments to the line as
//! long as they fit. However, this can lead to poor line breaks if a
//! large fragment almost-but-not-quite fits on a line. When that
//! happens, the fragment is moved to the next line and it will leave
//! behind a large gap. A more advanced algorithm, implemented by
//! [`wrap_optimal_fit`], will take this into account. The optimal-fit
//! algorithm considers all possible line breaks and will attempt to
//! minimize the gaps left behind by overly short lines.
//!
//! While both algorithms run in linear time, the first-fit algorithm
//! is about 4 times faster than the optimal-fit algorithm.
#[cfg(feature = "smawk")]
mod optimal_fit;
#[cfg(feature = "smawk")]
pub use optimal_fit::{wrap_optimal_fit, OverflowError, Penalties};
use crate::core::{Fragment, Word};
/// Describes how to wrap words into lines.
///
/// The simplest approach is to wrap words one word at a time and
/// accept the first way of wrapping which fit
/// ([`WrapAlgorithm::FirstFit`]). If the `smawk` Cargo feature is
/// enabled, a more complex algorithm is available which will look at
/// an entire paragraph at a time in order to find optimal line breaks
/// ([`WrapAlgorithm::OptimalFit`]).
#[derive(Clone, Copy)]
pub enum WrapAlgorithm {
/// Wrap words using a fast and simple algorithm.
///
/// This algorithm uses no look-ahead when finding line breaks.
/// Implemented by [`wrap_first_fit`], please see that function for
/// details and examples.
FirstFit,
/// Wrap words using an advanced algorithm with look-ahead.
///
/// This wrapping algorithm considers the entire paragraph to find
/// optimal line breaks. When wrapping text, "penalties" are
/// assigned to line breaks based on the gaps left at the end of
/// lines. See [`Penalties`] for details.
///
/// The underlying wrapping algorithm is implemented by
/// [`wrap_optimal_fit`], please see that function for examples.
///
/// **Note:** Only available when the `smawk` Cargo feature is
/// enabled.
#[cfg(feature = "smawk")]
OptimalFit(Penalties),
/// Custom wrapping function.
///
/// Use this if you want to implement your own wrapping algorithm.
/// The function can freely decide how to turn a slice of
/// [`Word`]s into lines.
///
/// # Example
///
/// ```
/// use textwrap::core::Word;
/// use textwrap::{wrap, Options, WrapAlgorithm};
///
/// fn stair<'a, 'b>(words: &'b [Word<'a>], _: &'b [usize]) -> Vec<&'b [Word<'a>]> {
/// let mut lines = Vec::new();
/// let mut step = 1;
/// let mut start_idx = 0;
/// while start_idx + step <= words.len() {
/// lines.push(&words[start_idx .. start_idx+step]);
/// start_idx += step;
/// step += 1;
/// }
/// lines
/// }
///
/// let options = Options::new(10).wrap_algorithm(WrapAlgorithm::Custom(stair));
/// assert_eq!(wrap("First, second, third, fourth, fifth, sixth", options),
/// vec!["First,",
/// "second, third,",
/// "fourth, fifth, sixth"]);
/// ```
Custom(for<'a, 'b> fn(words: &'b [Word<'a>], line_widths: &'b [usize]) -> Vec<&'b [Word<'a>]>),
}
impl std::fmt::Debug for WrapAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WrapAlgorithm::FirstFit => f.write_str("FirstFit"),
#[cfg(feature = "smawk")]
WrapAlgorithm::OptimalFit(penalties) => write!(f, "OptimalFit({:?})", penalties),
WrapAlgorithm::Custom(_) => f.write_str("Custom(...)"),
}
}
}
impl WrapAlgorithm {
/// Create new wrap algorithm.
///
/// The best wrapping algorithm is used by default, i.e.,
/// [`WrapAlgorithm::OptimalFit`] if available, otherwise
/// [`WrapAlgorithm::FirstFit`].
pub const fn new() -> Self {
#[cfg(not(feature = "smawk"))]
{
WrapAlgorithm::FirstFit
}
#[cfg(feature = "smawk")]
{
WrapAlgorithm::new_optimal_fit()
}
}
/// New [`WrapAlgorithm::OptimalFit`] with default penalties. This
/// works well for monospace text.
///
/// **Note:** Only available when the `smawk` Cargo feature is
/// enabled.
#[cfg(feature = "smawk")]
pub const fn new_optimal_fit() -> Self {
WrapAlgorithm::OptimalFit(Penalties::new())
}
/// Wrap words according to line widths.
///
/// The `line_widths` slice gives the target line width for each
/// line (the last slice element is repeated as necessary). This
/// can be used to implement hanging indentation.
#[inline]
pub fn wrap<'a, 'b>(
&self,
words: &'b [Word<'a>],
line_widths: &'b [usize],
) -> Vec<&'b [Word<'a>]> {
// Every integer up to 2u64.pow(f64::MANTISSA_DIGITS) = 2**53
// = 9_007_199_254_740_992 can be represented without loss by
// a f64. Larger line widths will be rounded to the nearest
// representable number.
let f64_line_widths = line_widths.iter().map(|w| *w as f64).collect::<Vec<_>>();
match self {
WrapAlgorithm::FirstFit => wrap_first_fit(words, &f64_line_widths),
#[cfg(feature = "smawk")]
WrapAlgorithm::OptimalFit(penalties) => {
// The computation cannnot overflow when the line
// widths are restricted to usize.
wrap_optimal_fit(words, &f64_line_widths, penalties).unwrap()
}
WrapAlgorithm::Custom(func) => func(words, line_widths),
}
}
}
impl Default for WrapAlgorithm {
fn default() -> Self {
WrapAlgorithm::new()
}
}
/// Wrap abstract fragments into lines with a first-fit algorithm.
///
/// The `line_widths` slice gives the target line width for each line
/// (the last slice element is repeated as necessary). This can be
/// used to implement hanging indentation.
///
/// The fragments must already have been split into the desired
/// widths, this function will not (and cannot) attempt to split them
/// further when arranging them into lines.
///
/// # First-Fit Algorithm
///
/// This implements a simple “greedy” algorithm: accumulate fragments
/// one by one and when a fragment no longer fits, start a new line.
/// There is no look-ahead, we simply take first fit of the fragments
/// we find.
///
/// While fast and predictable, this algorithm can produce poor line
/// breaks when a long fragment is moved to a new line, leaving behind
/// a large gap:
///
/// ```
/// use textwrap::core::Word;
/// use textwrap::wrap_algorithms::wrap_first_fit;
/// use textwrap::WordSeparator;
///
/// // Helper to convert wrapped lines to a Vec<String>.
/// fn lines_to_strings(lines: Vec<&[Word<'_>]>) -> Vec<String> {
/// lines.iter().map(|line| {
/// line.iter().map(|word| &**word).collect::<Vec<_>>().join(" ")
/// }).collect::<Vec<_>>()
/// }
///
/// let text = "These few words will unfortunately not wrap nicely.";
/// let words = WordSeparator::AsciiSpace.find_words(text).collect::<Vec<_>>();
/// assert_eq!(lines_to_strings(wrap_first_fit(&words, &[15.0])),
/// vec!["These few words",
/// "will", // <-- short line
/// "unfortunately",
/// "not wrap",
/// "nicely."]);
///
/// // We can avoid the short line if we look ahead:
/// #[cfg(feature = "smawk")]
/// use textwrap::wrap_algorithms::{wrap_optimal_fit, Penalties};
/// #[cfg(feature = "smawk")]
/// assert_eq!(lines_to_strings(wrap_optimal_fit(&words, &[15.0], &Penalties::new()).unwrap()),
/// vec!["These few",
/// "words will",
/// "unfortunately",
/// "not wrap",
/// "nicely."]);
/// ```
///
/// The [`wrap_optimal_fit`] function was used above to get better
/// line breaks. It uses an advanced algorithm which tries to avoid
/// short lines. This function is about 4 times faster than
/// [`wrap_optimal_fit`].
///
/// # Examples
///
/// Imagine you're building a house site and you have a number of
/// tasks you need to execute. Things like pour foundation, complete
/// framing, install plumbing, electric cabling, install insulation.
///
/// The construction workers can only work during daytime, so they
/// need to pack up everything at night. Because they need to secure
/// their tools and move machines back to the garage, this process
/// takes much more time than the time it would take them to simply
/// switch to another task.
///
/// You would like to make a list of tasks to execute every day based
/// on your estimates. You can model this with a program like this:
///
/// ```
/// use textwrap::core::{Fragment, Word};
/// use textwrap::wrap_algorithms::wrap_first_fit;
///
/// #[derive(Debug)]
/// struct Task<'a> {
/// name: &'a str,
/// hours: f64, // Time needed to complete task.
/// sweep: f64, // Time needed for a quick sweep after task during the day.
/// cleanup: f64, // Time needed for full cleanup if day ends with this task.
/// }
///
/// impl Fragment for Task<'_> {
/// fn width(&self) -> f64 { self.hours }
/// fn whitespace_width(&self) -> f64 { self.sweep }
/// fn penalty_width(&self) -> f64 { self.cleanup }
/// }
///
/// // The morning tasks
/// let tasks = vec![
/// Task { name: "Foundation", hours: 4.0, sweep: 2.0, cleanup: 3.0 },
/// Task { name: "Framing", hours: 3.0, sweep: 1.0, cleanup: 2.0 },
/// Task { name: "Plumbing", hours: 2.0, sweep: 2.0, cleanup: 2.0 },
/// Task { name: "Electrical", hours: 2.0, sweep: 1.0, cleanup: 2.0 },
/// Task { name: "Insulation", hours: 2.0, sweep: 1.0, cleanup: 2.0 },
/// Task { name: "Drywall", hours: 3.0, sweep: 1.0, cleanup: 2.0 },
/// Task { name: "Floors", hours: 3.0, sweep: 1.0, cleanup: 2.0 },
/// Task { name: "Countertops", hours: 1.0, sweep: 1.0, cleanup: 2.0 },
/// Task { name: "Bathrooms", hours: 2.0, sweep: 1.0, cleanup: 2.0 },
/// ];
///
/// // Fill tasks into days, taking `day_length` into account. The
/// // output shows the hours worked per day along with the names of
/// // the tasks for that day.
/// fn assign_days<'a>(tasks: &[Task<'a>], day_length: f64) -> Vec<(f64, Vec<&'a str>)> {
/// let mut days = Vec::new();
/// // Assign tasks to days. The assignment is a vector of slices,
/// // with a slice per day.
/// let assigned_days: Vec<&[Task<'a>]> = wrap_first_fit(&tasks, &[day_length]);
/// for day in assigned_days.iter() {
/// let last = day.last().unwrap();
/// let work_hours: f64 = day.iter().map(|t| t.hours + t.sweep).sum();
/// let names = day.iter().map(|t| t.name).collect::<Vec<_>>();
/// days.push((work_hours - last.sweep + last.cleanup, names));
/// }
/// days
/// }
///
/// // With a single crew working 8 hours a day:
/// assert_eq!(
/// assign_days(&tasks, 8.0),
/// [
/// (7.0, vec!["Foundation"]),
/// (8.0, vec!["Framing", "Plumbing"]),
/// (7.0, vec!["Electrical", "Insulation"]),
/// (5.0, vec!["Drywall"]),
/// (7.0, vec!["Floors", "Countertops"]),
/// (4.0, vec!["Bathrooms"]),
/// ]
/// );
///
/// // With two crews working in shifts, 16 hours a day:
/// assert_eq!(
/// assign_days(&tasks, 16.0),
/// [
/// (14.0, vec!["Foundation", "Framing", "Plumbing"]),
/// (15.0, vec!["Electrical", "Insulation", "Drywall", "Floors"]),
/// (6.0, vec!["Countertops", "Bathrooms"]),
/// ]
/// );
/// ```
///
/// Apologies to anyone who actually knows how to build a house and
/// knows how long each step takes :-)
pub fn wrap_first_fit<'a, 'b, T: Fragment>(
fragments: &'a [T],
line_widths: &'b [f64],
) -> Vec<&'a [T]> {
// The final line width is used for all remaining lines.
let default_line_width = line_widths.last().copied().unwrap_or(0.0);
let mut lines = Vec::new();
let mut start = 0;
let mut width = 0.0;
for (idx, fragment) in fragments.iter().enumerate() {
let line_width = line_widths
.get(lines.len())
.copied()
.unwrap_or(default_line_width);
if width + fragment.width() + fragment.penalty_width() > line_width && idx > start {
lines.push(&fragments[start..idx]);
start = idx;
width = 0.0;
}
width += fragment.width() + fragment.whitespace_width();
}
lines.push(&fragments[start..]);
lines
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct Word(f64);
#[rustfmt::skip]
impl Fragment for Word {
fn width(&self) -> f64 { self.0 }
fn whitespace_width(&self) -> f64 { 1.0 }
fn penalty_width(&self) -> f64 { 0.0 }
}
#[test]
fn wrap_string_longer_than_f64() {
let words = vec![
Word(1e307),
Word(2e307),
Word(3e307),
Word(4e307),
Word(5e307),
Word(6e307),
];
// Wrap at just under f64::MAX (~19e307). The tiny
// whitespace_widths disappear because of loss of precision.
assert_eq!(
wrap_first_fit(&words, &[15e307]),
&[
vec![
Word(1e307),
Word(2e307),
Word(3e307),
Word(4e307),
Word(5e307)
],
vec![Word(6e307)]
]
);
}
}

View File

@ -0,0 +1,433 @@
use std::cell::RefCell;
use crate::core::Fragment;
/// Penalties for
/// [`WrapAlgorithm::OptimalFit`](crate::WrapAlgorithm::OptimalFit)
/// and [`wrap_optimal_fit`].
///
/// This wrapping algorithm in [`wrap_optimal_fit`] considers the
/// entire paragraph to find optimal line breaks. When wrapping text,
/// "penalties" are assigned to line breaks based on the gaps left at
/// the end of lines. The penalties are given by this struct, with
/// [`Penalties::default`] assigning penalties that work well for
/// monospace text.
///
/// If you are wrapping proportional text, you are advised to assign
/// your own penalties according to your font size. See the individual
/// penalties below for details.
///
/// **Note:** Only available when the `smawk` Cargo feature is
/// enabled.
#[derive(Clone, Copy, Debug)]
pub struct Penalties {
/// Per-line penalty. This is added for every line, which makes it
/// expensive to output more lines than the minimum required.
pub nline_penalty: usize,
/// Per-character cost for lines that overflow the target line width.
///
/// With a default value of 50², every single character costs as
/// much as leaving a gap of 50 characters behind. This is because
/// we assign as cost of `gap * gap` to a short line. When
/// wrapping monospace text, we can overflow the line by 1
/// character in extreme cases:
///
/// ```
/// use textwrap::core::Word;
/// use textwrap::wrap_algorithms::{wrap_optimal_fit, Penalties};
///
/// let short = "foo ";
/// let long = "x".repeat(50);
/// let length = (short.len() + long.len()) as f64;
/// let fragments = vec![Word::from(short), Word::from(&long)];
/// let penalties = Penalties::new();
///
/// // Perfect fit, both words are on a single line with no overflow.
/// let wrapped = wrap_optimal_fit(&fragments, &[length], &penalties).unwrap();
/// assert_eq!(wrapped, vec![&[Word::from(short), Word::from(&long)]]);
///
/// // The words no longer fit, yet we get a single line back. While
/// // the cost of overflow (`1 * 2500`) is the same as the cost of the
/// // gap (`50 * 50 = 2500`), the tie is broken by `nline_penalty`
/// // which makes it cheaper to overflow than to use two lines.
/// let wrapped = wrap_optimal_fit(&fragments, &[length - 1.0], &penalties).unwrap();
/// assert_eq!(wrapped, vec![&[Word::from(short), Word::from(&long)]]);
///
/// // The cost of overflow would be 2 * 2500, whereas the cost of
/// // the gap is only `49 * 49 + nline_penalty = 2401 + 1000 =
/// // 3401`. We therefore get two lines.
/// let wrapped = wrap_optimal_fit(&fragments, &[length - 2.0], &penalties).unwrap();
/// assert_eq!(wrapped, vec![&[Word::from(short)],
/// &[Word::from(&long)]]);
/// ```
///
/// This only happens if the overflowing word is 50 characters
/// long _and_ if the word overflows the line by exactly one
/// character. If it overflows by more than one character, the
/// overflow penalty will quickly outgrow the cost of the gap, as
/// seen above.
pub overflow_penalty: usize,
/// When should the a single word on the last line be considered
/// "too short"?
///
/// If the last line of the text consist of a single word and if
/// this word is shorter than `1 / short_last_line_fraction` of
/// the line width, then the final line will be considered "short"
/// and `short_last_line_penalty` is added as an extra penalty.
///
/// The effect of this is to avoid a final line consisting of a
/// single small word. For example, with a
/// `short_last_line_penalty` of 25 (the default), a gap of up to
/// 5 columns will be seen as more desirable than having a final
/// short line.
///
/// ## Examples
///
/// ```
/// use textwrap::{wrap, wrap_algorithms, Options, WrapAlgorithm};
///
/// let text = "This is a demo of the short last line penalty.";
///
/// // The first-fit algorithm leaves a single short word on the last line:
/// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::FirstFit)),
/// vec!["This is a demo of the short last line",
/// "penalty."]);
///
/// #[cfg(feature = "smawk")] {
/// let mut penalties = wrap_algorithms::Penalties::new();
///
/// // Since "penalty." is shorter than 25% of the line width, the
/// // optimal-fit algorithm adds a penalty of 25. This is enough
/// // to move "line " down:
/// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
/// vec!["This is a demo of the short last",
/// "line penalty."]);
///
/// // We can change the meaning of "short" lines. Here, only words
/// // shorter than 1/10th of the line width will be considered short:
/// penalties.short_last_line_fraction = 10;
/// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
/// vec!["This is a demo of the short last line",
/// "penalty."]);
///
/// // If desired, the penalty can also be disabled:
/// penalties.short_last_line_fraction = 4;
/// penalties.short_last_line_penalty = 0;
/// assert_eq!(wrap(text, Options::new(37).wrap_algorithm(WrapAlgorithm::OptimalFit(penalties))),
/// vec!["This is a demo of the short last line",
/// "penalty."]);
/// }
/// ```
pub short_last_line_fraction: usize,
/// Penalty for a last line with a single short word.
///
/// Set this to zero if you do not want to penalize short last lines.
pub short_last_line_penalty: usize,
/// Penalty for lines ending with a hyphen.
pub hyphen_penalty: usize,
}
impl Penalties {
/// Default penalties for monospace text.
///
/// The penalties here work well for monospace text. This is
/// because they expect the gaps at the end of lines to be roughly
/// in the range `0..100`. If the gaps are larger, the
/// `overflow_penalty` and `hyphen_penalty` become insignificant.
pub const fn new() -> Self {
Penalties {
nline_penalty: 1000,
overflow_penalty: 50 * 50,
short_last_line_fraction: 4,
short_last_line_penalty: 25,
hyphen_penalty: 25,
}
}
}
impl Default for Penalties {
fn default() -> Self {
Self::new()
}
}
/// Cache for line numbers. This is necessary to avoid a O(n**2)
/// behavior when computing line numbers in [`wrap_optimal_fit`].
struct LineNumbers {
line_numbers: RefCell<Vec<usize>>,
}
impl LineNumbers {
fn new(size: usize) -> Self {
let mut line_numbers = Vec::with_capacity(size);
line_numbers.push(0);
LineNumbers {
line_numbers: RefCell::new(line_numbers),
}
}
fn get<T>(&self, i: usize, minima: &[(usize, T)]) -> usize {
while self.line_numbers.borrow_mut().len() < i + 1 {
let pos = self.line_numbers.borrow().len();
let line_number = 1 + self.get(minima[pos].0, minima);
self.line_numbers.borrow_mut().push(line_number);
}
self.line_numbers.borrow()[i]
}
}
/// Overflow error during the [`wrap_optimal_fit`] computation.
#[derive(Debug, PartialEq, Eq)]
pub struct OverflowError;
impl std::fmt::Display for OverflowError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "wrap_optimal_fit cost computation overflowed")
}
}
impl std::error::Error for OverflowError {}
/// Wrap abstract fragments into lines with an optimal-fit algorithm.
///
/// The `line_widths` slice gives the target line width for each line
/// (the last slice element is repeated as necessary). This can be
/// used to implement hanging indentation.
///
/// The fragments must already have been split into the desired
/// widths, this function will not (and cannot) attempt to split them
/// further when arranging them into lines.
///
/// # Optimal-Fit Algorithm
///
/// The algorithm considers all possible break points and picks the
/// breaks which minimizes the gaps at the end of each line. More
/// precisely, the algorithm assigns a cost or penalty to each break
/// point, determined by `cost = gap * gap` where `gap = target_width -
/// line_width`. Shorter lines are thus penalized more heavily since
/// they leave behind a larger gap.
///
/// We can illustrate this with the text “To be, or not to be: that is
/// the question”. We will be wrapping it in a narrow column with room
/// for only 10 characters. The [greedy
/// algorithm](super::wrap_first_fit) will produce these lines, each
/// annotated with the corresponding penalty:
///
/// ```text
/// "To be, or" 1² = 1
/// "not to be:" 0² = 0
/// "that is" 3² = 9
/// "the" 7² = 49
/// "question" 2² = 4
/// ```
///
/// We see that line four with “the” leaves a gap of 7 columns, which
/// gives it a penalty of 49. The sum of the penalties is 63.
///
/// There are 10 words, which means that there are `2_u32.pow(9)` or
/// 512 different ways to typeset it. We can compute
/// the sum of the penalties for each possible line break and search
/// for the one with the lowest sum:
///
/// ```text
/// "To be," 4² = 16
/// "or not to" 1² = 1
/// "be: that" 2² = 4
/// "is the" 4² = 16
/// "question" 2² = 4
/// ```
///
/// The sum of the penalties is 41, which is better than what the
/// greedy algorithm produced.
///
/// Searching through all possible combinations would normally be
/// prohibitively slow. However, it turns out that the problem can be
/// formulated as the task of finding column minima in a cost matrix.
/// This matrix has a special form (totally monotone) which lets us
/// use a [linear-time algorithm called
/// SMAWK](https://lib.rs/crates/smawk) to find the optimal break
/// points.
///
/// This means that the time complexity remains O(_n_) where _n_ is
/// the number of words. Compared to
/// [`wrap_first_fit`](super::wrap_first_fit), this function is about
/// 4 times slower.
///
/// The optimization of per-line costs over the entire paragraph is
/// inspired by the line breaking algorithm used in TeX, as described
/// in the 1981 article [_Breaking Paragraphs into
/// Lines_](http://www.eprg.org/G53DOC/pdfs/knuth-plass-breaking.pdf)
/// by Knuth and Plass. The implementation here is based on [Python
/// code by David
/// Eppstein](https://github.com/jfinkels/PADS/blob/master/pads/wrap.py).
///
/// # Errors
///
/// In case of an overflow during the cost computation, an `Err` is
/// returned. Overflows happens when fragments or lines have infinite
/// widths (`f64::INFINITY`) or if the widths are so large that the
/// gaps at the end of lines have sizes larger than `f64::MAX.sqrt()`
/// (approximately 1e154):
///
/// ```
/// use textwrap::core::Fragment;
/// use textwrap::wrap_algorithms::{wrap_optimal_fit, OverflowError, Penalties};
///
/// #[derive(Debug, PartialEq)]
/// struct Word(f64);
///
/// impl Fragment for Word {
/// fn width(&self) -> f64 { self.0 }
/// fn whitespace_width(&self) -> f64 { 1.0 }
/// fn penalty_width(&self) -> f64 { 0.0 }
/// }
///
/// // Wrapping overflows because 1e155 * 1e155 = 1e310, which is
/// // larger than f64::MAX:
/// assert_eq!(wrap_optimal_fit(&[Word(0.0), Word(0.0)], &[1e155], &Penalties::default()),
/// Err(OverflowError));
/// ```
///
/// When using fragment widths and line widths which fit inside an
/// `u64`, overflows cannot happen. This means that fragments derived
/// from a `&str` cannot cause overflows.
///
/// **Note:** Only available when the `smawk` Cargo feature is
/// enabled.
pub fn wrap_optimal_fit<'a, 'b, T: Fragment>(
fragments: &'a [T],
line_widths: &'b [f64],
penalties: &'b Penalties,
) -> Result<Vec<&'a [T]>, OverflowError> {
// The final line width is used for all remaining lines.
let default_line_width = line_widths.last().copied().unwrap_or(0.0);
let mut widths = Vec::with_capacity(fragments.len() + 1);
let mut width = 0.0;
widths.push(width);
for fragment in fragments {
width += fragment.width() + fragment.whitespace_width();
widths.push(width);
}
let line_numbers = LineNumbers::new(fragments.len());
let minima = smawk::online_column_minima(0.0, widths.len(), |minima, i, j| {
// Line number for fragment `i`.
let line_number = line_numbers.get(i, minima);
let line_width = line_widths
.get(line_number)
.copied()
.unwrap_or(default_line_width);
let target_width = line_width.max(1.0);
// Compute the width of a line spanning fragments[i..j] in
// constant time. We need to adjust widths[j] by subtracting
// the whitespace of fragment[j-1] and then add the penalty.
let line_width = widths[j] - widths[i] - fragments[j - 1].whitespace_width()
+ fragments[j - 1].penalty_width();
// We compute cost of the line containing fragments[i..j]. We
// start with values[i].1, which is the optimal cost for
// breaking before fragments[i].
//
// First, every extra line cost NLINE_PENALTY.
let mut cost = minima[i].1 + penalties.nline_penalty as f64;
// Next, we add a penalty depending on the line length.
if line_width > target_width {
// Lines that overflow get a hefty penalty.
let overflow = line_width - target_width;
cost += overflow * penalties.overflow_penalty as f64;
} else if j < fragments.len() {
// Other lines (except for the last line) get a milder
// penalty which depend on the size of the gap.
let gap = target_width - line_width;
cost += gap * gap;
} else if i + 1 == j
&& line_width < target_width / penalties.short_last_line_fraction as f64
{
// The last line can have any size gap, but we do add a
// penalty if the line is very short (typically because it
// contains just a single word).
cost += penalties.short_last_line_penalty as f64;
}
// Finally, we discourage hyphens.
if fragments[j - 1].penalty_width() > 0.0 {
// TODO: this should use a penalty value from the fragment
// instead.
cost += penalties.hyphen_penalty as f64;
}
cost
});
for (_, cost) in &minima {
if cost.is_infinite() {
return Err(OverflowError);
}
}
let mut lines = Vec::with_capacity(line_numbers.get(fragments.len(), &minima));
let mut pos = fragments.len();
loop {
let prev = minima[pos].0;
lines.push(&fragments[prev..pos]);
pos = prev;
if pos == 0 {
break;
}
}
lines.reverse();
Ok(lines)
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct Word(f64);
#[rustfmt::skip]
impl Fragment for Word {
fn width(&self) -> f64 { self.0 }
fn whitespace_width(&self) -> f64 { 1.0 }
fn penalty_width(&self) -> f64 { 0.0 }
}
#[test]
fn wrap_fragments_with_infinite_widths() {
let words = vec![Word(f64::INFINITY)];
assert_eq!(
wrap_optimal_fit(&words, &[0.0], &Penalties::default()),
Err(OverflowError)
);
}
#[test]
fn wrap_fragments_with_huge_widths() {
let words = vec![Word(1e200), Word(1e250), Word(1e300)];
assert_eq!(
wrap_optimal_fit(&words, &[1e300], &Penalties::default()),
Err(OverflowError)
);
}
#[test]
fn wrap_fragments_with_large_widths() {
// The gaps will be of the sizes between 1e25 and 1e75. This
// makes the `gap * gap` cost fit comfortably in a f64.
let words = vec![Word(1e25), Word(1e50), Word(1e75)];
assert_eq!(
wrap_optimal_fit(&words, &[1e100], &Penalties::default()),
Ok(vec![&vec![Word(1e25), Word(1e50), Word(1e75)][..]])
);
}
}

88
vendor/textwrap/tests/indent.rs vendored Normal file
View File

@ -0,0 +1,88 @@
/// tests cases ported over from python standard library
use textwrap::{dedent, indent};
const ROUNDTRIP_CASES: [&str; 3] = [
// basic test case
"Hi.\nThis is a test.\nTesting.",
// include a blank line
"Hi.\nThis is a test.\n\nTesting.",
// include leading and trailing blank lines
"\nHi.\nThis is a test.\nTesting.\n",
];
const WINDOWS_CASES: [&str; 2] = [
// use windows line endings
"Hi.\r\nThis is a test.\r\nTesting.",
// pathological case
"Hi.\r\nThis is a test.\n\r\nTesting.\r\n\n",
];
#[test]
fn test_indent_nomargin_default() {
// indent should do nothing if 'prefix' is empty.
for text in ROUNDTRIP_CASES.iter() {
assert_eq!(&indent(text, ""), text);
}
for text in WINDOWS_CASES.iter() {
assert_eq!(&indent(text, ""), text);
}
}
#[test]
fn test_roundtrip_spaces() {
// A whitespace prefix should roundtrip with dedent
for text in ROUNDTRIP_CASES.iter() {
assert_eq!(&dedent(&indent(text, " ")), text);
}
}
#[test]
fn test_roundtrip_tabs() {
// A whitespace prefix should roundtrip with dedent
for text in ROUNDTRIP_CASES.iter() {
assert_eq!(&dedent(&indent(text, "\t\t")), text);
}
}
#[test]
fn test_roundtrip_mixed() {
// A whitespace prefix should roundtrip with dedent
for text in ROUNDTRIP_CASES.iter() {
assert_eq!(&dedent(&indent(text, " \t \t ")), text);
}
}
#[test]
fn test_indent_default() {
// Test default indenting of lines that are not whitespace only
let prefix = " ";
let expected = [
// Basic test case
" Hi.\n This is a test.\n Testing.",
// Include a blank line
" Hi.\n This is a test.\n\n Testing.",
// Include leading and trailing blank lines
"\n Hi.\n This is a test.\n Testing.\n",
];
for (text, expect) in ROUNDTRIP_CASES.iter().zip(expected.iter()) {
assert_eq!(&indent(text, prefix), expect)
}
let expected = [
// Use Windows line endings
" Hi.\r\n This is a test.\r\n Testing.",
// Pathological case
" Hi.\r\n This is a test.\n\r\n Testing.\r\n\n",
];
for (text, expect) in WINDOWS_CASES.iter().zip(expected.iter()) {
assert_eq!(&indent(text, prefix), expect)
}
}
#[test]
fn indented_text_should_have_the_same_number_of_lines_as_the_original_text() {
let texts = ["foo\nbar", "foo\nbar\n", "foo\nbar\nbaz"];
for original in texts.iter() {
let indented = indent(original, "");
assert_eq!(&indented, original);
}
}

View File

@ -0,0 +1,22 @@
#[test]
fn test_readme_deps() {
version_sync::assert_markdown_deps_updated!("README.md");
}
#[test]
fn test_changelog() {
version_sync::assert_contains_regex!(
"CHANGELOG.md",
r"^## Version {version} \(20\d\d-\d\d-\d\d\)"
);
}
#[test]
fn test_html_root_url() {
version_sync::assert_html_root_url_updated!("src/lib.rs");
}
#[test]
fn test_dependency_graph() {
version_sync::assert_contains_regex!("src/lib.rs", "master/images/textwrap-{version}.svg");
}