aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs600
1 files changed, 156 insertions, 444 deletions
diff --git a/src/lib.rs b/src/lib.rs
index f2f5542..6d68309 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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!["| |"]);
}