diff --git a/README.md b/README.md index 7baa7d22..a767894c 100644 --- a/README.md +++ b/README.md @@ -73,17 +73,17 @@ hyphenation for [about 70 languages][patterns] via high-quality TeX hyphenation patterns. Your program must load the hyphenation pattern and configure -`Wrapper::splitter` to use it: +`Options::splitter` to use it: ```rust use hyphenation::{Language, Load, Standard}; -use textwrap::Wrapper; +use textwrap::Options; fn main() { let hyphenator = Standard::from_embedded(Language::EnglishUS).unwrap(); - let wrapper = Wrapper::new(18).splitter(Box::new(hyphenator)); + let options = Options::new(18).splitter(Box::new(hyphenator)); let text = "textwrap: a small library for wrapping text."; - println!("{}", wrapper.fill(text)) + println!("{}", fill_with(text, &options); } ``` diff --git a/benches/linear.rs b/benches/linear.rs index 566816f1..c16445e7 100644 --- a/benches/linear.rs +++ b/benches/linear.rs @@ -23,12 +23,13 @@ pub fn benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("String lengths"); for length in [100, 200, 400, 800, 1600].iter() { let text = lorem_ipsum(*length); - let mut wrapper = textwrap::Wrapper::new(LINE_LENGTH); + let mut options = textwrap::Options::new(LINE_LENGTH); group.bench_with_input(BenchmarkId::new("fill", length), &text, |b, text| { - b.iter(|| wrapper.fill(text)); + b.iter(|| textwrap::fill(text, &options)); }); - group.bench_with_input(BenchmarkId::new("wrap", length), &text, |b, text| { - b.iter(|| wrapper.wrap(text)); + + group.bench_with_input(BenchmarkId::new("fill_usize", length), &text, |b, text| { + b.iter(|| textwrap::fill(text, LINE_LENGTH)); }); #[cfg(feature = "hyphenation")] @@ -38,9 +39,9 @@ pub fn benchmark(c: &mut Criterion) { .join("benches") .join("la.standard.bincode"); let dictionary = Standard::from_path(Language::Latin, &path).unwrap(); - wrapper.splitter = Box::new(dictionary); + options.splitter = Box::new(dictionary); group.bench_with_input(BenchmarkId::new("hyphenation", length), &text, |b, text| { - b.iter(|| wrapper.fill(text)); + b.iter(|| textwrap::fill(text, &options)); }); } } diff --git a/examples/hyphenation.rs b/examples/hyphenation.rs index cf925b72..ce152fea 100644 --- a/examples/hyphenation.rs +++ b/examples/hyphenation.rs @@ -12,6 +12,6 @@ fn main() { fn main() { let text = "textwrap: a small library for wrapping text."; let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let wrapper = textwrap::Wrapper::new(18).splitter(Box::new(dictionary)); - println!("{}", wrapper.fill(text)); + let options = textwrap::Options::new(18).splitter(Box::new(dictionary)); + println!("{}", textwrap::fill(text, &options)); } diff --git a/examples/layout.rs b/examples/layout.rs index 568ad8df..04654d99 100644 --- a/examples/layout.rs +++ b/examples/layout.rs @@ -1,4 +1,4 @@ -use textwrap::Wrapper; +use textwrap::{wrap, Options}; fn main() { let example = "Memory safety without garbage collection. \ @@ -6,18 +6,18 @@ fn main() { Zero-cost abstractions."; let mut prev_lines = vec![]; - let mut wrapper = Wrapper::new(0); + let mut options = Options::new(0); #[cfg(feature = "hyphenation")] { use hyphenation::Load; let language = hyphenation::Language::EnglishUS; let dictionary = hyphenation::Standard::from_embedded(language).unwrap(); - wrapper.splitter = Box::new(dictionary); + options.splitter = Box::new(dictionary); } for width in 15..60 { - wrapper.width = width; - let lines = wrapper.wrap(example); + options.width = width; + let lines = wrap(example, &options).collect::>(); if lines != prev_lines { let title = format!(" Width: {} ", width); println!(".{:-^1$}.", title, width + 2); diff --git a/examples/termwidth.rs b/examples/termwidth.rs index 14a10e61..3044ad1b 100644 --- a/examples/termwidth.rs +++ b/examples/termwidth.rs @@ -1,5 +1,5 @@ #[cfg(feature = "terminal_size")] -use textwrap::Wrapper; +use textwrap::{fill, Options}; #[cfg(not(feature = "terminal_size"))] fn main() { @@ -13,21 +13,21 @@ fn main() { Zero-cost abstractions."; #[cfg(not(feature = "hyphenation"))] - let (msg, wrapper) = ("without hyphenation", Wrapper::with_termwidth()); + let (msg, options) = ("without hyphenation", Options::with_termwidth()); #[cfg(feature = "hyphenation")] use hyphenation::Load; #[cfg(feature = "hyphenation")] - let (msg, wrapper) = ( + let (msg, options) = ( "with hyphenation", - Wrapper::with_termwidth().splitter(Box::new( + Options::with_termwidth().splitter(Box::new( hyphenation::Standard::from_embedded(hyphenation::Language::EnglishUS).unwrap(), )), ); - println!("Formatted {} in {} columns:", msg, wrapper.width); + println!("Formatted {} in {} columns:", msg, options.width); println!("----"); - println!("{}", wrapper.fill(example)); + println!("{}", fill(example, &options)); println!("----"); } diff --git a/src/lib.rs b/src/lib.rs index 837c6851..2703c81f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,14 +25,14 @@ //! ```no_run //! # #[cfg(feature = "hyphenation")] //! use hyphenation::{Language, Load, Standard}; -//! use textwrap::Wrapper; +//! use textwrap::{Options, fill}; //! //! # #[cfg(feature = "hyphenation")] //! fn main() { //! let text = "textwrap: a small library for wrapping text."; //! let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); -//! let wrapper = Wrapper::new(18).splitter(Box::new(dictionary)); -//! println!("{}", wrapper.fill(text)); +//! let options = Options::new(18).splitter(Box::new(dictionary)); +//! println!("{}", fill(text, &options)); //! } //! //! # #[cfg(not(feature = "hyphenation"))] @@ -75,7 +75,7 @@ //! //! * `terminal_size`: enables automatic detection of the terminal //! width via the [terminal_size][] crate. See the -//! [`Wrapper::with_termwidth`] constructor for details. +//! [`Options::with_termwidth`] constructor for details. //! //! * `hyphenation`: enables language-sentive hyphenation via the //! [hyphenation][] crate. See the [`WordSplitter`] trait for @@ -85,7 +85,7 @@ //! [unicode-width]: https://docs.rs/unicode-width/ //! [terminal_size]: https://crates.io/crates/terminal_size //! [hyphenation]: https://crates.io/crates/hyphenation -//! [`Wrapper::with_termwidth`]: struct.Wrapper.html#method.with_termwidth +//! [`Options::with_termwidth`]: struct.Options.html#method.with_termwidth //! [`WordSplitter`]: trait.WordSplitter.html #![doc(html_root_url = "https://docs.rs/textwrap/0.12.1")] @@ -116,21 +116,28 @@ pub use crate::indentation::indent; mod splitting; pub use crate::splitting::{HyphenSplitter, NoHyphenation, WordSplitter}; -/// A Wrapper holds settings for wrapping and filling text. Use it -/// when the convenience [`wrap_iter`], [`wrap`] and [`fill`] functions -/// are not flexible enough. +/// Options for wrapping and filling text. Used with the [`wrap`] and +/// [`fill`] functions. /// -/// [`wrap_iter`]: fn.wrap_iter.html /// [`wrap`]: fn.wrap.html /// [`fill`]: fn.fill.html -/// -/// The algorithm used by the iterator returned from the `wrap_iter` -/// method works by doing successive partial scans over words in the -/// input string (where each single scan yields a single line) so that -/// the overall time and memory complexity is O(*n*) where *n* is the -/// length of the input string. +pub trait WrapOptions { + /// The width in columns at which the text will be wrapped. + fn width(&self) -> usize; + /// Indentation used for the first line of output. + fn initial_indent(&self) -> &str; + /// Indentation used for subsequent lines of output. + fn subsequent_indent(&self) -> &str; + /// Allow long words to be broken if they cannot fit on a line. + /// When set to `false`, some lines may be longer than `width`. + fn break_words(&self) -> bool; + /// Split word as with `WordSplitter::split`. + fn split<'w>(&self, word: &'w str) -> Vec<(&'w str, &'w str, &'w str)>; +} + +/// Holds settings for wrapping and filling text. #[derive(Debug)] -pub struct Wrapper<'a> { +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. @@ -147,21 +154,106 @@ pub struct Wrapper<'a> { pub splitter: Box, } -/// # Builder Functionality. +/// Allows using an `Options` with [`wrap`] and [`fill`]: +/// +/// ``` +/// use textwrap::{fill, Options}; +/// +/// let options = Options::new(15).initial_indent("> "); +/// assert_eq!(fill("Wrapping with options!", &options), +/// "> Wrapping with\noptions!"); +/// ``` +/// +/// The integer specifes the wrapping width. This is equivalent to +/// passing `Options::new(15)`. +/// +/// [`wrap`]: fn.wrap.html +/// [`fill`]: fn.fill.html +impl WrapOptions for &Options<'_> { + #[inline] + fn width(&self) -> usize { + self.width + } + #[inline] + fn initial_indent(&self) -> &str { + self.initial_indent + } + #[inline] + fn subsequent_indent(&self) -> &str { + self.subsequent_indent + } + #[inline] + fn break_words(&self) -> bool { + self.break_words + } + #[inline] + fn split<'w>(&self, word: &'w str) -> Vec<(&'w str, &'w str, &'w str)> { + self.splitter.split(word) + } +} + +/// Allows using an `usize` directly as options for [`wrap`] and +/// [`fill`]: /// -/// The methods here make it easy to construct a `Wrapper` using -/// chained method calls. Start with `Wrapper::new` and override -/// settings as you like. -impl<'a> Wrapper<'a> { - /// Create a new Wrapper for wrapping at the specified width. By - /// default, we allow words longer than `width` to be broken. A - /// [`HyphenSplitter`] will be used by default for splitting - /// words. See the [`WordSplitter`] trait for other options. +/// ``` +/// use textwrap::fill; +/// +/// assert_eq!(fill("Quick and easy wrapping!", 15), +/// "Quick and easy\nwrapping!"); +/// ``` +/// +/// The integer specifes the wrapping width. This is equivalent to +/// passing `Options::new(15)`. +/// +/// [`wrap`]: fn.wrap.html +/// [`fill`]: fn.fill.html +impl WrapOptions for usize { + #[inline] + fn width(&self) -> usize { + *self + } + #[inline] + fn initial_indent(&self) -> &str { + "" + } + #[inline] + fn subsequent_indent(&self) -> &str { + "" + } + #[inline] + fn break_words(&self) -> bool { + true + } + #[inline] + fn split<'w>(&self, word: &'w str) -> Vec<(&'w str, &'w str, &'w str)> { + HyphenSplitter.split(word) + } +} + +impl<'a> Options<'a> { + /// Creates a new `Options` with the specified width. Equivalent + /// to /// - /// [`HyphenSplitter`]: struct.HyphenSplitter.html - /// [`WordSplitter`]: trait.WordSplitter.html - pub fn new(width: usize) -> Wrapper<'a> { - Wrapper { + /// ``` + /// # use textwrap::{Options, HyphenSplitter}; + /// # let width = 80; + /// # let actual = Options::new(width); + /// # let expected = + /// Options { + /// width: width, + /// initial_indent: "", + /// subsequent_indent: "", + /// break_words: true, + /// splitter: Box::new(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); + /// ``` + pub fn new(width: usize) -> Options<'static> { + Options { width: width, initial_indent: "", subsequent_indent: "", @@ -170,26 +262,26 @@ impl<'a> Wrapper<'a> { } } - /// Create a new Wrapper for wrapping text at 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 `Wrapper::new`. + /// 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 /// # #![allow(unused_variables)] - /// use textwrap::{Wrapper, termwidth}; + /// use textwrap::{Options, termwidth}; /// - /// let wrapper = Wrapper::new(termwidth()); + /// let options = Options::new(termwidth()); /// ``` /// /// **Note:** Only available when the `terminal_size` feature is /// enabled. #[cfg(feature = "terminal_size")] - pub fn with_termwidth() -> Wrapper<'a> { - Wrapper::new(termwidth()) + pub fn with_termwidth() -> Options<'static> { + Options::new(termwidth()) } /// Change [`self.initial_indent`]. The initial indentation is @@ -202,14 +294,14 @@ impl<'a> Wrapper<'a> { /// /// ```no_run /// # #![allow(unused_variables)] - /// use textwrap::Wrapper; + /// use textwrap::Options; /// - /// let wrapper = Wrapper::new(15).initial_indent(" "); + /// let options = Options::new(15).initial_indent(" "); /// ``` /// /// [`self.initial_indent`]: #structfield.initial_indent - pub fn initial_indent(self, indent: &'a str) -> Wrapper<'a> { - Wrapper { + pub fn initial_indent(self, indent: &'a str) -> Options<'a> { + Options { initial_indent: indent, ..self } @@ -225,16 +317,16 @@ impl<'a> Wrapper<'a> { /// /// ```no_run /// # #![allow(unused_variables)] - /// use textwrap::Wrapper; + /// use textwrap::Options; /// - /// let wrapper = Wrapper::new(15) + /// let options = Options::new(15) /// .initial_indent("* ") /// .subsequent_indent(" "); /// ``` /// /// [`self.subsequent_indent`]: #structfield.subsequent_indent - pub fn subsequent_indent(self, indent: &'a str) -> Wrapper<'a> { - Wrapper { + pub fn subsequent_indent(self, indent: &'a str) -> Options<'a> { + Options { subsequent_indent: indent, ..self } @@ -245,8 +337,8 @@ impl<'a> Wrapper<'a> { /// sticking out into the right margin. /// /// [`self.break_words`]: #structfield.break_words - pub fn break_words(self, setting: bool) -> Wrapper<'a> { - Wrapper { + pub fn break_words(self, setting: bool) -> Options<'a> { + Options { break_words: setting, ..self } @@ -257,224 +349,28 @@ impl<'a> Wrapper<'a> { /// /// [`self.splitter`]: #structfield.splitter /// [`WordSplitter`]: trait.WordSplitter.html - pub fn splitter(self, splitter: Box) -> Wrapper<'a> { - Wrapper { + pub fn splitter(self, splitter: Box) -> Options<'a> { + Options { splitter: splitter, ..self } } } -/// # Wrapping Functionality. -/// -/// The methods here are the real meat: they take strings as input and -/// give you word-wrapped strings as output. -impl<'a> Wrapper<'a> { - /// Fill a line of text at `self.width` characters. - /// - /// The result is a string with newlines between each line. Use - /// the `wrap` method if you need access to the individual lines. - /// - /// # Complexities - /// - /// This method simply joins the lines produced by `wrap_iter`. As - /// such, it inherits the O(*n*) time and memory complexity where - /// *n* is the input string length. - /// - /// # Examples - /// - /// ``` - /// use textwrap::Wrapper; - /// - /// let wrapper = Wrapper::new(15); - /// assert_eq!(wrapper.fill("Memory safety without garbage collection."), - /// "Memory safety\nwithout garbage\ncollection."); - /// ``` - pub fn fill(&self, s: &str) -> String { - // This will avoid reallocation in simple cases (no - // indentation, no hyphenation). - let mut result = String::with_capacity(s.len()); - - for (i, line) in self.wrap_iter(s).enumerate() { - if i > 0 { - result.push('\n'); - } - result.push_str(&line); - } - - result - } - - /// Wrap a line of text at `self.width` characters. - /// - /// # Complexities - /// - /// This method simply collects the lines produced by `wrap_iter`. - /// As such, it inherits the O(*n*) overall time and memory - /// complexity where *n* is the input string length. - /// - /// # Examples - /// - /// ``` - /// use textwrap::Wrapper; - /// - /// let wrap15 = Wrapper::new(15); - /// assert_eq!(wrap15.wrap("Concurrency without data races."), - /// vec!["Concurrency", - /// "without data", - /// "races."]); - /// - /// let wrap20 = Wrapper::new(20); - /// assert_eq!(wrap20.wrap("Concurrency without data races."), - /// vec!["Concurrency without", - /// "data races."]); - /// ``` - /// - /// Notice that newlines in the input are preserved. This means - /// that they force a line break, regardless of how long the - /// current line is: - /// - /// ``` - /// use textwrap::Wrapper; - /// - /// let wrapper = Wrapper::new(40); - /// assert_eq!(wrapper.wrap("First line.\nSecond line."), - /// vec!["First line.", "Second line."]); - /// ``` - /// - pub fn wrap(&self, s: &'a str) -> Vec> { - self.wrap_iter(s).collect::>() - } - - /// Lazily wrap a line of text at `self.width` characters. - /// - /// The [`WordSplitter`] stored in [`self.splitter`] is used - /// whenever when a word is too large to fit on the current line. - /// By changing the field, different hyphenation strategies can be - /// implemented. - /// - /// # Complexities - /// - /// This method returns an iterator which borrows this `Wrapper`. - /// The algorithm used has a linear complexity, so getting the - /// next line from the iterator will take O(*w*) time, where *w* - /// is the wrapping width. Fully processing the iterator will take - /// O(*n*) time for an input string of length *n*. - /// - /// When no indentation is used, each line returned is a slice of - /// the input string and the memory overhead is thus constant. - /// Otherwise new memory is allocated for each line returned. - /// - /// # Examples - /// - /// ``` - /// use std::borrow::Cow::Borrowed; - /// use textwrap::Wrapper; - /// - /// let wrap20 = Wrapper::new(20); - /// let mut wrap20_iter = wrap20.wrap_iter("Zero-cost abstractions."); - /// assert_eq!(wrap20_iter.next(), Some(Borrowed("Zero-cost"))); - /// assert_eq!(wrap20_iter.next(), Some(Borrowed("abstractions."))); - /// assert_eq!(wrap20_iter.next(), None); - /// - /// let wrap25 = Wrapper::new(25); - /// let mut wrap25_iter = wrap25.wrap_iter("Zero-cost abstractions."); - /// assert_eq!(wrap25_iter.next(), Some(Borrowed("Zero-cost abstractions."))); - /// assert_eq!(wrap25_iter.next(), None); - /// ``` - /// - /// [`self.splitter`]: #structfield.splitter - /// [`WordSplitter`]: trait.WordSplitter.html - pub fn wrap_iter<'w>(&'w self, s: &'a str) -> impl Iterator> + 'w { - WrapIter { - wrapper: self, - inner: WrapIterImpl::new(self, s), - } - } - - /// Lazily wrap a line of text at `self.width` characters. - /// - /// The [`WordSplitter`] stored in [`self.splitter`] is used - /// whenever when a word is too large to fit on the current line. - /// By changing the field, different hyphenation strategies can be - /// implemented. - /// - /// # Complexities - /// - /// This method consumes the `Wrapper` and returns an iterator. - /// Fully processing the iterator has the same O(*n*) time - /// complexity as [`wrap_iter`], where *n* is the length of the - /// input string. - /// - /// # Examples - /// - /// ``` - /// use std::borrow::Cow::Borrowed; - /// use textwrap::Wrapper; - /// - /// let wrap20 = Wrapper::new(20); - /// let mut wrap20_iter = wrap20.into_wrap_iter("Zero-cost abstractions."); - /// assert_eq!(wrap20_iter.next(), Some(Borrowed("Zero-cost"))); - /// assert_eq!(wrap20_iter.next(), Some(Borrowed("abstractions."))); - /// assert_eq!(wrap20_iter.next(), None); - /// ``` - /// - /// [`self.splitter`]: #structfield.splitter - /// [`WordSplitter`]: trait.WordSplitter.html - /// [`wrap_iter`]: #method.wrap_iter - pub fn into_wrap_iter(self, s: &'a str) -> impl Iterator> { - let inner = WrapIterImpl::new(&self, s); - - IntoWrapIter { - wrapper: self, - inner: inner, - } - } -} - -/// An iterator owns a `Wrapper`. -#[derive(Debug)] -struct IntoWrapIter<'a> { - wrapper: Wrapper<'a>, - inner: WrapIterImpl<'a>, -} - -impl<'a> Iterator for IntoWrapIter<'a> { - type Item = Cow<'a, str>; - - fn next(&mut self) -> Option> { - self.inner.next(&self.wrapper) - } -} - -/// An iterator which borrows a `Wrapper`. -#[derive(Debug)] -struct WrapIter<'w, 'a: 'w> { - wrapper: &'w Wrapper<'a>, - inner: WrapIterImpl<'a>, -} - -impl<'w, 'a: 'w> Iterator for WrapIter<'w, 'a> { - type Item = Cow<'a, str>; - - fn next(&mut self) -> Option> { - self.inner.next(self.wrapper) - } -} - /// Like `char::is_whitespace`, but non-breaking spaces don't count. #[inline] fn is_whitespace(ch: char) -> bool { ch.is_whitespace() && ch != NBSP } -/// Common implementation details for `WrapIter` and `IntoWrapIter`. #[derive(Debug)] -struct WrapIterImpl<'a> { +struct WrapIter<'input, T: WrapOptions> { + options: T, + // String to wrap. - source: &'a str, + source: &'input str, // CharIndices iterator over self.source. - char_indices: CharIndices<'a>, + char_indices: CharIndices<'input>, // Byte index where the current line starts. start: usize, // Byte index of the last place where the string can be split. @@ -491,30 +387,46 @@ struct WrapIterImpl<'a> { finished: bool, } -impl<'a> WrapIterImpl<'a> { - fn new(wrapper: &Wrapper<'a>, s: &'a str) -> WrapIterImpl<'a> { - WrapIterImpl { +impl WrapIter<'_, T> { + fn new(options: T, s: &str) -> WrapIter<'_, T> { + let initial_indent_width = options.initial_indent().width(); + + WrapIter { + options: options, source: s, char_indices: s.char_indices(), start: 0, split: 0, split_len: 0, - line_width: wrapper.initial_indent.width(), - line_width_at_split: wrapper.initial_indent.width(), + line_width: initial_indent_width, + line_width_at_split: initial_indent_width, in_whitespace: false, finished: false, } } - fn create_result_line(&self, wrapper: &Wrapper<'a>) -> Cow<'a, str> { - if self.start == 0 { - Cow::from(wrapper.initial_indent) + fn create_result_line(&self) -> Cow<'static, str> { + let indent = if self.start == 0 { + self.options.initial_indent() + } else { + self.options.subsequent_indent() + }; + if indent.is_empty() { + Cow::Borrowed("") // return Cow<'static, str> } else { - Cow::from(wrapper.subsequent_indent) + // This removes the link between the lifetime of the + // indentation and the input string. The non-empty + // indentation will force us to create an owned `String` + // in any case. + Cow::Owned(String::from(indent)) } } +} - fn next(&mut self, wrapper: &Wrapper<'a>) -> Option> { +impl<'input, T: WrapOptions> Iterator for WrapIter<'input, T> { + type Item = Cow<'input, str>; + + fn next(&mut self) -> Option> { if self.finished { return None; } @@ -546,11 +458,11 @@ impl<'a> WrapIterImpl<'a> { // If this is not the final line, return the current line. Otherwise, // we will return the line with its line break after exiting the loop if self.split + self.split_len < self.source.len() { - let mut line = self.create_result_line(wrapper); + let mut line = self.create_result_line(); line += &self.source[self.start..self.split]; self.start = self.split + self.split_len; - self.line_width = wrapper.subsequent_indent.width(); + self.line_width = self.options.subsequent_indent().width(); return Some(line); } @@ -564,7 +476,7 @@ impl<'a> WrapIterImpl<'a> { } self.line_width_at_split = self.line_width + char_width; self.in_whitespace = true; - } else if self.line_width + char_width > wrapper.width { + } else if self.line_width + char_width > self.options.width() { // There is no room for this character on the current // line. Try to split the final word. self.in_whitespace = false; @@ -575,9 +487,10 @@ impl<'a> WrapIterImpl<'a> { }; let mut hyphen = ""; - let splits = wrapper.splitter.split(final_word); + let splits = self.options.split(final_word); for &(head, hyp, _) in splits.iter().rev() { - if self.line_width_at_split + head.width() + hyp.width() <= wrapper.width { + if self.line_width_at_split + head.width() + hyp.width() <= self.options.width() + { // We can fit head into the current line. // Advance the split point by the width of the // whitespace and the head length. @@ -597,7 +510,7 @@ impl<'a> WrapIterImpl<'a> { if self.start >= self.split { // The word is too big to fit on a single line. - if wrapper.break_words { + if self.options.break_words() { // Break work at current index. self.split = idx; self.split_len = 0; @@ -617,15 +530,15 @@ impl<'a> WrapIterImpl<'a> { } if self.start < self.split { - let mut line = self.create_result_line(wrapper); + let mut line = self.create_result_line(); line += &self.source[self.start..self.split]; line += hyphen; self.start = self.split + self.split_len; - self.line_width += wrapper.subsequent_indent.width(); + self.line_width += self.options.subsequent_indent().width(); self.line_width -= self.line_width_at_split; self.line_width += char_width; - self.line_width_at_split = wrapper.subsequent_indent.width(); + self.line_width_at_split = self.options.subsequent_indent().width(); return Some(line); } @@ -639,7 +552,7 @@ impl<'a> WrapIterImpl<'a> { // Add final line. if self.start < self.source.len() { - let mut line = self.create_result_line(wrapper); + let mut line = self.create_result_line(); line += &self.source[self.start..]; return Some(line); } @@ -654,15 +567,15 @@ impl<'a> WrapIterImpl<'a> { /// /// # Examples /// -/// Create a `Wrapper` for the current terminal with a two column -/// margin: +/// Create an `Options` for wrapping at the current terminal width +/// with a two column margin to the left and the right: /// /// ```no_run /// # #![allow(unused_variables)] -/// use textwrap::{Wrapper, NoHyphenation, termwidth}; +/// use textwrap::{Options, NoHyphenation, termwidth}; /// /// let width = termwidth() - 4; // Two columns on each side. -/// let wrapper = Wrapper::new(width) +/// let options = Options::new(width) /// .splitter(Box::new(NoHyphenation)) /// .initial_indent(" ") /// .subsequent_indent(" "); @@ -677,9 +590,12 @@ pub fn termwidth() -> usize { /// Fill a line of text at `width` characters. /// -/// The result is a string with newlines between each line. Use -/// [`wrap`] if you need access to the individual lines or -/// [`wrap_iter`] for its iterator counterpart. +/// 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 +/// `options`: /// /// ``` /// use textwrap::fill; @@ -688,76 +604,83 @@ pub fn termwidth() -> usize { /// "Memory safety\nwithout garbage\ncollection."); /// ``` /// -/// This function creates a Wrapper on the fly with default settings. -/// If you need to set a language corpus for automatic hyphenation, or -/// need to fill many strings, then it is suggested to create a Wrapper -/// and call its [`fill` method]. -/// -/// [`wrap`]: fn.wrap.html -/// [`wrap_iter`]: fn.wrap_iter.html -/// [`fill` method]: struct.Wrapper.html#method.fill -pub fn fill(s: &str, width: usize) -> String { - Wrapper::new(width).fill(s) -} - -/// Wrap a line of text at `width` characters. -/// -/// This function creates a Wrapper on the fly with default settings. -/// If you need to set a language corpus for automatic hyphenation, or -/// need to wrap many strings, then it is suggested to create a Wrapper -/// and call its [`wrap` method]. -/// -/// The result is a vector of strings. Use [`wrap_iter`] if you need an -/// iterator version. -/// -/// # Examples +/// If you need to customize the wrapping, you can pass an [`Options`] +/// instead of an `usize`: /// /// ``` -/// use textwrap::wrap; -/// -/// assert_eq!(wrap("Concurrency without data races.", 15), -/// vec!["Concurrency", -/// "without data", -/// "races."]); +/// use textwrap::{fill, Options}; /// -/// assert_eq!(wrap("Concurrency without data races.", 20), -/// vec!["Concurrency without", -/// "data races."]); +/// 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."); /// ``` /// -/// [`wrap_iter`]: fn.wrap_iter.html -/// [`wrap` method]: struct.Wrapper.html#method.wrap -pub fn wrap(s: &str, width: usize) -> Vec> { - Wrapper::new(width).wrap(s) +/// [`wrap`]: fn.wrap.html +pub fn fill(text: &str, options: T) -> String { + // 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, options).enumerate() { + if i > 0 { + result.push('\n'); + } + result.push_str(&line); + } + + result } -/// Lazily wrap a line of text at `width` characters. -/// -/// This function creates a Wrapper on the fly with default settings. -/// If you need to set a language corpus for automatic hyphenation, or -/// need to wrap many strings, then it is suggested to create a -/// Wrapper and call its [`wrap_iter`] or [`into_wrap_iter`] methods. +/// The easiest way to use this function is to pass an integer for +/// `options`: +/// let lines = wrap("Memory safety without garbage collection.", 15); +/// assert_eq!(lines.collect::>(), &[ +/// "Memory safety", +/// "without garbage", +/// "collection.", +/// ]); +/// If you need to customize the wrapping, you can pass an [`Options`] +/// instead of an `usize`: /// -/// # Examples +/// ``` +/// 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.collect::>(), &[ +/// "- Memory safety", +/// " without", +/// " garbage", +/// " collection.", +/// ]); /// ``` -/// use std::borrow::Cow::Borrowed; -/// use textwrap::wrap_iter; /// -/// let mut wrap20_iter = wrap_iter("Zero-cost abstractions.", 20); -/// assert_eq!(wrap20_iter.next(), Some(Borrowed("Zero-cost"))); -/// assert_eq!(wrap20_iter.next(), Some(Borrowed("abstractions."))); -/// assert_eq!(wrap20_iter.next(), None); +/// # Examples +/// +/// The returned iterator yields lines of type `Cow<'_, str>`. If +/// possible, the wrapped lines will borrow 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: /// -/// let mut wrap25_iter = wrap_iter("Zero-cost abstractions.", 25); -/// assert_eq!(wrap25_iter.next(), Some(Borrowed("Zero-cost abstractions."))); -/// assert_eq!(wrap25_iter.next(), None); /// ``` +/// use std::borrow::Cow::{Borrowed, Owned}; +/// use textwrap::{wrap, Options}; /// -/// [`wrap_iter`]: struct.Wrapper.html#method.wrap_iter -/// [`into_wrap_iter`]: struct.Wrapper.html#method.into_wrap_iter -pub fn wrap_iter(s: &str, width: usize) -> impl Iterator> { - Wrapper::new(width).into_wrap_iter(s) +/// let options = Options::new(15).subsequent_indent("...."); +/// let lines = wrap("Wrapping text all day long.", &options); +/// let annotated = lines.map(|line| match line { +/// Borrowed(text) => format!("[Borrowed] {}", text), +/// Owned(text) => format!("[Owned] {}", text), +/// }).collect::>(); +/// assert_eq!(annotated, &[ +/// "[Borrowed] Wrapping text", +/// "[Owned] ....all day", +/// "[Owned] ....long.", +/// ]); +/// ``` +pub fn wrap(text: &str, options: T) -> impl Iterator> { + WrapIter::new(options, text) } #[cfg(test)] @@ -766,49 +689,73 @@ mod tests { #[cfg(feature = "hyphenation")] use hyphenation::{Language, Load, Standard}; + macro_rules! assert_iter_eq { + ($left:expr, $right:expr) => { + assert_eq!($left.collect::>(), $right); + }; + } + + #[test] + fn options_agree_with_usize() { + let opt_usize: &dyn WrapOptions = &42; + let opt_options: &dyn WrapOptions = &&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.split("hello-world"), + opt_options.split("hello-world") + ); + } + #[test] fn no_wrap() { - assert_eq!(wrap("foo", 10), vec!["foo"]); + assert_iter_eq!(wrap("foo", 10), vec!["foo"]); } #[test] fn simple() { - assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]); + assert_iter_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]); } #[test] fn multi_word_on_line() { - assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]); + assert_iter_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]); } #[test] fn long_word() { - assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]); + assert_iter_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"]); + assert_iter_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]); } #[test] fn max_width() { - assert_eq!(wrap("foo bar", usize::max_value()), vec!["foo bar"]); + assert_iter_eq!(wrap("foo bar", usize::max_value()), vec!["foo bar"]); } #[test] fn leading_whitespace() { - assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]); + assert_iter_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]); } #[test] fn trailing_whitespace() { - assert_eq!(wrap("foo bar ", 6), vec!["foo", "bar "]); + assert_iter_eq!(wrap("foo bar ", 6), vec!["foo", "bar "]); } #[test] fn interior_whitespace() { - assert_eq!(wrap("foo: bar baz", 10), vec!["foo: bar", "baz"]); + assert_iter_eq!(wrap("foo: bar baz", 10), vec!["foo: bar", "baz"]); } #[test] @@ -817,14 +764,14 @@ mod tests { // gets too long and is broken, the first word starts in // column zero and is not indented. The line before might end // up with trailing whitespace. - assert_eq!(wrap("foo bar", 5), vec!["foo", "bar"]); + assert_iter_eq!(wrap("foo bar", 5), vec!["foo", "bar"]); } #[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!( + assert_iter_eq!( wrap("aaabbbccc x yyyzzzwww", 9), vec!["aaabbbccc", "x", "yyyzzzwww"] ); @@ -834,13 +781,13 @@ 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. - assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]); + assert_iter_eq!(wrap("x – x", 1), vec!["x", "–", "x"]); } #[test] fn wide_character_handling() { - assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]); - assert_eq!( + assert_iter_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]); + assert_iter_eq!( wrap("Hello, World!", 15), vec!["Hello,", "World!"] ); @@ -848,120 +795,123 @@ mod tests { #[test] fn empty_input_not_indented() { - let wrapper = Wrapper::new(10).initial_indent("!!!"); - assert_eq!(wrapper.fill(""), ""); + let options = Options::new(10).initial_indent("!!!"); + assert_eq!(fill("", &options), ""); } #[test] fn indent_single_line() { - let wrapper = Wrapper::new(10).initial_indent(">>>"); // No trailing space - assert_eq!(wrapper.fill("foo"), ">>>foo"); + let options = Options::new(10).initial_indent(">>>"); // No trailing space + assert_eq!(fill("foo", &options), ">>>foo"); } #[test] fn indent_multiple_lines() { - let wrapper = Wrapper::new(6).initial_indent("* ").subsequent_indent(" "); - assert_eq!(wrapper.wrap("foo bar baz"), vec!["* foo", " bar", " baz"]); + let options = Options::new(6).initial_indent("* ").subsequent_indent(" "); + assert_iter_eq!( + wrap("foo bar baz", &options), + vec!["* foo", " bar", " baz"] + ); } #[test] fn indent_break_words() { - let wrapper = Wrapper::new(5).initial_indent("* ").subsequent_indent(" "); - assert_eq!(wrapper.wrap("foobarbaz"), vec!["* foo", " bar", " baz"]); + let options = Options::new(5).initial_indent("* ").subsequent_indent(" "); + assert_iter_eq!(wrap("foobarbaz", &options), vec!["* foo", " bar", " baz"]); } #[test] fn hyphens() { - assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]); + assert_iter_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]); } #[test] fn trailing_hyphen() { - let wrapper = Wrapper::new(5).break_words(false); - assert_eq!(wrapper.wrap("foobar-"), vec!["foobar-"]); + let options = Options::new(5).break_words(false); + assert_iter_eq!(wrap("foobar-", &options), vec!["foobar-"]); } #[test] fn multiple_hyphens() { - assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]); + assert_iter_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]); } #[test] fn hyphens_flag() { - let wrapper = Wrapper::new(5).break_words(false); - assert_eq!( - wrapper.wrap("The --foo-bar flag."), + let options = Options::new(5).break_words(false); + assert_iter_eq!( + wrap("The --foo-bar flag.", &options), vec!["The", "--foo-", "bar", "flag."] ); } #[test] fn repeated_hyphens() { - let wrapper = Wrapper::new(4).break_words(false); - assert_eq!(wrapper.wrap("foo--bar"), vec!["foo--bar"]); + let options = Options::new(4).break_words(false); + assert_iter_eq!(wrap("foo--bar", &options), vec!["foo--bar"]); } #[test] fn hyphens_alphanumeric() { - assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]); + assert_iter_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]); } #[test] fn hyphens_non_alphanumeric() { - let wrapper = Wrapper::new(5).break_words(false); - assert_eq!(wrapper.wrap("foo(-)bar"), vec!["foo(-)bar"]); + let options = Options::new(5).break_words(false); + assert_iter_eq!(wrap("foo(-)bar", &options), vec!["foo(-)bar"]); } #[test] fn multiple_splits() { - assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]); + assert_iter_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]); } #[test] fn forced_split() { - let wrapper = Wrapper::new(5).break_words(false); - assert_eq!(wrapper.wrap("foobar-baz"), vec!["foobar-", "baz"]); + let options = Options::new(5).break_words(false); + assert_iter_eq!(wrap("foobar-baz", &options), vec!["foobar-", "baz"]); } #[test] fn multiple_unbroken_words_issue_193() { - let wrapper = Wrapper::new(3).break_words(false); - assert_eq!( - wrapper.wrap("small large tiny"), + let options = Options::new(3).break_words(false); + assert_iter_eq!( + wrap("small large tiny", &options), vec!["small", "large", "tiny"] ); - assert_eq!( - wrapper.wrap("small large tiny"), + assert_iter_eq!( + wrap("small large tiny", &options), vec!["small", "large", "tiny"] ); } #[test] fn very_narrow_lines_issue_193() { - let wrapper = Wrapper::new(1).break_words(false); - assert_eq!(wrapper.wrap("fooo x y"), vec!["fooo", "x", "y"]); - assert_eq!(wrapper.wrap("fooo x y"), vec!["fooo", "x", "y"]); + let options = Options::new(1).break_words(false); + assert_iter_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]); + assert_iter_eq!(wrap("fooo x y", &options), vec!["fooo", "x", "y"]); } #[test] fn no_hyphenation() { - let wrapper = Wrapper::new(8).splitter(Box::new(NoHyphenation)); - assert_eq!(wrapper.wrap("foo bar-baz"), vec!["foo", "bar-baz"]); + let options = Options::new(8).splitter(Box::new(NoHyphenation)); + assert_iter_eq!(wrap("foo bar-baz", &options), vec!["foo", "bar-baz"]); } #[test] #[cfg(feature = "hyphenation")] fn auto_hyphenation() { let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let wrapper = Wrapper::new(10); - assert_eq!( - wrapper.wrap("Internationalization"), + let options = Options::new(10); + assert_iter_eq!( + wrap("Internationalization", &options), vec!["Internatio", "nalization"] ); - let wrapper = Wrapper::new(10).splitter(Box::new(dictionary)); - assert_eq!( - wrapper.wrap("Internationalization"), + let options = Options::new(10).splitter(Box::new(dictionary)); + assert_iter_eq!( + wrap("Internationalization", &options), vec!["Interna-", "tionaliza-", "tion"] ); } @@ -970,15 +920,15 @@ mod tests { #[cfg(feature = "hyphenation")] fn auto_hyphenation_issue_158() { let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let wrapper = Wrapper::new(10); - assert_eq!( - wrapper.wrap("participation is the key to success"), + let options = Options::new(10); + assert_iter_eq!( + wrap("participation is the key to success", &options), vec!["participat", "ion is the", "key to", "success"] ); - let wrapper = Wrapper::new(10).splitter(Box::new(dictionary)); - assert_eq!( - wrapper.wrap("participation is the key to success"), + let options = Options::new(10).splitter(Box::new(dictionary)); + assert_iter_eq!( + wrap("participation is the key to success", &options), vec!["participa-", "tion is the", "key to", "success"] ); } @@ -989,9 +939,9 @@ mod tests { // Test that hyphenation takes the width of the wihtespace // into account. let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let wrapper = Wrapper::new(15).splitter(Box::new(dictionary)); - assert_eq!( - wrapper.wrap("garbage collection"), + let options = Options::new(15).splitter(Box::new(dictionary)); + assert_iter_eq!( + wrap("garbage collection", &options), vec!["garbage col-", "lection"] ); } @@ -1003,8 +953,8 @@ mod tests { // line is borrowed. use std::borrow::Cow::{Borrowed, Owned}; let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let wrapper = Wrapper::new(10).splitter(Box::new(dictionary)); - let lines = wrapper.wrap("Internationalization"); + let options = Options::new(10).splitter(Box::new(dictionary)); + let lines = wrap("Internationalization", &options).collect::>(); if let Borrowed(s) = lines[0] { assert!(false, "should not have been borrowed: {:?}", s); } @@ -1020,29 +970,32 @@ mod tests { #[cfg(feature = "hyphenation")] fn auto_hyphenation_with_hyphen() { let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap(); - let wrapper = Wrapper::new(8).break_words(false); - assert_eq!(wrapper.wrap("over-caffinated"), vec!["over-", "caffinated"]); + let options = Options::new(8).break_words(false); + assert_iter_eq!( + wrap("over-caffinated", &options), + vec!["over-", "caffinated"] + ); - let wrapper = wrapper.splitter(Box::new(dictionary)); - assert_eq!( - wrapper.wrap("over-caffinated"), + let options = options.splitter(Box::new(dictionary)); + assert_iter_eq!( + wrap("over-caffinated", &options), vec!["over-", "caffi-", "nated"] ); } #[test] fn break_words() { - assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]); + assert_iter_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]); } #[test] fn break_words_wide_characters() { - assert_eq!(wrap("Hello", 5), vec!["He", "ll", "o"]); + assert_iter_eq!(wrap("Hello", 5), vec!["He", "ll", "o"]); } #[test] fn break_words_zero_width() { - assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]); + assert_iter_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]); } #[test] @@ -1065,14 +1018,14 @@ mod tests { #[test] fn non_breaking_space() { - let wrapper = Wrapper::new(5).break_words(false); - assert_eq!(wrapper.fill("foo bar baz"), "foo bar baz"); + let options = Options::new(5).break_words(false); + assert_eq!(fill("foo bar baz", &options), "foo bar baz"); } #[test] fn non_breaking_hyphen() { - let wrapper = Wrapper::new(5).break_words(false); - assert_eq!(wrapper.fill("foo‑bar‑baz"), "foo‑bar‑baz"); + let options = Options::new(5).break_words(false); + assert_eq!(fill("foo‑bar‑baz", &options), "foo‑bar‑baz"); } #[test] diff --git a/src/splitting.rs b/src/splitting.rs index f7e21e03..c35ae584 100644 --- a/src/splitting.rs +++ b/src/splitting.rs @@ -7,10 +7,10 @@ /// An interface for splitting words. /// -/// When the [`wrap_iter`] method will try to fit text into a line, it -/// will eventually find a word that it too large the current text -/// width. It will then call the currently configured `WordSplitter` to -/// have it attempt to split the word into smaller parts. This trait +/// When the [`wrap`] function tries to fit text into a line, it will +/// eventually find a word that it too large the current text width. +/// It will then call the currently configured `WordSplitter` to have +/// it attempt to split the word into smaller parts. This trait /// describes that functionality via the [`split`] method. /// /// If the `textwrap` crate has been compiled with the `hyphenation` @@ -19,7 +19,7 @@ /// language-aware hyphenation. See the [`hyphenation` documentation] /// for details. /// -/// [`wrap_iter`]: ../struct.Wrapper.html#method.wrap_iter +/// [`wrap`]: ../fn.wrap.html /// [`split`]: #tymethod.split /// [`hyphenation` documentation]: https://docs.rs/hyphenation/ pub trait WordSplitter: std::fmt::Debug { @@ -41,17 +41,18 @@ pub trait WordSplitter: std::fmt::Debug { fn split<'w>(&self, word: &'w str) -> Vec<(&'w str, &'w str, &'w str)>; } -/// Use this as a [`Wrapper.splitter`] to avoid any kind of +/// Use this as a [`Options.splitter`] to avoid any kind of /// hyphenation: /// /// ``` -/// use textwrap::{Wrapper, NoHyphenation}; +/// use textwrap::{wrap, Options, NoHyphenation}; /// -/// let wrapper = Wrapper::new(8).splitter(Box::new(NoHyphenation)); -/// assert_eq!(wrapper.wrap("foo bar-baz"), vec!["foo", "bar-baz"]); +/// let options = Options::new(8).splitter(Box::new(NoHyphenation)); +/// assert_eq!(wrap("foo bar-baz", &options).collect::>(), +/// vec!["foo", "bar-baz"]); /// ``` /// -/// [`Wrapper.splitter`]: ../struct.Wrapper.html#structfield.splitter +/// [`Options.splitter`]: ../struct.Options.html#structfield.splitter #[derive(Clone, Debug)] pub struct NoHyphenation; @@ -67,12 +68,12 @@ impl WordSplitter for NoHyphenation { /// hyphens only. /// /// You probably don't need to use this type since it's already used -/// by default by `Wrapper::new`. +/// by default by `Options::new`. #[derive(Clone, Debug)] pub struct HyphenSplitter; /// `HyphenSplitter` is the default `WordSplitter` used by -/// `Wrapper::new`. It will split words on any existing hyphens in the +/// `Options::new`. It will split words on any existing hyphens in the /// word. /// /// It will only use hyphens that are surrounded by alphanumeric