diff options
Diffstat (limited to 'src/lib.rs')
-rw-r--r-- | src/lib.rs | 600 |
1 files changed, 156 insertions, 444 deletions
@@ -7,47 +7,35 @@ //! you want to format dynamic output nicely so it looks good in a //! terminal. A quick example: //! -//! ```no_run -//! fn main() { -//! let text = "textwrap: a small library for wrapping text."; -//! println!("{}", textwrap::fill(text, 18)); -//! } //! ``` -//! -//! When you run this program, it will display the following output: -//! -//! ```text -//! textwrap: a small -//! library for -//! wrapping text. +//! # #[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: //! -//! ```no_run -//! # #[cfg(feature = "hyphenation")] +//! ``` +//! #[cfg(feature = "hyphenation")] { //! use hyphenation::{Language, Load, Standard}; -//! use textwrap::{fill, Options}; +//! use textwrap::{wrap, Options, WordSplitter}; //! -//! # #[cfg(feature = "hyphenation")] -//! fn main() { -//! let text = "textwrap: a small library for wrapping text."; -//! let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); -//! let options = Options::new(18).word_splitter(dictionary); -//! println!("{}", fill(text, &options)); +//! 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."]); //! } -//! -//! # #[cfg(not(feature = "hyphenation"))] -//! # fn main() { } -//! ``` -//! -//! The program will now output: -//! -//! ```text -//! textwrap: a small -//! library for wrap- -//! ping text. //! ``` //! //! See also the [`unfill`] and [`refill`] functions which allow you to @@ -124,7 +112,7 @@ //! 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.14.2.svg"> +//! <img src="https://raw.githubusercontent.com/mgeisler/textwrap/master/images/textwrap-0.15.0.svg"> //! //! ## Default Features //! @@ -138,8 +126,7 @@ //! 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 the -//! [`word_separators::WordSeparator`] trait for details. +//! 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, @@ -159,6 +146,29 @@ //! 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: @@ -168,71 +178,61 @@ //! [`Options::with_termwidth`] constructor for details. //! //! * `hyphenation`: enables language-sensitive hyphenation via the -//! [hyphenation] crate. See the [`word_splitters::WordSplitter`] trait for details. +//! [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.14.2")] +#![doc(html_root_url = "https://docs.rs/textwrap/0.15.0")] #![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; -pub use crate::indentation::indent; +pub use crate::indentation::{dedent, indent}; + +mod word_separators; +pub use word_separators::WordSeparator; -pub mod word_separators; pub mod word_splitters; +pub use word_splitters::WordSplitter; + pub mod wrap_algorithms; +pub use wrap_algorithms::WrapAlgorithm; pub mod core; -// These private macros lets us hide the actual WrapAlgorithm and -// WordSeperator used in the function signatures below. -#[cfg(feature = "smawk")] -macro_rules! DefaultWrapAlgorithm { - () => { - wrap_algorithms::OptimalFit - }; -} - -#[cfg(not(feature = "smawk"))] -macro_rules! DefaultWrapAlgorithm { - () => { - wrap_algorithms::FirstFit - }; -} - #[cfg(feature = "unicode-linebreak")] macro_rules! DefaultWordSeparator { () => { - word_separators::UnicodeBreakProperties + WordSeparator::UnicodeBreakProperties }; } #[cfg(not(feature = "unicode-linebreak"))] macro_rules! DefaultWordSeparator { () => { - word_separators::AsciiSpace + WordSeparator::AsciiSpace }; } -/// Holds settings for wrapping and filling text. +/// Holds configuration options for wrapping and filling text. #[derive(Debug, Clone)] -pub struct Options< - 'a, - WrapAlgo = Box<dyn wrap_algorithms::WrapAlgorithm>, - WordSep = Box<dyn word_separators::WordSeparator>, - WordSplit = Box<dyn word_splitters::WordSplitter>, -> { +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 @@ -247,62 +247,42 @@ pub struct Options< pub break_words: bool, /// Wrapping algorithm to use, see the implementations of the /// [`wrap_algorithms::WrapAlgorithm`] trait for details. - pub wrap_algorithm: WrapAlgo, + pub wrap_algorithm: WrapAlgorithm, /// The line breaking algorithm to use, see /// [`word_separators::WordSeparator`] trait for an overview and /// possible implementations. - pub word_separator: WordSep, + 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. Please see the - /// [`word_splitters::WordSplitter`] trait for details. - pub word_splitter: WordSplit, + /// language-aware machine hyphenation. + pub word_splitter: WordSplitter, } -impl<'a, WrapAlgo, WordSep, WordSplit> From<&'a Options<'a, WrapAlgo, WordSep, WordSplit>> - for Options<'a, WrapAlgo, WordSep, WordSplit> -where - WrapAlgo: Clone, - WordSep: Clone, - WordSplit: Clone, -{ - fn from(options: &'a Options<'a, WrapAlgo, WordSep, WordSplit>) -> Self { +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.clone(), - wrap_algorithm: options.wrap_algorithm.clone(), + word_separator: options.word_separator, + wrap_algorithm: options.wrap_algorithm, word_splitter: options.word_splitter.clone(), } } } -impl<'a> From<usize> - for Options< - 'a, - DefaultWrapAlgorithm!(), - DefaultWordSeparator!(), - word_splitters::HyphenSplitter, - > -{ +impl<'a> From<usize> for Options<'a> { fn from(width: usize) -> Self { Options::new(width) } } -/// Constructors for boxed Options, specifically. -impl<'a> - Options<'a, DefaultWrapAlgorithm!(), DefaultWordSeparator!(), word_splitters::HyphenSplitter> -{ - /// Creates a new [`Options`] with the specified width and static - /// dispatch using the [`word_splitters::HyphenSplitter`]. - /// Equivalent to +impl<'a> Options<'a> { + /// Creates a new [`Options`] with the specified width. Equivalent to /// /// ``` - /// # use textwrap::word_splitters::{HyphenSplitter, WordSplitter}; - /// # use textwrap::Options; + /// # use textwrap::{Options, WordSplitter, WordSeparator, WrapAlgorithm}; /// # let width = 80; /// # let actual = Options::new(width); /// # let expected = @@ -312,76 +292,36 @@ impl<'a> /// subsequent_indent: "", /// break_words: true, /// #[cfg(feature = "unicode-linebreak")] - /// word_separator: textwrap::word_separators::UnicodeBreakProperties, + /// word_separator: WordSeparator::UnicodeBreakProperties, /// #[cfg(not(feature = "unicode-linebreak"))] - /// word_separator: textwrap::word_separators::AsciiSpace, + /// word_separator: WordSeparator::AsciiSpace, /// #[cfg(feature = "smawk")] - /// wrap_algorithm: textwrap::wrap_algorithms::OptimalFit, + /// wrap_algorithm: WrapAlgorithm::new_optimal_fit(), /// #[cfg(not(feature = "smawk"))] - /// wrap_algorithm: textwrap::wrap_algorithms::FirstFit, - /// word_splitter: textwrap::word_splitters::HyphenSplitter, + /// 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 algorithm is used by default. - /// - /// Static dispatch means here, that the word splitter is stored as-is - /// and the type is known at compile-time. Thus the returned value - /// is actually a `Options<AsciiSpace, HyphenSplitter>`. - /// - /// Dynamic dispatch on the other hand, means that the word - /// separator and/or word splitter is stored as a trait object - /// such as a `Box<dyn word_splitters::WordSplitter>`. This way - /// the word splitter's inner type can be changed without changing - /// the type of this struct, which then would be just `Options` as - /// a short cut for `Options<Box<dyn - /// word_separators::WordSeparator>, Box<dyn - /// word_splitters::WordSplitter>>`. - /// - /// The value and type of the word splitter can be choose from the - /// start using the [`Options::with_word_splitter`] constructor or - /// changed afterwards using the [`Options::word_splitter`] - /// method. Whether static or dynamic dispatch is used, depends on - /// whether these functions are given a boxed - /// [`word_splitters::WordSplitter`] or not. Take for example: - /// - /// ``` - /// use textwrap::Options; - /// use textwrap::word_splitters::{HyphenSplitter, NoHyphenation}; - /// # use textwrap::word_splitters::WordSplitter; - /// # use textwrap::word_separators::AsciiSpace; - /// # let width = 80; - /// - /// // uses HyphenSplitter with static dispatch - /// // the actual type: Options<AsciiSpace, HyphenSplitter> - /// let opt = Options::new(width); - /// - /// // uses NoHyphenation with static dispatch - /// // the actual type: Options<AsciiSpace, NoHyphenation> - /// let opt = Options::new(width).word_splitter(NoHyphenation); - /// - /// // uses HyphenSplitter with dynamic dispatch - /// // the actual type: Options<AsciiSpace, Box<dyn word_splitters::WordSplitter>> - /// let opt: Options<_, _, _> = Options::new(width).word_splitter(Box::new(HyphenSplitter)); - /// - /// // uses NoHyphenation with dynamic dispatch - /// // the actual type: Options<AsciiSpace, Box<dyn word_splitters::WordSplitter>> - /// let opt: Options<_, _, _> = Options::new(width).word_splitter(Box::new(NoHyphenation)); - /// ``` - /// - /// Notice that the last two variables have the same type, despite - /// the different `WordSplitter` in use. Thus dynamic dispatch - /// allows to change the word splitter at run-time without - /// changing the variables type. + /// available algorithms are used by default. pub const fn new(width: usize) -> Self { - Options::with_word_splitter(width, word_splitters::HyphenSplitter) + 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 @@ -407,97 +347,7 @@ impl<'a> } } -impl<'a, WordSplit> Options<'a, DefaultWrapAlgorithm!(), DefaultWordSeparator!(), WordSplit> { - /// Creates a new [`Options`] with the specified width and - /// word splitter. Equivalent to - /// - /// ``` - /// # use textwrap::Options; - /// # use textwrap::word_splitters::{NoHyphenation, HyphenSplitter}; - /// # const word_splitter: NoHyphenation = NoHyphenation; - /// # const width: usize = 80; - /// # let actual = Options::with_word_splitter(width, word_splitter); - /// # let expected = - /// Options { - /// width: width, - /// initial_indent: "", - /// subsequent_indent: "", - /// break_words: true, - /// #[cfg(feature = "unicode-linebreak")] - /// word_separator: textwrap::word_separators::UnicodeBreakProperties, - /// #[cfg(not(feature = "unicode-linebreak"))] - /// word_separator: textwrap::word_separators::AsciiSpace, - /// #[cfg(feature = "smawk")] - /// wrap_algorithm: textwrap::wrap_algorithms::OptimalFit, - /// #[cfg(not(feature = "smawk"))] - /// wrap_algorithm: textwrap::wrap_algorithms::FirstFit, - /// word_splitter: word_splitter, - /// } - /// # ; - /// # 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); - /// ``` - /// - /// This constructor allows to specify the word splitter to be - /// used. It is like a short-cut for - /// `Options::new(w).word_splitter(s)`, but this function is a - /// `const fn`. The given word splitter may be in a [`Box`], which - /// then can be coerced into a trait object for dynamic dispatch: - /// - /// ``` - /// use textwrap::Options; - /// use textwrap::word_splitters::{HyphenSplitter, NoHyphenation, WordSplitter}; - /// # const width: usize = 80; - /// - /// // This opt contains a boxed trait object as splitter. - /// // The type annotation is important, otherwise it will be not a trait object - /// let mut opt: Options<_, _, Box<dyn WordSplitter>> - /// = Options::with_word_splitter(width, Box::new(NoHyphenation)); - /// // Its type is actually: `Options<AsciiSpace, Box<dyn word_splitters::WordSplitter>>`: - /// let opt_coerced: Options<_, _, Box<dyn WordSplitter>> = opt; - /// - /// // Thus, it can be overridden with a different word splitter. - /// opt = Options::with_word_splitter(width, Box::new(HyphenSplitter)); - /// // Now, containing a `HyphenSplitter` instead. - /// ``` - /// - /// Since the word splitter is given by value, which determines - /// the generic type parameter, it can be used to produce both an - /// [`Options`] with static and dynamic dispatch, respectively. - /// While dynamic dispatch allows to change the type of the inner - /// word splitter at run time as seen above, static dispatch - /// especially can store the word splitter directly, without the - /// need for a box. This in turn allows it to be used in constant - /// and static context: - /// - /// ``` - /// use textwrap::word_splitters::HyphenSplitter; use textwrap::{ Options}; - /// use textwrap::word_separators::AsciiSpace; - /// use textwrap::wrap_algorithms::FirstFit; - /// # const width: usize = 80; - /// - /// # #[cfg(all(not(feature = "smawk"), not(feature = "unicode-linebreak")))] { - /// const FOO: Options<FirstFit, AsciiSpace, HyphenSplitter> = - /// Options::with_word_splitter(width, HyphenSplitter); - /// static BAR: Options<FirstFit, AsciiSpace, HyphenSplitter> = FOO; - /// # } - /// ``` - pub const fn with_word_splitter(width: usize, word_splitter: WordSplit) -> Self { - Options { - width, - initial_indent: "", - subsequent_indent: "", - break_words: true, - word_separator: DefaultWordSeparator!(), - wrap_algorithm: DefaultWrapAlgorithm!(), - word_splitter: word_splitter, - } - } -} - -impl<'a, WrapAlgo, WordSep, WordSplit> Options<'a, WrapAlgo, WordSep, WordSplit> { +impl<'a> Options<'a> { /// Change [`self.initial_indent`]. The initial indentation is /// used on the very first line of output. /// @@ -507,7 +357,7 @@ impl<'a, WrapAlgo, WordSep, WordSplit> Options<'a, WrapAlgo, WordSep, WordSplit> /// initial indentation and wrapping each paragraph by itself: /// /// ``` - /// use textwrap::{Options, wrap}; + /// use textwrap::{wrap, Options}; /// /// let options = Options::new(16).initial_indent(" "); /// assert_eq!(wrap("This is a little example.", options), @@ -532,7 +382,7 @@ impl<'a, WrapAlgo, WordSep, WordSplit> Options<'a, WrapAlgo, WordSep, WordSplit> /// single paragraph as a bullet list: /// /// ``` - /// use textwrap::{Options, wrap}; + /// use textwrap::{wrap, Options}; /// /// let options = Options::new(12) /// .initial_indent("* ") @@ -591,10 +441,7 @@ impl<'a, WrapAlgo, WordSep, WordSplit> Options<'a, WrapAlgo, WordSep, WordSplit> /// See [`word_separators::WordSeparator`] for details on the choices. /// /// [`self.word_separator`]: #structfield.word_separator - pub fn word_separator<NewWordSep>( - self, - word_separator: NewWordSep, - ) -> Options<'a, WrapAlgo, NewWordSep, WordSplit> { + pub fn word_separator(self, word_separator: WordSeparator) -> Options<'a> { Options { width: self.width, initial_indent: self.initial_indent, @@ -612,10 +459,7 @@ impl<'a, WrapAlgo, WordSep, WordSplit> Options<'a, WrapAlgo, WordSep, WordSplit> /// the choices. /// /// [`self.wrap_algorithm`]: #structfield.wrap_algorithm - pub fn wrap_algorithm<NewWrapAlgo>( - self, - wrap_algorithm: NewWrapAlgo, - ) -> Options<'a, NewWrapAlgo, WordSep, WordSplit> { + pub fn wrap_algorithm(self, wrap_algorithm: WrapAlgorithm) -> Options<'a> { Options { width: self.width, initial_indent: self.initial_indent, @@ -631,25 +475,18 @@ impl<'a, WrapAlgo, WordSep, WordSplit> Options<'a, WrapAlgo, WordSep, WordSplit> /// [`word_splitters::WordSplitter`] is used to fit part of a word /// into the current line when wrapping text. /// - /// This function may return a different type than `Self`. That is - /// the case when the given `splitter` is of a different type the - /// the currently stored one in the `splitter` field. Take for - /// example: + /// # Examples /// /// ``` - /// use textwrap::word_splitters::{HyphenSplitter, NoHyphenation}; - /// use textwrap::Options; - /// // The default type returned by `new`: - /// let opt: Options<_, _, HyphenSplitter> = Options::new(80); - /// // Setting a different word splitter changes the type - /// let opt: Options<_, _, NoHyphenation> = opt.word_splitter(NoHyphenation); + /// 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<NewWordSplit>( - self, - word_splitter: NewWordSplit, - ) -> Options<'a, WrapAlgo, WordSep, NewWordSplit> { + pub fn word_splitter(self, word_splitter: WordSplitter) -> Options<'a> { Options { width: self.width, initial_indent: self.initial_indent, @@ -675,11 +512,9 @@ impl<'a, WrapAlgo, WordSep, WordSplit> Options<'a, WrapAlgo, WordSep, WordSplit> /// /// ```no_run /// use textwrap::{termwidth, Options}; -/// use textwrap::word_splitters::NoHyphenation; /// /// let width = termwidth() - 4; // Two columns on each side. /// let options = Options::new(width) -/// .word_splitter(NoHyphenation) /// .initial_indent(" ") /// .subsequent_indent(" "); /// ``` @@ -723,12 +558,9 @@ pub fn termwidth() -> usize { /// "- Memory safety\n without\n garbage\n collection." /// ); /// ``` -pub fn fill<'a, WrapAlgo, WordSep, WordSplit, Opt>(text: &str, width_or_options: Opt) -> String +pub fn fill<'a, Opt>(text: &str, width_or_options: Opt) -> String where - WrapAlgo: wrap_algorithms::WrapAlgorithm, - WordSep: word_separators::WordSeparator, - WordSplit: word_splitters::WordSplitter, - Opt: Into<Options<'a, WrapAlgo, WordSep, WordSplit>>, + Opt: Into<Options<'a>>, { // This will avoid reallocation in simple cases (no // indentation, no hyphenation). @@ -738,7 +570,7 @@ where if i > 0 { result.push('\n'); } - result.push_str(&line); + result.push_str(line); } result @@ -790,12 +622,7 @@ where /// assert_eq!(options.initial_indent, "* "); /// assert_eq!(options.subsequent_indent, " "); /// ``` -pub fn unfill( - text: &str, -) -> ( - String, - Options<'_, DefaultWrapAlgorithm!(), DefaultWordSeparator!(), word_splitters::HyphenSplitter>, -) { +pub fn unfill(text: &str) -> (String, Options<'_>) { let trimmed = text.trim_end_matches('\n'); let prefix_chars: &[_] = &[' ', '-', '+', '*', '>', '#', '/']; @@ -890,15 +717,9 @@ pub fn unfill( /// item. /// "); /// ``` -pub fn refill<'a, WrapAlgo, WordSep, WordSplit, Opt>( - filled_text: &str, - new_width_or_options: Opt, -) -> String +pub fn refill<'a, Opt>(filled_text: &str, new_width_or_options: Opt) -> String where - WrapAlgo: wrap_algorithms::WrapAlgorithm, - WordSep: word_separators::WordSeparator, - WordSplit: word_splitters::WordSplitter, - Opt: Into<Options<'a, WrapAlgo, WordSep, WordSplit>>, + Opt: Into<Options<'a>>, { let trimmed = filled_text.trim_end_matches('\n'); let (text, options) = unfill(trimmed); @@ -964,7 +785,7 @@ where /// narrow column with room for only 10 characters looks like this: /// /// ``` -/// # use textwrap::{wrap_algorithms::FirstFit, Options, wrap}; +/// # 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)); @@ -988,11 +809,12 @@ where /// /// ``` /// # #[cfg(feature = "smawk")] { -/// # use textwrap::{Options, wrap}; -/// # use textwrap::wrap_algorithms::OptimalFit; +/// # use textwrap::{Options, WrapAlgorithm, wrap}; /// # -/// # let lines = wrap("To be, or not to be: that is the question", -/// # Options::new(10).wrap_algorithm(OptimalFit)); +/// # 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 @@ -1002,7 +824,7 @@ where /// # "); } /// ``` /// -/// Please see the [`wrap_algorithms::WrapAlgorithm`] trait for details. +/// Please see [`WrapAlgorithm`] for details on the choices. /// /// # Examples /// @@ -1079,15 +901,9 @@ where /// assert_eq!(wrap(" foo bar", 8), vec![" foo", "bar"]); /// assert_eq!(wrap(" foo bar", 4), vec!["", "foo", "bar"]); /// ``` -pub fn wrap<'a, WrapAlgo, WordSep, WordSplit, Opt>( - text: &str, - width_or_options: Opt, -) -> Vec<Cow<'_, str>> +pub fn wrap<'a, Opt>(text: &str, width_or_options: Opt) -> Vec<Cow<'_, str>> where - WrapAlgo: wrap_algorithms::WrapAlgorithm, - WordSep: word_separators::WordSeparator, - WordSplit: word_splitters::WordSplitter, - Opt: Into<Options<'a, WrapAlgo, WordSep, WordSplit>>, + Opt: Into<Options<'a>>, { let options = width_or_options.into(); @@ -1155,7 +971,7 @@ where result += &line[idx..idx + len]; if !last_word.penalty.is_empty() { - result.to_mut().push_str(&last_word.penalty); + result.to_mut().push_str(last_word.penalty); } lines.push(result); @@ -1227,7 +1043,7 @@ where /// "| example text, | columns. | shorter than |", /// "| which is | Notice how | the others. |", /// "| wrapped into | the final | |"]); -pub fn wrap_columns<'a, WrapAlgo, WordSep, WordSplit, Opt>( +pub fn wrap_columns<'a, Opt>( text: &str, columns: usize, total_width_or_options: Opt, @@ -1236,10 +1052,7 @@ pub fn wrap_columns<'a, WrapAlgo, WordSep, WordSplit, Opt>( right_gap: &str, ) -> Vec<String> where - WrapAlgo: wrap_algorithms::WrapAlgorithm, - WordSep: word_separators::WordSeparator, - WordSplit: word_splitters::WordSplitter, - Opt: Into<Options<'a, WrapAlgo, WordSep, WordSplit>>, + Opt: Into<Options<'a>>, { assert!(columns > 0); @@ -1263,8 +1076,8 @@ where 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))); + line.push_str(column_line); + line.push_str(&" ".repeat(column_width - core::display_width(column_line))); } None => { line.push_str(&" ".repeat(column_width)); @@ -1298,21 +1111,20 @@ where /// [`fill`] with these options: /// /// ``` -/// # use textwrap::{core, Options}; -/// # use textwrap::{word_separators, word_splitters, wrap_algorithms}; +/// # use textwrap::{core, Options, WordSplitter, WordSeparator, WrapAlgorithm}; /// # let width = 80; /// Options { /// width: width, /// initial_indent: "", /// subsequent_indent: "", /// break_words: false, -/// word_separator: word_separators::AsciiSpace, -/// wrap_algorithm: wrap_algorithms::FirstFit, -/// word_splitter: word_splitters::NoHyphenation, +/// word_separator: WordSeparator::AsciiSpace, +/// wrap_algorithm: WrapAlgorithm::FirstFit, +/// word_splitter: WordSplitter::NoHyphenation, /// }; /// ``` /// -/// The wrap algorithm is [`wrap_algorithms::FirstFit`] since this +/// 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. @@ -1338,15 +1150,14 @@ where /// benchmark](https://github.com/mgeisler/textwrap/blob/master/benches/linear.rs) /// for details. pub fn fill_inplace(text: &mut String, width: usize) { - use word_separators::WordSeparator; let mut indices = Vec::new(); let mut offset = 0; for line in text.split('\n') { - let words = word_separators::AsciiSpace + let words = WordSeparator::AsciiSpace .find_words(line) .collect::<Vec<_>>(); - let wrapped_words = wrap_algorithms::wrap_first_fit(&words, &[width]); + 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] { @@ -1376,8 +1187,6 @@ pub fn fill_inplace(text: &mut String, width: usize) { #[cfg(test)] mod tests { use super::*; - use crate::word_splitters::WordSplitter; - use crate::{word_splitters, wrap_algorithms}; #[cfg(feature = "hyphenation")] use hyphenation::{Language, Load, Standard}; @@ -1412,7 +1221,7 @@ mod tests { assert_eq!( wrap( "To be, or not to be, that is the question.", - Options::new(10).wrap_algorithm(wrap_algorithms::FirstFit) + Options::new(10).wrap_algorithm(WrapAlgorithm::FirstFit) ), vec!["To be, or", "not to be,", "that is", "the", "question."] ); @@ -1435,7 +1244,11 @@ mod tests { #[test] fn max_width() { - assert_eq!(wrap("foo bar", usize::max_value()), vec!["foo bar"]); + 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] @@ -1474,18 +1287,17 @@ mod tests { 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(word_separators::AsciiSpace); + let options = Options::new(1).word_separator(WordSeparator::AsciiSpace); assert_eq!(wrap("x – x", options), vec!["x", "–", "x"]); } #[test] - #[cfg(feature = "unicode-width")] fn wide_character_handling() { assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]); assert_eq!( wrap( "Hello, World!", - Options::new(15).word_separator(word_separators::AsciiSpace) + Options::new(15).word_separator(WordSeparator::AsciiSpace) ), vec!["Hello,", "World!"] ); @@ -1496,7 +1308,7 @@ mod tests { assert_eq!( wrap( "Hello, World!", - Options::new(15).word_separator(word_separators::UnicodeBreakProperties) + Options::new(15).word_separator(WordSeparator::UnicodeBreakProperties) ), vec!["Hello, W", "orld!"] ); @@ -1519,7 +1331,6 @@ mod tests { } #[test] - #[cfg(feature = "unicode-width")] fn indent_first_emoji() { let options = Options::new(10).initial_indent("👉👉"); assert_eq!( @@ -1627,34 +1438,20 @@ mod tests { } #[test] - fn simple_hyphens_static() { - let options = Options::new(8).word_splitter(word_splitters::HyphenSplitter); - assert_eq!(wrap("foo bar-baz", &options), vec!["foo bar-", "baz"]); - } - - #[test] - fn simple_hyphens_dynamic() { - let options: Options<_, _> = - Options::new(8).word_splitter(Box::new(word_splitters::HyphenSplitter)); + 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_static() { - let options = Options::new(8).word_splitter(word_splitters::NoHyphenation); - assert_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]); - } - - #[test] - fn no_hyphenation_dynamic() { - let options: Options<_, _> = - Options::new(8).word_splitter(Box::new(word_splitters::NoHyphenation)); + 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_static() { + fn auto_hyphenation_double_hyphenation() { let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); let options = Options::new(10); assert_eq!( @@ -1662,25 +1459,7 @@ mod tests { vec!["Internatio", "nalization"] ); - let options = Options::new(10).word_splitter(dictionary); - assert_eq!( - wrap("Internationalization", &options), - vec!["Interna-", "tionaliza-", "tion"] - ); - } - - #[test] - #[cfg(feature = "hyphenation")] - fn auto_hyphenation_double_hyphenation_dynamic() { - let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let mut options: Options<_, _, Box<dyn word_splitters::WordSplitter>> = - Options::new(10).word_splitter(Box::new(word_splitters::HyphenSplitter)); - assert_eq!( - wrap("Internationalization", &options), - vec!["Internatio", "nalization"] - ); - - options = Options::new(10).word_splitter(Box::new(dictionary)); + let options = Options::new(10).word_splitter(WordSplitter::Hyphenation(dictionary)); assert_eq!( wrap("Internationalization", &options), vec!["Interna-", "tionaliza-", "tion"] @@ -1697,7 +1476,7 @@ mod tests { vec!["participat", "ion is", "the key to", "success"] ); - let options = Options::new(10).word_splitter(dictionary); + 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"] @@ -1707,10 +1486,10 @@ mod tests { #[test] #[cfg(feature = "hyphenation")] fn split_len_hyphenation() { - // Test that hyphenation takes the width of the wihtespace + // 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(dictionary); + let options = Options::new(15).word_splitter(WordSplitter::Hyphenation(dictionary)); assert_eq!( wrap("garbage collection", &options), vec!["garbage col-", "lection"] @@ -1724,8 +1503,9 @@ mod tests { // line is borrowed. use std::borrow::Cow::{Borrowed, Owned}; let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let options = Options::new(10).word_splitter(dictionary); + 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); } @@ -1747,7 +1527,7 @@ mod tests { vec!["over-", "caffinated"] ); - let options = options.word_splitter(dictionary); + let options = options.word_splitter(WordSplitter::Hyphenation(dictionary)); assert_eq!( wrap("over-caffinated", &options), vec!["over-", "caffi-", "nated"] @@ -1763,7 +1543,7 @@ mod tests { 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(word_separators::AsciiSpace); + let options = Options::new(5).word_separator(WordSeparator::AsciiSpace); assert_eq!(wrap("Hello", options), vec!["He", "ll", "o"]); } @@ -1801,14 +1581,14 @@ mod tests { assert_eq!( fill( "1 3 5 7\n1 3 5 7", - Options::new(7).wrap_algorithm(wrap_algorithms::FirstFit) + 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(wrap_algorithms::FirstFit) + Options::new(5).wrap_algorithm(WrapAlgorithm::FirstFit) ), "1 3 5\n7\n1 3 5\n7" ); @@ -1857,20 +1637,6 @@ mod tests { } #[test] - #[cfg(not(feature = "smawk"))] - #[cfg(not(feature = "unicode-linebreak"))] - fn cloning_works() { - static OPT: Options< - wrap_algorithms::FirstFit, - word_separators::AsciiSpace, - word_splitters::HyphenSplitter, - > = Options::with_word_splitter(80, word_splitters::HyphenSplitter); - #[allow(clippy::clone_on_copy)] - let opt = OPT.clone(); - assert_eq!(opt.width, 80); - } - - #[test] fn fill_inplace_empty() { let mut text = String::from(""); fill_inplace(&mut text, 80); @@ -2006,60 +1772,6 @@ mod tests { } #[test] - fn trait_object_vec() { - // Create a vector of Options containing trait-objects. - let mut vector: Vec< - Options< - _, - Box<dyn word_separators::WordSeparator>, - Box<dyn word_splitters::WordSplitter>, - >, - > = Vec::new(); - // Expected result from each options - let mut results = Vec::new(); - - let opt_full_type: Options< - _, - Box<dyn word_separators::WordSeparator>, - Box<dyn word_splitters::WordSplitter>, - > = - Options::new(10) - .word_splitter(Box::new(word_splitters::HyphenSplitter) - as Box<dyn word_splitters::WordSplitter>) - .word_separator(Box::new(word_separators::AsciiSpace) - as Box<dyn word_separators::WordSeparator>); - vector.push(opt_full_type); - results.push(vec!["over-", "caffinated"]); - - // Actually: Options<Box<AsciiSpace>, Box<dyn word_splitters::WordSplitter>> - let opt_abbreviated_type = - Options::new(10) - .break_words(false) - .word_splitter(Box::new(word_splitters::NoHyphenation) - as Box<dyn word_splitters::WordSplitter>) - .word_separator(Box::new(word_separators::AsciiSpace) - as Box<dyn word_separators::WordSeparator>); - vector.push(opt_abbreviated_type); - results.push(vec!["over-caffinated"]); - - #[cfg(feature = "hyphenation")] - { - let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let opt_hyp = Options::new(8) - .word_splitter(Box::new(dictionary) as Box<dyn word_splitters::WordSplitter>) - .word_separator(Box::new(word_separators::AsciiSpace) - as Box<dyn word_separators::WordSeparator>); - vector.push(opt_hyp); - results.push(vec!["over-", "caffi-", "nated"]); - } - - // Test each entry - for (opt, expected) in vector.into_iter().zip(results) { - assert_eq!(wrap("over-caffinated", opt), expected); - } - } - - #[test] fn wrap_columns_empty_text() { assert_eq!(wrap_columns("", 1, 10, "| ", "", " |"), vec!["| |"]); } |