diff options
Diffstat (limited to 'vendor/textwrap')
-rw-r--r-- | vendor/textwrap/.cargo-checksum.json | 1 | ||||
-rw-r--r-- | vendor/textwrap/CHANGELOG.md | 574 | ||||
-rw-r--r-- | vendor/textwrap/Cargo.lock | 966 | ||||
-rw-r--r-- | vendor/textwrap/Cargo.toml | 106 | ||||
-rw-r--r-- | vendor/textwrap/LICENSE | 21 | ||||
-rw-r--r-- | vendor/textwrap/README.md | 176 | ||||
-rw-r--r-- | vendor/textwrap/rustfmt.toml | 1 | ||||
-rw-r--r-- | vendor/textwrap/src/core.rs | 433 | ||||
-rw-r--r-- | vendor/textwrap/src/indentation.rs | 347 | ||||
-rw-r--r-- | vendor/textwrap/src/lib.rs | 1847 | ||||
-rw-r--r-- | vendor/textwrap/src/word_separators.rs | 428 | ||||
-rw-r--r-- | vendor/textwrap/src/word_splitters.rs | 314 | ||||
-rw-r--r-- | vendor/textwrap/src/wrap_algorithms.rs | 381 | ||||
-rw-r--r-- | vendor/textwrap/src/wrap_algorithms/optimal_fit.rs | 433 | ||||
-rw-r--r-- | vendor/textwrap/tests/indent.rs | 88 | ||||
-rw-r--r-- | vendor/textwrap/tests/version-numbers.rs | 22 |
16 files changed, 0 insertions, 6138 deletions
diff --git a/vendor/textwrap/.cargo-checksum.json b/vendor/textwrap/.cargo-checksum.json deleted file mode 100644 index 90983e0..0000000 --- a/vendor/textwrap/.cargo-checksum.json +++ /dev/null @@ -1 +0,0 @@ -{"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"}
\ No newline at end of file diff --git a/vendor/textwrap/CHANGELOG.md b/vendor/textwrap/CHANGELOG.md deleted file mode 100644 index 434f156..0000000 --- a/vendor/textwrap/CHANGELOG.md +++ /dev/null @@ -1,574 +0,0 @@ -# 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 won’t 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. We’ve 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 diff --git a/vendor/textwrap/Cargo.lock b/vendor/textwrap/Cargo.lock deleted file mode 100644 index ea81eca..0000000 --- a/vendor/textwrap/Cargo.lock +++ /dev/null @@ -1,966 +0,0 @@ -# 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" diff --git a/vendor/textwrap/Cargo.toml b/vendor/textwrap/Cargo.toml deleted file mode 100644 index 3191af7..0000000 --- a/vendor/textwrap/Cargo.toml +++ /dev/null @@ -1,106 +0,0 @@ -# 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" diff --git a/vendor/textwrap/LICENSE b/vendor/textwrap/LICENSE deleted file mode 100644 index 0d37ec3..0000000 --- a/vendor/textwrap/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -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. diff --git a/vendor/textwrap/README.md b/vendor/textwrap/README.md deleted file mode 100644 index 9eeea07..0000000 --- a/vendor/textwrap/README.md +++ /dev/null @@ -1,176 +0,0 @@ -# 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, you’re 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 diff --git a/vendor/textwrap/rustfmt.toml b/vendor/textwrap/rustfmt.toml deleted file mode 100644 index c1578aa..0000000 --- a/vendor/textwrap/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -imports_granularity = "Module" diff --git a/vendor/textwrap/src/core.rs b/vendor/textwrap/src/core.rs deleted file mode 100644 index 0ab4ef8..0000000 --- a/vendor/textwrap/src/core.rs +++ /dev/null @@ -1,433 +0,0 @@ -//! 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 0x40–0x7E. - 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); - } -} diff --git a/vendor/textwrap/src/indentation.rs b/vendor/textwrap/src/indentation.rs deleted file mode 100644 index 5d90c06..0000000 --- a/vendor/textwrap/src/indentation.rs +++ /dev/null @@ -1,347 +0,0 @@ -//! 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); - } -} diff --git a/vendor/textwrap/src/lib.rs b/vendor/textwrap/src/lib.rs deleted file mode 100644 index e570eac..0000000 --- a/vendor/textwrap/src/lib.rs +++ /dev/null @@ -1,1847 +0,0 @@ -//! The textwrap library provides functions for word wrapping and -//! indenting text. -//! -//! # Wrapping Text -//! -//! Wrapping text can be very useful in command-line programs where -//! you want to format dynamic output nicely so it looks good in a -//! terminal. A quick example: -//! -//! ``` -//! # #[cfg(feature = "smawk")] { -//! let text = "textwrap: a small library for wrapping text."; -//! assert_eq!(textwrap::wrap(text, 18), -//! vec!["textwrap: a", -//! "small library for", -//! "wrapping text."]); -//! # } -//! ``` -//! -//! The [`wrap`] function returns the individual lines, use [`fill`] -//! is you want the lines joined with `'\n'` to form a `String`. -//! -//! If you enable the `hyphenation` Cargo feature, you can get -//! automatic hyphenation for a number of languages: -//! -//! ``` -//! #[cfg(feature = "hyphenation")] { -//! use hyphenation::{Language, Load, Standard}; -//! use textwrap::{wrap, Options, WordSplitter}; -//! -//! let text = "textwrap: a small library for wrapping text."; -//! let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); -//! let options = Options::new(18).word_splitter(WordSplitter::Hyphenation(dictionary)); -//! assert_eq!(wrap(text, &options), -//! vec!["textwrap: a small", -//! "library for wrap-", -//! "ping text."]); -//! } -//! ``` -//! -//! See also the [`unfill`] and [`refill`] functions which allow you to -//! manipulate already wrapped text. -//! -//! ## 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. -//! -//! ## Displayed Width vs Byte Size -//! -//! To word wrap text, one must know the width of each word so one can -//! know when to break lines. This library will by default measure the -//! width of text using the _displayed width_, not the size in bytes. -//! The `unicode-width` Cargo feature controls this. -//! -//! This is important for non-ASCII text. ASCII characters such as `a` -//! and `!` are simple and take up one column each. This means that -//! the displayed width is equal to the string length in bytes. -//! However, non-ASCII characters and symbols take up more than one -//! byte when UTF-8 encoded: `é` is `0xc3 0xa9` (two bytes) and `⚙` is -//! `0xe2 0x9a 0x99` (three bytes) in UTF-8, respectively. -//! -//! This is why we take care to use the displayed width instead of the -//! byte count when computing line lengths. All functions in this -//! library handle Unicode characters like this when the -//! `unicode-width` Cargo feature is enabled (it is enabled by -//! default). -//! -//! # Indentation and Dedentation -//! -//! The textwrap library also offers functions for adding a prefix to -//! every line of a string and to remove leading whitespace. As an -//! example, the [`indent`] function allows you to turn lines of text -//! into a bullet list: -//! -//! ``` -//! let before = "\ -//! foo -//! bar -//! baz -//! "; -//! let after = "\ -//! * foo -//! * bar -//! * baz -//! "; -//! assert_eq!(textwrap::indent(before, "* "), after); -//! ``` -//! -//! Removing leading whitespace is done with [`dedent`]: -//! -//! ``` -//! let before = " -//! Some -//! indented -//! text -//! "; -//! let after = " -//! Some -//! indented -//! text -//! "; -//! assert_eq!(textwrap::dedent(before), after); -//! ``` -//! -//! # Cargo Features -//! -//! The textwrap library can be slimmed down as needed via a number of -//! Cargo features. This means you only pay for the features you -//! actually use. -//! -//! The full dependency graph, where dashed lines indicate optional -//! dependencies, is shown below: -//! -//! <img src="https://raw.githubusercontent.com/mgeisler/textwrap/master/images/textwrap-0.15.2.svg"> -//! -//! ## Default Features -//! -//! These features are enabled by default: -//! -//! * `unicode-linebreak`: enables finding words using the -//! [unicode-linebreak] crate, which implements the line breaking -//! algorithm described in [Unicode Standard Annex -//! #14](https://www.unicode.org/reports/tr14/). -//! -//! This feature can be disabled if you are happy to find words -//! separated by ASCII space characters only. People wrapping text -//! with emojis or East-Asian characters will want most likely want -//! to enable this feature. See [`WordSeparator`] for details. -//! -//! * `unicode-width`: enables correct width computation of non-ASCII -//! characters via the [unicode-width] crate. Without this feature, -//! every [`char`] is 1 column wide, except for emojis which are 2 -//! columns wide. See the [`core::display_width`] function for -//! details. -//! -//! This feature can be disabled if you only need to wrap ASCII -//! text, or if the functions in [`core`] are used directly with -//! [`core::Fragment`]s for which the widths have been computed in -//! other ways. -//! -//! * `smawk`: enables linear-time wrapping of the whole paragraph via -//! the [smawk] crate. See the [`wrap_algorithms::wrap_optimal_fit`] -//! function for details on the optimal-fit algorithm. -//! -//! This feature can be disabled if you only ever intend to use -//! [`wrap_algorithms::wrap_first_fit`]. -//! -//! With Rust 1.59.0, the size impact of the above features on your -//! binary is as follows: -//! -//! | Configuration | Binary Size | Delta | -//! | :--- | ---: | ---: | -//! | quick-and-dirty implementation | 289 KB | — KB | -//! | textwrap without default features | 301 KB | 12 KB | -//! | textwrap with smawk | 317 KB | 28 KB | -//! | textwrap with unicode-width | 313 KB | 24 KB | -//! | textwrap with unicode-linebreak | 395 KB | 106 KB | -//! -//! The above sizes are the stripped sizes and the binary is compiled -//! in release mode with this profile: -//! -//! ```toml -//! [profile.release] -//! lto = true -//! codegen-units = 1 -//! ``` -//! -//! See the [binary-sizes demo] if you want to reproduce these -//! results. -//! -//! ## Optional Features -//! -//! These Cargo features enable new functionality: -//! -//! * `terminal_size`: enables automatic detection of the terminal -//! width via the [terminal_size] crate. See the -//! [`Options::with_termwidth`] constructor for details. -//! -//! * `hyphenation`: enables language-sensitive hyphenation via the -//! [hyphenation] crate. See the [`word_splitters::WordSplitter`] -//! trait for details. -//! -//! [unicode-linebreak]: https://docs.rs/unicode-linebreak/ -//! [unicode-width]: https://docs.rs/unicode-width/ -//! [smawk]: https://docs.rs/smawk/ -//! [binary-sizes demo]: https://github.com/mgeisler/textwrap/tree/master/examples/binary-sizes -//! [textwrap-macros]: https://docs.rs/textwrap-macros/ -//! [terminal_size]: https://docs.rs/terminal_size/ -//! [hyphenation]: https://docs.rs/hyphenation/ - -#![doc(html_root_url = "https://docs.rs/textwrap/0.15.2")] -#![forbid(unsafe_code)] // See https://github.com/mgeisler/textwrap/issues/210 -#![deny(missing_docs)] -#![deny(missing_debug_implementations)] -#![allow(clippy::redundant_field_names)] - -// Make `cargo test` execute the README doctests. -#[cfg(doctest)] -#[doc = include_str!("../README.md")] -mod readme_doctest {} - -use std::borrow::Cow; - -mod indentation; -pub use crate::indentation::{dedent, indent}; - -mod word_separators; -pub use word_separators::WordSeparator; - -pub mod word_splitters; -pub use word_splitters::WordSplitter; - -pub mod wrap_algorithms; -pub use wrap_algorithms::WrapAlgorithm; - -pub mod core; - -#[cfg(feature = "unicode-linebreak")] -macro_rules! DefaultWordSeparator { - () => { - WordSeparator::UnicodeBreakProperties - }; -} - -#[cfg(not(feature = "unicode-linebreak"))] -macro_rules! DefaultWordSeparator { - () => { - WordSeparator::AsciiSpace - }; -} - -/// Holds configuration options for wrapping and filling text. -#[derive(Debug, Clone)] -pub struct Options<'a> { - /// The width in columns at which the text will be wrapped. - pub width: usize, - /// Indentation used for the first line of output. See the - /// [`Options::initial_indent`] method. - pub initial_indent: &'a str, - /// Indentation used for subsequent lines of output. See the - /// [`Options::subsequent_indent`] method. - pub subsequent_indent: &'a str, - /// Allow long words to be broken if they cannot fit on a line. - /// When set to `false`, some lines may be longer than - /// `self.width`. See the [`Options::break_words`] method. - pub break_words: bool, - /// Wrapping algorithm to use, see the implementations of the - /// [`wrap_algorithms::WrapAlgorithm`] trait for details. - pub wrap_algorithm: WrapAlgorithm, - /// The line breaking algorithm to use, see - /// [`word_separators::WordSeparator`] trait for an overview and - /// possible implementations. - pub word_separator: WordSeparator, - /// The method for splitting words. This can be used to prohibit - /// splitting words on hyphens, or it can be used to implement - /// language-aware machine hyphenation. - pub word_splitter: WordSplitter, -} - -impl<'a> From<&'a Options<'a>> for Options<'a> { - fn from(options: &'a Options<'a>) -> Self { - Self { - width: options.width, - initial_indent: options.initial_indent, - subsequent_indent: options.subsequent_indent, - break_words: options.break_words, - word_separator: options.word_separator, - wrap_algorithm: options.wrap_algorithm, - word_splitter: options.word_splitter.clone(), - } - } -} - -impl<'a> From<usize> for Options<'a> { - fn from(width: usize) -> Self { - Options::new(width) - } -} - -impl<'a> Options<'a> { - /// Creates a new [`Options`] with the specified width. Equivalent to - /// - /// ``` - /// # use textwrap::{Options, WordSplitter, WordSeparator, WrapAlgorithm}; - /// # let width = 80; - /// # let actual = Options::new(width); - /// # let expected = - /// Options { - /// width: width, - /// initial_indent: "", - /// subsequent_indent: "", - /// break_words: true, - /// #[cfg(feature = "unicode-linebreak")] - /// word_separator: WordSeparator::UnicodeBreakProperties, - /// #[cfg(not(feature = "unicode-linebreak"))] - /// word_separator: WordSeparator::AsciiSpace, - /// #[cfg(feature = "smawk")] - /// wrap_algorithm: WrapAlgorithm::new_optimal_fit(), - /// #[cfg(not(feature = "smawk"))] - /// wrap_algorithm: WrapAlgorithm::FirstFit, - /// word_splitter: WordSplitter::HyphenSplitter, - /// } - /// # ; - /// # assert_eq!(actual.width, expected.width); - /// # assert_eq!(actual.initial_indent, expected.initial_indent); - /// # assert_eq!(actual.subsequent_indent, expected.subsequent_indent); - /// # assert_eq!(actual.break_words, expected.break_words); - /// # assert_eq!(actual.word_splitter, expected.word_splitter); - /// ``` - /// - /// Note that the default word separator and wrap algorithms - /// changes based on the available Cargo features. The best - /// available algorithms are used by default. - pub const fn new(width: usize) -> Self { - Options { - width, - initial_indent: "", - subsequent_indent: "", - break_words: true, - word_separator: DefaultWordSeparator!(), - wrap_algorithm: WrapAlgorithm::new(), - word_splitter: WordSplitter::HyphenSplitter, - } - } - - /// Creates a new [`Options`] with `width` set to the current - /// terminal width. If the terminal width cannot be determined - /// (typically because the standard input and output is not - /// connected to a terminal), a width of 80 characters will be - /// used. Other settings use the same defaults as - /// [`Options::new`]. - /// - /// Equivalent to: - /// - /// ```no_run - /// use textwrap::{termwidth, Options}; - /// - /// let options = Options::new(termwidth()); - /// ``` - /// - /// **Note:** Only available when the `terminal_size` feature is - /// enabled. - #[cfg(feature = "terminal_size")] - pub fn with_termwidth() -> Self { - Self::new(termwidth()) - } -} - -impl<'a> Options<'a> { - /// Change [`self.initial_indent`]. The initial indentation is - /// used on the very first line of output. - /// - /// # Examples - /// - /// Classic paragraph indentation can be achieved by specifying an - /// initial indentation and wrapping each paragraph by itself: - /// - /// ``` - /// use textwrap::{wrap, Options}; - /// - /// let options = Options::new(16).initial_indent(" "); - /// assert_eq!(wrap("This is a little example.", options), - /// vec![" This is a", - /// "little example."]); - /// ``` - /// - /// [`self.initial_indent`]: #structfield.initial_indent - pub fn initial_indent(self, indent: &'a str) -> Self { - Options { - initial_indent: indent, - ..self - } - } - - /// Change [`self.subsequent_indent`]. The subsequent indentation - /// is used on lines following the first line of output. - /// - /// # Examples - /// - /// Combining initial and subsequent indentation lets you format a - /// single paragraph as a bullet list: - /// - /// ``` - /// use textwrap::{wrap, Options}; - /// - /// let options = Options::new(12) - /// .initial_indent("* ") - /// .subsequent_indent(" "); - /// #[cfg(feature = "smawk")] - /// assert_eq!(wrap("This is a little example.", options), - /// vec!["* This is", - /// " a little", - /// " example."]); - /// - /// // Without the `smawk` feature, the wrapping is a little different: - /// #[cfg(not(feature = "smawk"))] - /// assert_eq!(wrap("This is a little example.", options), - /// vec!["* This is a", - /// " little", - /// " example."]); - /// ``` - /// - /// [`self.subsequent_indent`]: #structfield.subsequent_indent - pub fn subsequent_indent(self, indent: &'a str) -> Self { - Options { - subsequent_indent: indent, - ..self - } - } - - /// Change [`self.break_words`]. This controls if words longer - /// than `self.width` can be broken, or if they will be left - /// sticking out into the right margin. - /// - /// # Examples - /// - /// ``` - /// use textwrap::{wrap, Options}; - /// - /// let options = Options::new(4).break_words(true); - /// assert_eq!(wrap("This is a little example.", options), - /// vec!["This", - /// "is a", - /// "litt", - /// "le", - /// "exam", - /// "ple."]); - /// ``` - /// - /// [`self.break_words`]: #structfield.break_words - pub fn break_words(self, setting: bool) -> Self { - Options { - break_words: setting, - ..self - } - } - - /// Change [`self.word_separator`]. - /// - /// See [`word_separators::WordSeparator`] for details on the choices. - /// - /// [`self.word_separator`]: #structfield.word_separator - pub fn word_separator(self, word_separator: WordSeparator) -> Options<'a> { - Options { - width: self.width, - initial_indent: self.initial_indent, - subsequent_indent: self.subsequent_indent, - break_words: self.break_words, - word_separator: word_separator, - wrap_algorithm: self.wrap_algorithm, - word_splitter: self.word_splitter, - } - } - - /// Change [`self.wrap_algorithm`]. - /// - /// See the [`wrap_algorithms::WrapAlgorithm`] trait for details on - /// the choices. - /// - /// [`self.wrap_algorithm`]: #structfield.wrap_algorithm - pub fn wrap_algorithm(self, wrap_algorithm: WrapAlgorithm) -> Options<'a> { - Options { - width: self.width, - initial_indent: self.initial_indent, - subsequent_indent: self.subsequent_indent, - break_words: self.break_words, - word_separator: self.word_separator, - wrap_algorithm: wrap_algorithm, - word_splitter: self.word_splitter, - } - } - - /// Change [`self.word_splitter`]. The - /// [`word_splitters::WordSplitter`] is used to fit part of a word - /// into the current line when wrapping text. - /// - /// # Examples - /// - /// ``` - /// use textwrap::{Options, WordSplitter}; - /// let opt = Options::new(80); - /// assert_eq!(opt.word_splitter, WordSplitter::HyphenSplitter); - /// let opt = opt.word_splitter(WordSplitter::NoHyphenation); - /// assert_eq!(opt.word_splitter, WordSplitter::NoHyphenation); - /// ``` - /// - /// [`self.word_splitter`]: #structfield.word_splitter - pub fn word_splitter(self, word_splitter: WordSplitter) -> Options<'a> { - Options { - width: self.width, - initial_indent: self.initial_indent, - subsequent_indent: self.subsequent_indent, - break_words: self.break_words, - word_separator: self.word_separator, - wrap_algorithm: self.wrap_algorithm, - word_splitter, - } - } -} - -/// Return the current terminal width. -/// -/// If the terminal width cannot be determined (typically because the -/// standard output is not connected to a terminal), a default width -/// of 80 characters will be used. -/// -/// # Examples -/// -/// Create an [`Options`] for wrapping at the current terminal width -/// with a two column margin to the left and the right: -/// -/// ```no_run -/// use textwrap::{termwidth, Options}; -/// -/// let width = termwidth() - 4; // Two columns on each side. -/// let options = Options::new(width) -/// .initial_indent(" ") -/// .subsequent_indent(" "); -/// ``` -/// -/// **Note:** Only available when the `terminal_size` Cargo feature is -/// enabled. -#[cfg(feature = "terminal_size")] -pub fn termwidth() -> usize { - terminal_size::terminal_size().map_or(80, |(terminal_size::Width(w), _)| w.into()) -} - -/// Fill a line of text at a given width. -/// -/// The result is a [`String`], complete with newlines between each -/// line. Use the [`wrap`] function if you need access to the -/// individual lines. -/// -/// The easiest way to use this function is to pass an integer for -/// `width_or_options`: -/// -/// ``` -/// use textwrap::fill; -/// -/// assert_eq!( -/// fill("Memory safety without garbage collection.", 15), -/// "Memory safety\nwithout garbage\ncollection." -/// ); -/// ``` -/// -/// If you need to customize the wrapping, you can pass an [`Options`] -/// instead of an `usize`: -/// -/// ``` -/// use textwrap::{fill, Options}; -/// -/// let options = Options::new(15) -/// .initial_indent("- ") -/// .subsequent_indent(" "); -/// assert_eq!( -/// fill("Memory safety without garbage collection.", &options), -/// "- Memory safety\n without\n garbage\n collection." -/// ); -/// ``` -pub fn fill<'a, Opt>(text: &str, width_or_options: Opt) -> String -where - Opt: Into<Options<'a>>, -{ - // This will avoid reallocation in simple cases (no - // indentation, no hyphenation). - let mut result = String::with_capacity(text.len()); - - for (i, line) in wrap(text, width_or_options).iter().enumerate() { - if i > 0 { - result.push('\n'); - } - result.push_str(line); - } - - result -} - -/// Unpack a paragraph of already-wrapped text. -/// -/// This function attempts to recover the original text from a single -/// paragraph of text produced by the [`fill`] function. This means -/// that it turns -/// -/// ```text -/// textwrap: a small -/// library for -/// wrapping text. -/// ``` -/// -/// back into -/// -/// ```text -/// textwrap: a small library for wrapping text. -/// ``` -/// -/// In addition, it will recognize a common prefix among the lines. -/// The prefix of the first line is returned in -/// [`Options::initial_indent`] and the prefix (if any) of the the -/// other lines is returned in [`Options::subsequent_indent`]. -/// -/// In addition to `' '`, the prefixes can consist of characters used -/// for unordered lists (`'-'`, `'+'`, and `'*'`) and block quotes -/// (`'>'`) in Markdown as well as characters often used for inline -/// comments (`'#'` and `'/'`). -/// -/// The text must come from a single wrapped paragraph. This means -/// that there can be no `"\n\n"` within the text. -/// -/// # Examples -/// -/// ``` -/// use textwrap::unfill; -/// -/// let (text, options) = unfill("\ -/// * This is an -/// example of -/// a list item. -/// "); -/// -/// assert_eq!(text, "This is an example of a list item.\n"); -/// assert_eq!(options.initial_indent, "* "); -/// assert_eq!(options.subsequent_indent, " "); -/// ``` -pub fn unfill(text: &str) -> (String, Options<'_>) { - let trimmed = text.trim_end_matches('\n'); - let prefix_chars: &[_] = &[' ', '-', '+', '*', '>', '#', '/']; - - let mut options = Options::new(0); - for (idx, line) in trimmed.split('\n').enumerate() { - options.width = std::cmp::max(options.width, core::display_width(line)); - let without_prefix = line.trim_start_matches(prefix_chars); - let prefix = &line[..line.len() - without_prefix.len()]; - - if idx == 0 { - options.initial_indent = prefix; - } else if idx == 1 { - options.subsequent_indent = prefix; - } else if idx > 1 { - for ((idx, x), y) in prefix.char_indices().zip(options.subsequent_indent.chars()) { - if x != y { - options.subsequent_indent = &prefix[..idx]; - break; - } - } - if prefix.len() < options.subsequent_indent.len() { - options.subsequent_indent = prefix; - } - } - } - - let mut unfilled = String::with_capacity(text.len()); - for (idx, line) in trimmed.split('\n').enumerate() { - if idx == 0 { - unfilled.push_str(&line[options.initial_indent.len()..]); - } else { - unfilled.push(' '); - unfilled.push_str(&line[options.subsequent_indent.len()..]); - } - } - - unfilled.push_str(&text[trimmed.len()..]); - (unfilled, options) -} - -/// Refill a paragraph of wrapped text with a new width. -/// -/// This function will first use the [`unfill`] function to remove -/// newlines from the text. Afterwards the text is filled again using -/// the [`fill`] function. -/// -/// The `new_width_or_options` argument specify the new width and can -/// specify other options as well — except for -/// [`Options::initial_indent`] and [`Options::subsequent_indent`], -/// which are deduced from `filled_text`. -/// -/// # Examples -/// -/// ``` -/// use textwrap::refill; -/// -/// // Some loosely wrapped text. The "> " prefix is recognized automatically. -/// let text = "\ -/// > Memory -/// > safety without garbage -/// > collection. -/// "; -/// -/// assert_eq!(refill(text, 20), "\ -/// > Memory safety -/// > without garbage -/// > collection. -/// "); -/// -/// assert_eq!(refill(text, 40), "\ -/// > Memory safety without garbage -/// > collection. -/// "); -/// -/// assert_eq!(refill(text, 60), "\ -/// > Memory safety without garbage collection. -/// "); -/// ``` -/// -/// You can also reshape bullet points: -/// -/// ``` -/// use textwrap::refill; -/// -/// let text = "\ -/// - This is my -/// list item. -/// "; -/// -/// assert_eq!(refill(text, 20), "\ -/// - This is my list -/// item. -/// "); -/// ``` -pub fn refill<'a, Opt>(filled_text: &str, new_width_or_options: Opt) -> String -where - Opt: Into<Options<'a>>, -{ - let trimmed = filled_text.trim_end_matches('\n'); - let (text, options) = unfill(trimmed); - let mut new_options = new_width_or_options.into(); - new_options.initial_indent = options.initial_indent; - new_options.subsequent_indent = options.subsequent_indent; - let mut refilled = fill(&text, new_options); - refilled.push_str(&filled_text[trimmed.len()..]); - refilled -} - -/// Wrap a line of text at a given width. -/// -/// The result is a vector of lines, each line is of type [`Cow<'_, -/// str>`](Cow), which means that the line will borrow from the input -/// `&str` if possible. The lines do not have trailing whitespace, -/// including a final `'\n'`. Please use the [`fill`] function if you -/// need a [`String`] instead. -/// -/// The easiest way to use this function is to pass an integer for -/// `width_or_options`: -/// -/// ``` -/// use textwrap::wrap; -/// -/// let lines = wrap("Memory safety without garbage collection.", 15); -/// assert_eq!(lines, &[ -/// "Memory safety", -/// "without garbage", -/// "collection.", -/// ]); -/// ``` -/// -/// If you need to customize the wrapping, you can pass an [`Options`] -/// instead of an `usize`: -/// -/// ``` -/// use textwrap::{wrap, Options}; -/// -/// let options = Options::new(15) -/// .initial_indent("- ") -/// .subsequent_indent(" "); -/// let lines = wrap("Memory safety without garbage collection.", &options); -/// assert_eq!(lines, &[ -/// "- Memory safety", -/// " without", -/// " garbage", -/// " collection.", -/// ]); -/// ``` -/// -/// # Optimal-Fit Wrapping -/// -/// By default, `wrap` will try to ensure an even right margin by -/// finding breaks which avoid short lines. We call this an -/// “optimal-fit algorithm” since the line breaks are computed by -/// considering all possible line breaks. The alternative is a -/// “first-fit algorithm” which simply accumulates words until they no -/// longer fit on the line. -/// -/// As an example, using the first-fit algorithm to wrap the famous -/// Hamlet quote “To be, or not to be: that is the question” in a -/// narrow column with room for only 10 characters looks like this: -/// -/// ``` -/// # use textwrap::{WrapAlgorithm::FirstFit, Options, wrap}; -/// # -/// # let lines = wrap("To be, or not to be: that is the question", -/// # Options::new(10).wrap_algorithm(FirstFit)); -/// # assert_eq!(lines.join("\n") + "\n", "\ -/// To be, or -/// not to be: -/// that is -/// the -/// question -/// # "); -/// ``` -/// -/// Notice how the second to last line is quite narrow because -/// “question” was too large to fit? The greedy first-fit algorithm -/// doesn’t look ahead, so it has no other option than to put -/// “question” onto its own line. -/// -/// With the optimal-fit wrapping algorithm, the previous lines are -/// shortened slightly in order to make the word “is” go into the -/// second last line: -/// -/// ``` -/// # #[cfg(feature = "smawk")] { -/// # use textwrap::{Options, WrapAlgorithm, wrap}; -/// # -/// # let lines = wrap( -/// # "To be, or not to be: that is the question", -/// # Options::new(10).wrap_algorithm(WrapAlgorithm::new_optimal_fit()) -/// # ); -/// # assert_eq!(lines.join("\n") + "\n", "\ -/// To be, -/// or not to -/// be: that -/// is the -/// question -/// # "); } -/// ``` -/// -/// Please see [`WrapAlgorithm`] for details on the choices. -/// -/// # Examples -/// -/// The returned iterator yields lines of type `Cow<'_, str>`. If -/// possible, the wrapped lines will borrow from the input string. As -/// an example, a hanging indentation, the first line can borrow from -/// the input, but the subsequent lines become owned strings: -/// -/// ``` -/// use std::borrow::Cow::{Borrowed, Owned}; -/// use textwrap::{wrap, Options}; -/// -/// let options = Options::new(15).subsequent_indent("...."); -/// let lines = wrap("Wrapping text all day long.", &options); -/// let annotated = lines -/// .iter() -/// .map(|line| match line { -/// Borrowed(text) => format!("[Borrowed] {}", text), -/// Owned(text) => format!("[Owned] {}", text), -/// }) -/// .collect::<Vec<_>>(); -/// assert_eq!( -/// annotated, -/// &[ -/// "[Borrowed] Wrapping text", -/// "[Owned] ....all day", -/// "[Owned] ....long.", -/// ] -/// ); -/// ``` -/// -/// ## Leading and Trailing Whitespace -/// -/// As a rule, leading whitespace (indentation) is preserved and -/// trailing whitespace is discarded. -/// -/// In more details, when wrapping words into lines, words are found -/// by splitting the input text on space characters. One or more -/// spaces (shown here as “␣”) are attached to the end of each word: -/// -/// ```text -/// "Foo␣␣␣bar␣baz" -> ["Foo␣␣␣", "bar␣", "baz"] -/// ``` -/// -/// These words are then put into lines. The interword whitespace is -/// preserved, unless the lines are wrapped so that the `"Foo␣␣␣"` -/// word falls at the end of a line: -/// -/// ``` -/// use textwrap::wrap; -/// -/// assert_eq!(wrap("Foo bar baz", 10), vec!["Foo bar", "baz"]); -/// assert_eq!(wrap("Foo bar baz", 8), vec!["Foo", "bar baz"]); -/// ``` -/// -/// Notice how the trailing whitespace is removed in both case: in the -/// first example, `"bar␣"` becomes `"bar"` and in the second case -/// `"Foo␣␣␣"` becomes `"Foo"`. -/// -/// Leading whitespace is preserved when the following word fits on -/// the first line. To understand this, consider how words are found -/// in a text with leading spaces: -/// -/// ```text -/// "␣␣foo␣bar" -> ["␣␣", "foo␣", "bar"] -/// ``` -/// -/// When put into lines, the indentation is preserved if `"foo"` fits -/// on the first line, otherwise you end up with an empty line: -/// -/// ``` -/// use textwrap::wrap; -/// -/// assert_eq!(wrap(" foo bar", 8), vec![" foo", "bar"]); -/// assert_eq!(wrap(" foo bar", 4), vec!["", "foo", "bar"]); -/// ``` -pub fn wrap<'a, Opt>(text: &str, width_or_options: Opt) -> Vec<Cow<'_, str>> -where - Opt: Into<Options<'a>>, -{ - let options = width_or_options.into(); - - let initial_width = options - .width - .saturating_sub(core::display_width(options.initial_indent)); - let subsequent_width = options - .width - .saturating_sub(core::display_width(options.subsequent_indent)); - - let mut lines = Vec::new(); - for line in text.split('\n') { - let words = options.word_separator.find_words(line); - let split_words = word_splitters::split_words(words, &options.word_splitter); - let broken_words = if options.break_words { - let mut broken_words = core::break_words(split_words, subsequent_width); - if !options.initial_indent.is_empty() { - // Without this, the first word will always go into - // the first line. However, since we break words based - // on the _second_ line width, it can be wrong to - // unconditionally put the first word onto the first - // line. An empty zero-width word fixed this. - broken_words.insert(0, core::Word::from("")); - } - broken_words - } else { - split_words.collect::<Vec<_>>() - }; - - let line_widths = [initial_width, subsequent_width]; - let wrapped_words = options.wrap_algorithm.wrap(&broken_words, &line_widths); - - let mut idx = 0; - for words in wrapped_words { - let last_word = match words.last() { - None => { - lines.push(Cow::from("")); - continue; - } - Some(word) => word, - }; - - // We assume here that all words are contiguous in `line`. - // That is, the sum of their lengths should add up to the - // length of `line`. - let len = words - .iter() - .map(|word| word.len() + word.whitespace.len()) - .sum::<usize>() - - last_word.whitespace.len(); - - // The result is owned if we have indentation, otherwise - // we can simply borrow an empty string. - let mut result = if lines.is_empty() && !options.initial_indent.is_empty() { - Cow::Owned(options.initial_indent.to_owned()) - } else if !lines.is_empty() && !options.subsequent_indent.is_empty() { - Cow::Owned(options.subsequent_indent.to_owned()) - } else { - // We can use an empty string here since string - // concatenation for `Cow` preserves a borrowed value - // when either side is empty. - Cow::from("") - }; - - result += &line[idx..idx + len]; - - if !last_word.penalty.is_empty() { - result.to_mut().push_str(last_word.penalty); - } - - lines.push(result); - - // Advance by the length of `result`, plus the length of - // `last_word.whitespace` -- even if we had a penalty, we - // need to skip over the whitespace. - idx += len + last_word.whitespace.len(); - } - } - - lines -} - -/// Wrap text into columns with a given total width. -/// -/// The `left_gap`, `middle_gap` and `right_gap` arguments specify the -/// strings to insert before, between, and after the columns. The -/// total width of all columns and all gaps is specified using the -/// `total_width_or_options` argument. This argument can simply be an -/// integer if you want to use default settings when wrapping, or it -/// can be a [`Options`] value if you want to customize the wrapping. -/// -/// If the columns are narrow, it is recommended to set -/// [`Options::break_words`] to `true` to prevent words from -/// protruding into the margins. -/// -/// The per-column width is computed like this: -/// -/// ``` -/// # let (left_gap, middle_gap, right_gap) = ("", "", ""); -/// # let columns = 2; -/// # let options = textwrap::Options::new(80); -/// let inner_width = options.width -/// - textwrap::core::display_width(left_gap) -/// - textwrap::core::display_width(right_gap) -/// - textwrap::core::display_width(middle_gap) * (columns - 1); -/// let column_width = inner_width / columns; -/// ``` -/// -/// The `text` is wrapped using [`wrap`] and the given `options` -/// argument, but the width is overwritten to the computed -/// `column_width`. -/// -/// # Panics -/// -/// Panics if `columns` is zero. -/// -/// # Examples -/// -/// ``` -/// use textwrap::wrap_columns; -/// -/// let text = "\ -/// This is an example text, which is wrapped into three columns. \ -/// Notice how the final column can be shorter than the others."; -/// -/// #[cfg(feature = "smawk")] -/// assert_eq!(wrap_columns(text, 3, 50, "| ", " | ", " |"), -/// vec!["| This is | into three | column can be |", -/// "| an example | columns. | shorter than |", -/// "| text, which | Notice how | the others. |", -/// "| is wrapped | the final | |"]); -/// -/// // Without the `smawk` feature, the middle column is a little more uneven: -/// #[cfg(not(feature = "smawk"))] -/// assert_eq!(wrap_columns(text, 3, 50, "| ", " | ", " |"), -/// vec!["| This is an | three | column can be |", -/// "| example text, | columns. | shorter than |", -/// "| which is | Notice how | the others. |", -/// "| wrapped into | the final | |"]); -pub fn wrap_columns<'a, Opt>( - text: &str, - columns: usize, - total_width_or_options: Opt, - left_gap: &str, - middle_gap: &str, - right_gap: &str, -) -> Vec<String> -where - Opt: Into<Options<'a>>, -{ - assert!(columns > 0); - - let mut options = total_width_or_options.into(); - - let inner_width = options - .width - .saturating_sub(core::display_width(left_gap)) - .saturating_sub(core::display_width(right_gap)) - .saturating_sub(core::display_width(middle_gap) * (columns - 1)); - - let column_width = std::cmp::max(inner_width / columns, 1); - options.width = column_width; - let last_column_padding = " ".repeat(inner_width % column_width); - let wrapped_lines = wrap(text, options); - let lines_per_column = - wrapped_lines.len() / columns + usize::from(wrapped_lines.len() % columns > 0); - let mut lines = Vec::new(); - for line_no in 0..lines_per_column { - let mut line = String::from(left_gap); - for column_no in 0..columns { - match wrapped_lines.get(line_no + column_no * lines_per_column) { - Some(column_line) => { - line.push_str(column_line); - line.push_str(&" ".repeat(column_width - core::display_width(column_line))); - } - None => { - line.push_str(&" ".repeat(column_width)); - } - } - if column_no == columns - 1 { - line.push_str(&last_column_padding); - } else { - line.push_str(middle_gap); - } - } - line.push_str(right_gap); - lines.push(line); - } - - lines -} - -/// Fill `text` in-place without reallocating the input string. -/// -/// This function works by modifying the input string: some `' '` -/// characters will be replaced by `'\n'` characters. The rest of the -/// text remains untouched. -/// -/// Since we can only replace existing whitespace in the input with -/// `'\n'`, we cannot do hyphenation nor can we split words longer -/// than the line width. We also need to use `AsciiSpace` as the word -/// separator since we need `' '` characters between words in order to -/// replace some of them with a `'\n'`. Indentation is also ruled out. -/// In other words, `fill_inplace(width)` behaves as if you had called -/// [`fill`] with these options: -/// -/// ``` -/// # use textwrap::{core, Options, WordSplitter, WordSeparator, WrapAlgorithm}; -/// # let width = 80; -/// Options { -/// width: width, -/// initial_indent: "", -/// subsequent_indent: "", -/// break_words: false, -/// word_separator: WordSeparator::AsciiSpace, -/// wrap_algorithm: WrapAlgorithm::FirstFit, -/// word_splitter: WordSplitter::NoHyphenation, -/// }; -/// ``` -/// -/// The wrap algorithm is [`WrapAlgorithm::FirstFit`] since this -/// is the fastest algorithm — and the main reason to use -/// `fill_inplace` is to get the string broken into newlines as fast -/// as possible. -/// -/// A last difference is that (unlike [`fill`]) `fill_inplace` can -/// leave trailing whitespace on lines. This is because we wrap by -/// inserting a `'\n'` at the final whitespace in the input string: -/// -/// ``` -/// let mut text = String::from("Hello World!"); -/// textwrap::fill_inplace(&mut text, 10); -/// assert_eq!(text, "Hello \nWorld!"); -/// ``` -/// -/// If we didn't do this, the word `World!` would end up being -/// indented. You can avoid this if you make sure that your input text -/// has no double spaces. -/// -/// # Performance -/// -/// In benchmarks, `fill_inplace` is about twice as fast as [`fill`]. -/// Please see the [`linear` -/// benchmark](https://github.com/mgeisler/textwrap/blob/master/benches/linear.rs) -/// for details. -pub fn fill_inplace(text: &mut String, width: usize) { - let mut indices = Vec::new(); - - let mut offset = 0; - for line in text.split('\n') { - let words = WordSeparator::AsciiSpace - .find_words(line) - .collect::<Vec<_>>(); - let wrapped_words = wrap_algorithms::wrap_first_fit(&words, &[width as f64]); - - let mut line_offset = offset; - for words in &wrapped_words[..wrapped_words.len() - 1] { - let line_len = words - .iter() - .map(|word| word.len() + word.whitespace.len()) - .sum::<usize>(); - - line_offset += line_len; - // We've advanced past all ' ' characters -- want to move - // one ' ' backwards and insert our '\n' there. - indices.push(line_offset - 1); - } - - // Advance past entire line, plus the '\n' which was removed - // by the split call above. - offset += line.len() + 1; - } - - let mut bytes = std::mem::take(text).into_bytes(); - for idx in indices { - bytes[idx] = b'\n'; - } - *text = String::from_utf8(bytes).unwrap(); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[cfg(feature = "hyphenation")] - use hyphenation::{Language, Load, Standard}; - - #[test] - fn options_agree_with_usize() { - let opt_usize = Options::from(42_usize); - let opt_options = Options::new(42); - - assert_eq!(opt_usize.width, opt_options.width); - assert_eq!(opt_usize.initial_indent, opt_options.initial_indent); - assert_eq!(opt_usize.subsequent_indent, opt_options.subsequent_indent); - assert_eq!(opt_usize.break_words, opt_options.break_words); - assert_eq!( - opt_usize.word_splitter.split_points("hello-world"), - opt_options.word_splitter.split_points("hello-world") - ); - } - - #[test] - fn no_wrap() { - assert_eq!(wrap("foo", 10), vec!["foo"]); - } - - #[test] - fn wrap_simple() { - assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]); - } - - #[test] - fn to_be_or_not() { - assert_eq!( - wrap( - "To be, or not to be, that is the question.", - Options::new(10).wrap_algorithm(WrapAlgorithm::FirstFit) - ), - vec!["To be, or", "not to be,", "that is", "the", "question."] - ); - } - - #[test] - fn multiple_words_on_first_line() { - assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]); - } - - #[test] - fn long_word() { - assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]); - } - - #[test] - fn long_words() { - assert_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]); - } - - #[test] - fn max_width() { - assert_eq!(wrap("foo bar", usize::MAX), vec!["foo bar"]); - - let text = "Hello there! This is some English text. \ - It should not be wrapped given the extents below."; - assert_eq!(wrap(text, usize::MAX), vec![text]); - } - - #[test] - fn leading_whitespace() { - assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]); - } - - #[test] - fn leading_whitespace_empty_first_line() { - // If there is no space for the first word, the first line - // will be empty. This is because the string is split into - // words like [" ", "foobar ", "baz"], which puts "foobar " on - // the second line. We never output trailing whitespace - assert_eq!(wrap(" foobar baz", 6), vec!["", "foobar", "baz"]); - } - - #[test] - fn trailing_whitespace() { - // Whitespace is only significant inside a line. After a line - // gets too long and is broken, the first word starts in - // column zero and is not indented. - assert_eq!(wrap("foo bar baz ", 5), vec!["foo", "bar", "baz"]); - } - - #[test] - fn issue_99() { - // We did not reset the in_whitespace flag correctly and did - // not handle single-character words after a line break. - assert_eq!( - wrap("aaabbbccc x yyyzzzwww", 9), - vec!["aaabbbccc", "x", "yyyzzzwww"] - ); - } - - #[test] - fn issue_129() { - // The dash is an em-dash which takes up four bytes. We used - // to panic since we tried to index into the character. - let options = Options::new(1).word_separator(WordSeparator::AsciiSpace); - assert_eq!(wrap("x – x", options), vec!["x", "–", "x"]); - } - - #[test] - fn wide_character_handling() { - assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]); - assert_eq!( - wrap( - "Hello, World!", - Options::new(15).word_separator(WordSeparator::AsciiSpace) - ), - vec!["Hello,", "World!"] - ); - - // Wide characters are allowed to break if the - // unicode-linebreak feature is enabled. - #[cfg(feature = "unicode-linebreak")] - assert_eq!( - wrap( - "Hello, World!", - Options::new(15).word_separator(WordSeparator::UnicodeBreakProperties) - ), - vec!["Hello, W", "orld!"] - ); - } - - #[test] - fn empty_line_is_indented() { - // Previously, indentation was not applied to empty lines. - // However, this is somewhat inconsistent and undesirable if - // the indentation is something like a border ("| ") which you - // want to apply to all lines, empty or not. - let options = Options::new(10).initial_indent("!!!"); - assert_eq!(fill("", &options), "!!!"); - } - - #[test] - fn indent_single_line() { - let options = Options::new(10).initial_indent(">>>"); // No trailing space - assert_eq!(fill("foo", &options), ">>>foo"); - } - - #[test] - fn indent_first_emoji() { - let options = Options::new(10).initial_indent("👉👉"); - assert_eq!( - wrap("x x x x x x x x x x x x x", &options), - vec!["👉👉x x x", "x x x x x", "x x x x x"] - ); - } - - #[test] - fn indent_multiple_lines() { - let options = Options::new(6).initial_indent("* ").subsequent_indent(" "); - assert_eq!( - wrap("foo bar baz", &options), - vec!["* foo", " bar", " baz"] - ); - } - - #[test] - fn indent_break_words() { - let options = Options::new(5).initial_indent("* ").subsequent_indent(" "); - assert_eq!(wrap("foobarbaz", &options), vec!["* foo", " bar", " baz"]); - } - - #[test] - fn initial_indent_break_words() { - // This is a corner-case showing how the long word is broken - // according to the width of the subsequent lines. The first - // fragment of the word no longer fits on the first line, - // which ends up being pure indentation. - let options = Options::new(5).initial_indent("-->"); - assert_eq!(wrap("foobarbaz", &options), vec!["-->", "fooba", "rbaz"]); - } - - #[test] - fn hyphens() { - assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]); - } - - #[test] - fn trailing_hyphen() { - let options = Options::new(5).break_words(false); - assert_eq!(wrap("foobar-", &options), vec!["foobar-"]); - } - - #[test] - fn multiple_hyphens() { - assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]); - } - - #[test] - fn hyphens_flag() { - let options = Options::new(5).break_words(false); - assert_eq!( - wrap("The --foo-bar flag.", &options), - vec!["The", "--foo-", "bar", "flag."] - ); - } - - #[test] - fn repeated_hyphens() { - let options = Options::new(4).break_words(false); - assert_eq!(wrap("foo--bar", &options), vec!["foo--bar"]); - } - - #[test] - fn hyphens_alphanumeric() { - assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]); - } - - #[test] - fn hyphens_non_alphanumeric() { - let options = Options::new(5).break_words(false); - assert_eq!(wrap("foo(-)bar", &options), vec!["foo(-)bar"]); - } - - #[test] - fn multiple_splits() { - assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]); - } - - #[test] - fn forced_split() { - let options = Options::new(5).break_words(false); - assert_eq!(wrap("foobar-baz", &options), vec!["foobar-", "baz"]); - } - - #[test] - fn multiple_unbroken_words_issue_193() { - let options = Options::new(3).break_words(false); - assert_eq!( - wrap("small large tiny", &options), - vec!["small", "large", "tiny"] - ); - assert_eq!( - wrap("small large tiny", &options), - vec!["small", "large", "tiny"] - ); - } - - #[test] - fn very_narrow_lines_issue_193() { - let options = Options::new(1).break_words(false); - assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]); - assert_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]); - } - - #[test] - fn simple_hyphens() { - let options = Options::new(8).word_splitter(WordSplitter::HyphenSplitter); - assert_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]); - } - - #[test] - fn no_hyphenation() { - let options = Options::new(8).word_splitter(WordSplitter::NoHyphenation); - assert_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]); - } - - #[test] - #[cfg(feature = "hyphenation")] - fn auto_hyphenation_double_hyphenation() { - let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(10); - assert_eq!( - wrap("Internationalization", &options), - vec!["Internatio", "nalization"] - ); - - let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary)); - assert_eq!( - wrap("Internationalization", &options), - vec!["Interna-", "tionaliza-", "tion"] - ); - } - - #[test] - #[cfg(feature = "hyphenation")] - fn auto_hyphenation_issue_158() { - let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(10); - assert_eq!( - wrap("participation is the key to success", &options), - vec!["participat", "ion is", "the key to", "success"] - ); - - let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary)); - assert_eq!( - wrap("participation is the key to success", &options), - vec!["partici-", "pation is", "the key to", "success"] - ); - } - - #[test] - #[cfg(feature = "hyphenation")] - fn split_len_hyphenation() { - // Test that hyphenation takes the width of the whitespace - // into account. - let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(15).word_splitter(WordSplitter::Hyphenation(dictionary)); - assert_eq!( - wrap("garbage collection", &options), - vec!["garbage col-", "lection"] - ); - } - - #[test] - #[cfg(feature = "hyphenation")] - fn borrowed_lines() { - // Lines that end with an extra hyphen are owned, the final - // line is borrowed. - use std::borrow::Cow::{Borrowed, Owned}; - let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary)); - let lines = wrap("Internationalization", &options); - assert_eq!(lines, vec!["Interna-", "tionaliza-", "tion"]); - if let Borrowed(s) = lines[0] { - assert!(false, "should not have been borrowed: {:?}", s); - } - if let Borrowed(s) = lines[1] { - assert!(false, "should not have been borrowed: {:?}", s); - } - if let Owned(ref s) = lines[2] { - assert!(false, "should not have been owned: {:?}", s); - } - } - - #[test] - #[cfg(feature = "hyphenation")] - fn auto_hyphenation_with_hyphen() { - let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(8).break_words(false); - assert_eq!( - wrap("over-caffinated", &options), - vec!["over-", "caffinated"] - ); - - let options = options.word_splitter(WordSplitter::Hyphenation(dictionary)); - assert_eq!( - wrap("over-caffinated", &options), - vec!["over-", "caffi-", "nated"] - ); - } - - #[test] - fn break_words() { - assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]); - } - - #[test] - fn break_words_wide_characters() { - // Even the poor man's version of `ch_width` counts these - // characters as wide. - let options = Options::new(5).word_separator(WordSeparator::AsciiSpace); - assert_eq!(wrap("Hello", options), vec!["He", "ll", "o"]); - } - - #[test] - fn break_words_zero_width() { - assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]); - } - - #[test] - fn break_long_first_word() { - assert_eq!(wrap("testx y", 4), vec!["test", "x y"]); - } - - #[test] - fn break_words_line_breaks() { - assert_eq!(fill("ab\ncdefghijkl", 5), "ab\ncdefg\nhijkl"); - assert_eq!(fill("abcdefgh\nijkl", 5), "abcde\nfgh\nijkl"); - } - - #[test] - fn break_words_empty_lines() { - assert_eq!( - fill("foo\nbar", &Options::new(2).break_words(false)), - "foo\nbar" - ); - } - - #[test] - fn preserve_line_breaks() { - assert_eq!(fill("", 80), ""); - assert_eq!(fill("\n", 80), "\n"); - assert_eq!(fill("\n\n\n", 80), "\n\n\n"); - assert_eq!(fill("test\n", 80), "test\n"); - assert_eq!(fill("test\n\na\n\n", 80), "test\n\na\n\n"); - assert_eq!( - fill( - "1 3 5 7\n1 3 5 7", - Options::new(7).wrap_algorithm(WrapAlgorithm::FirstFit) - ), - "1 3 5 7\n1 3 5 7" - ); - assert_eq!( - fill( - "1 3 5 7\n1 3 5 7", - Options::new(5).wrap_algorithm(WrapAlgorithm::FirstFit) - ), - "1 3 5\n7\n1 3 5\n7" - ); - } - - #[test] - fn preserve_line_breaks_with_whitespace() { - assert_eq!(fill(" ", 80), ""); - assert_eq!(fill(" \n ", 80), "\n"); - assert_eq!(fill(" \n \n \n ", 80), "\n\n\n"); - } - - #[test] - fn non_breaking_space() { - let options = Options::new(5).break_words(false); - assert_eq!(fill("foo bar baz", &options), "foo bar baz"); - } - - #[test] - fn non_breaking_hyphen() { - let options = Options::new(5).break_words(false); - assert_eq!(fill("foo‑bar‑baz", &options), "foo‑bar‑baz"); - } - - #[test] - fn fill_simple() { - assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz"); - } - - #[test] - fn fill_colored_text() { - // The words are much longer than 6 bytes, but they remain - // intact after filling the text. - let green_hello = "\u{1b}[0m\u{1b}[32mHello\u{1b}[0m"; - let blue_world = "\u{1b}[0m\u{1b}[34mWorld!\u{1b}[0m"; - assert_eq!( - fill(&(String::from(green_hello) + " " + &blue_world), 6), - String::from(green_hello) + "\n" + &blue_world - ); - } - - #[test] - fn fill_unicode_boundary() { - // https://github.com/mgeisler/textwrap/issues/390 - fill("\u{1b}!Ͽ", 10); - } - - #[test] - fn fill_inplace_empty() { - let mut text = String::from(""); - fill_inplace(&mut text, 80); - assert_eq!(text, ""); - } - - #[test] - fn fill_inplace_simple() { - let mut text = String::from("foo bar baz"); - fill_inplace(&mut text, 10); - assert_eq!(text, "foo bar\nbaz"); - } - - #[test] - fn fill_inplace_multiple_lines() { - let mut text = String::from("Some text to wrap over multiple lines"); - fill_inplace(&mut text, 12); - assert_eq!(text, "Some text to\nwrap over\nmultiple\nlines"); - } - - #[test] - fn fill_inplace_long_word() { - let mut text = String::from("Internationalization is hard"); - fill_inplace(&mut text, 10); - assert_eq!(text, "Internationalization\nis hard"); - } - - #[test] - fn fill_inplace_no_hyphen_splitting() { - let mut text = String::from("A well-chosen example"); - fill_inplace(&mut text, 10); - assert_eq!(text, "A\nwell-chosen\nexample"); - } - - #[test] - fn fill_inplace_newlines() { - let mut text = String::from("foo bar\n\nbaz\n\n\n"); - fill_inplace(&mut text, 10); - assert_eq!(text, "foo bar\n\nbaz\n\n\n"); - } - - #[test] - fn fill_inplace_newlines_reset_line_width() { - let mut text = String::from("1 3 5\n1 3 5 7 9\n1 3 5 7 9 1 3"); - fill_inplace(&mut text, 10); - assert_eq!(text, "1 3 5\n1 3 5 7 9\n1 3 5 7 9\n1 3"); - } - - #[test] - fn fill_inplace_leading_whitespace() { - let mut text = String::from(" foo bar baz"); - fill_inplace(&mut text, 10); - assert_eq!(text, " foo bar\nbaz"); - } - - #[test] - fn fill_inplace_trailing_whitespace() { - let mut text = String::from("foo bar baz "); - fill_inplace(&mut text, 10); - assert_eq!(text, "foo bar\nbaz "); - } - - #[test] - fn fill_inplace_interior_whitespace() { - // To avoid an unwanted indentation of "baz", it is important - // to replace the final ' ' with '\n'. - let mut text = String::from("foo bar baz"); - fill_inplace(&mut text, 10); - assert_eq!(text, "foo bar \nbaz"); - } - - #[test] - fn unfill_simple() { - let (text, options) = unfill("foo\nbar"); - assert_eq!(text, "foo bar"); - assert_eq!(options.width, 3); - } - - #[test] - fn unfill_trailing_newlines() { - let (text, options) = unfill("foo\nbar\n\n\n"); - assert_eq!(text, "foo bar\n\n\n"); - assert_eq!(options.width, 3); - } - - #[test] - fn unfill_initial_indent() { - let (text, options) = unfill(" foo\nbar\nbaz"); - assert_eq!(text, "foo bar baz"); - assert_eq!(options.width, 5); - assert_eq!(options.initial_indent, " "); - } - - #[test] - fn unfill_differing_indents() { - let (text, options) = unfill(" foo\n bar\n baz"); - assert_eq!(text, "foo bar baz"); - assert_eq!(options.width, 7); - assert_eq!(options.initial_indent, " "); - assert_eq!(options.subsequent_indent, " "); - } - - #[test] - fn unfill_list_item() { - let (text, options) = unfill("* foo\n bar\n baz"); - assert_eq!(text, "foo bar baz"); - assert_eq!(options.width, 5); - assert_eq!(options.initial_indent, "* "); - assert_eq!(options.subsequent_indent, " "); - } - - #[test] - fn unfill_multiple_char_prefix() { - let (text, options) = unfill(" // foo bar\n // baz\n // quux"); - assert_eq!(text, "foo bar baz quux"); - assert_eq!(options.width, 14); - assert_eq!(options.initial_indent, " // "); - assert_eq!(options.subsequent_indent, " // "); - } - - #[test] - fn unfill_block_quote() { - let (text, options) = unfill("> foo\n> bar\n> baz"); - assert_eq!(text, "foo bar baz"); - assert_eq!(options.width, 5); - assert_eq!(options.initial_indent, "> "); - assert_eq!(options.subsequent_indent, "> "); - } - - #[test] - fn unfill_whitespace() { - assert_eq!(unfill("foo bar").0, "foo bar"); - } - - #[test] - fn wrap_columns_empty_text() { - assert_eq!(wrap_columns("", 1, 10, "| ", "", " |"), vec!["| |"]); - } - - #[test] - fn wrap_columns_single_column() { - assert_eq!( - wrap_columns("Foo", 3, 30, "| ", " | ", " |"), - vec!["| Foo | | |"] - ); - } - - #[test] - fn wrap_columns_uneven_columns() { - // The gaps take up a total of 5 columns, so the columns are - // (21 - 5)/4 = 4 columns wide: - assert_eq!( - wrap_columns("Foo Bar Baz Quux", 4, 21, "|", "|", "|"), - vec!["|Foo |Bar |Baz |Quux|"] - ); - // As the total width increases, the last column absorbs the - // excess width: - assert_eq!( - wrap_columns("Foo Bar Baz Quux", 4, 24, "|", "|", "|"), - vec!["|Foo |Bar |Baz |Quux |"] - ); - // Finally, when the width is 25, the columns can be resized - // to a width of (25 - 5)/4 = 5 columns: - assert_eq!( - wrap_columns("Foo Bar Baz Quux", 4, 25, "|", "|", "|"), - vec!["|Foo |Bar |Baz |Quux |"] - ); - } - - #[test] - #[cfg(feature = "unicode-width")] - fn wrap_columns_with_emojis() { - assert_eq!( - wrap_columns( - "Words and a few emojis 😍 wrapped in ⓶ columns", - 2, - 30, - "✨ ", - " ⚽ ", - " 👀" - ), - vec![ - "✨ Words ⚽ wrapped in 👀", - "✨ and a few ⚽ ⓶ columns 👀", - "✨ emojis 😍 ⚽ 👀" - ] - ); - } - - #[test] - fn wrap_columns_big_gaps() { - // The column width shrinks to 1 because the gaps take up all - // the space. - assert_eq!( - wrap_columns("xyz", 2, 10, "----> ", " !!! ", " <----"), - vec![ - "----> x !!! z <----", // - "----> y !!! <----" - ] - ); - } - - #[test] - #[should_panic] - fn wrap_columns_panic_with_zero_columns() { - wrap_columns("", 0, 10, "", "", ""); - } -} diff --git a/vendor/textwrap/src/word_separators.rs b/vendor/textwrap/src/word_separators.rs deleted file mode 100644 index 25adf31..0000000 --- a/vendor/textwrap/src/word_separators.rs +++ /dev/null @@ -1,428 +0,0 @@ -//! 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) don’t 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)] - ); - } -} diff --git a/vendor/textwrap/src/word_splitters.rs b/vendor/textwrap/src/word_splitters.rs deleted file mode 100644 index 69e246f..0000000 --- a/vendor/textwrap/src/word_splitters.rs +++ /dev/null @@ -1,314 +0,0 @@ -//! 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: "" - } - ] - ); - } -} diff --git a/vendor/textwrap/src/wrap_algorithms.rs b/vendor/textwrap/src/wrap_algorithms.rs deleted file mode 100644 index 5ca49c3..0000000 --- a/vendor/textwrap/src/wrap_algorithms.rs +++ /dev/null @@ -1,381 +0,0 @@ -//! 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)] - ] - ); - } -} diff --git a/vendor/textwrap/src/wrap_algorithms/optimal_fit.rs b/vendor/textwrap/src/wrap_algorithms/optimal_fit.rs deleted file mode 100644 index 0625e28..0000000 --- a/vendor/textwrap/src/wrap_algorithms/optimal_fit.rs +++ /dev/null @@ -1,433 +0,0 @@ -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)][..]]) - ); - } -} diff --git a/vendor/textwrap/tests/indent.rs b/vendor/textwrap/tests/indent.rs deleted file mode 100644 index 9dd5ad2..0000000 --- a/vendor/textwrap/tests/indent.rs +++ /dev/null @@ -1,88 +0,0 @@ -/// 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); - } -} diff --git a/vendor/textwrap/tests/version-numbers.rs b/vendor/textwrap/tests/version-numbers.rs deleted file mode 100644 index 3f429b1..0000000 --- a/vendor/textwrap/tests/version-numbers.rs +++ /dev/null @@ -1,22 +0,0 @@ -#[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"); -} |