Skip to content

Commit

Permalink
Reintroducing the type parameter on Options (previously Wrapper).
Browse files Browse the repository at this point in the history
This commit reverts the internal changes of #206, but keeps the public API
compatible to what it introduced. Essetially by adding a default for the
type parameter.

However, now in addition to the dynamic dispatch by default,
one may also explicitly use static dispatch by specifying the type parameter.
This now allows to construct an `Options` instance in const/static context.
Which is further facilitated by adding the const fn `with_splitter`.

Also, since `Options` now may be some specific type the `Clone` derive is added
again. So when `S` is `Clone` then `Options` is `Clone` too.

This change is actually backwards compatible with the previous commit,
as seen by the unchanged examples and tests.
  • Loading branch information
Cryptjar committed Oct 30, 2020
1 parent e49a2a3 commit f7dfa7d
Show file tree
Hide file tree
Showing 2 changed files with 177 additions and 22 deletions.
184 changes: 162 additions & 22 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ pub trait WrapOptions {
}

/// Holds settings for wrapping and filling text.
#[derive(Debug)]
pub struct Options<'a> {
#[derive(Debug, Clone)]
pub struct Options<'a, S = Box<dyn WordSplitter>> {
/// The width in columns at which the text will be wrapped.
pub width: usize,
/// Indentation used for the first line of output.
Expand All @@ -151,7 +151,7 @@ pub struct Options<'a> {
/// The method for splitting words. If the `hyphenation` feature
/// is enabled, you can use a `hyphenation::Standard` dictionary
/// here to get language-aware hyphenation.
pub splitter: Box<dyn WordSplitter>,
pub splitter: S,
}

/// Allows using an `Options` with [`wrap`] and [`fill`]:
Expand All @@ -169,7 +169,7 @@ pub struct Options<'a> {
///
/// [`wrap`]: fn.wrap.html
/// [`fill`]: fn.fill.html
impl WrapOptions for &Options<'_> {
impl<S: WordSplitter> WrapOptions for &Options<'_, S> {
#[inline]
fn width(&self) -> usize {
self.width
Expand Down Expand Up @@ -230,15 +230,16 @@ impl WrapOptions for usize {
}
}

/// Constructors for boxed Options, specifically.
impl<'a> Options<'a> {
/// Creates a new `Options` with the specified width. Equivalent
/// to
/// Creates a new `Options` with the specified width and dynamic dispatch
/// using the `HyphenSplitter`. Equivalent to
///
/// ```
/// # use textwrap::{Options, HyphenSplitter};
/// # use textwrap::{Options, HyphenSplitter, WordSplitter};
/// # let width = 80;
/// # let actual = Options::new(width);
/// # let expected =
/// # let expected: Options =
/// Options {
/// width: width,
/// initial_indent: "",
Expand All @@ -251,15 +252,18 @@ impl<'a> Options<'a> {
/// # assert_eq!(actual.initial_indent, expected.initial_indent);
/// # assert_eq!(actual.subsequent_indent, expected.subsequent_indent);
/// # assert_eq!(actual.break_words, expected.break_words);
/// # let expected_coerced: Options<'static, Box<dyn WordSplitter>> = expected;
/// ```
pub fn new(width: usize) -> Options<'static> {
Options {
width: width,
initial_indent: "",
subsequent_indent: "",
break_words: true,
splitter: Box::new(HyphenSplitter),
}
///
/// Dynamic dispatch means here, that the splitter is stored in a
/// `Box<dyn WordSplitter>`, which allows to even change the splitter's
/// inner type without changing the type of this struct. If you instead
/// need more control of the type of the splitter, there is also the
/// `with_splitter` constructor, which allows you to specify a precise
/// splitter without the boxing.
///
pub fn new(width: usize) -> Self {
Options::with_splitter(width, Box::new(HyphenSplitter))
}

/// Creates a new `Options` with `width` set to the current
Expand All @@ -280,10 +284,138 @@ impl<'a> Options<'a> {
/// **Note:** Only available when the `terminal_size` feature is
/// enabled.
#[cfg(feature = "terminal_size")]
pub fn with_termwidth() -> Options<'static> {
Options::new(termwidth())
pub fn with_termwidth() -> Self {
Self::new(termwidth())
}
}

impl<'a, S> Options<'a, S> {
/// Creates a new `Options` with the specified width and splitter. Equivalent
/// to
///
/// ```
/// # use textwrap::{Options, NoHyphenation};
/// # const splitter: NoHyphenation = NoHyphenation;
/// # const width: usize = 80;
/// # const actual: Options<'static, NoHyphenation> = Options::with_splitter(width, splitter);
/// # let expected =
/// Options {
/// width: width,
/// initial_indent: "",
/// subsequent_indent: "",
/// break_words: true,
/// splitter: 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);
/// # let expected_coerced: Options<'static, NoHyphenation> = expected;
/// ```
///
/// This constructor allows to specify the precise splitter to be used, and
/// let it stored as is in the struct, without the need for a box, unlike
/// `new`. However, this function can still be used with a box, for
/// instance, to obtain a boxed `Options` with a different splitter:
///
/// ```
/// use textwrap::{Options, NoHyphenation};
/// # use textwrap::{WordSplitter, HyphenSplitter};
/// # const width: usize = 80;
/// # let expected: Options =
/// # Options {
/// # width: width,
/// # initial_indent: "",
/// # subsequent_indent: "",
/// # break_words: true,
/// # splitter: Box::new(NoHyphenation),
/// # }
/// # ;
///
/// // This opt has the same type as returned by `new`, but it contains a
/// // `NoHyphenation` splitter
/// let mut opt: Options = Options::with_splitter(width, Box::new(NoHyphenation));
/// #
/// # let actual = opt;
/// # 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);
/// # let expected_coerced: Options<Box<dyn WordSplitter>> = expected;
///
/// // Thus, it can be overridden.
/// opt = Options::new(width);
/// // Now, containing a `HyphenSplitter` instead.
/// #
/// # let expected: Options =
/// # Options {
/// # width: width,
/// # initial_indent: "",
/// # subsequent_indent: "",
/// # break_words: true,
/// # splitter: Box::new(HyphenSplitter),
/// # }
/// # ;
/// # let actual = opt;
/// # 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);
/// # let expected_coerced: Options<Box<dyn WordSplitter>> = expected;
/// ```
///
/// Since the 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 splitter
/// at run time as seen above, static dispatch especially can store the splitter
/// directly, without the need for a box. This in turn allows it to be used
/// in constant and static context:
///
/// ```
/// use textwrap::{Options, HyphenSplitter};
/// # use textwrap::{WordSplitter};
/// # const width: usize = 80;
/// # let expected =
/// # Options {
/// # width: width,
/// # initial_indent: "",
/// # subsequent_indent: "",
/// # break_words: true,
/// # splitter: HyphenSplitter,
/// # }
/// # ;
///
/// const FOO: Options<HyphenSplitter> = Options::with_splitter(width, HyphenSplitter);
/// static BAR: Options<HyphenSplitter> = FOO;
/// #
/// # let actual = &BAR;
/// # 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);
/// # let expected_coerced: &Options<HyphenSplitter> = actual;
/// ```
///
/// Notice, that initializing `const` and `static` can only be done using
/// `with_splitter` (or initializing the struct in place), because the
/// functional equivalent `Options::new(width).splitter(HyphenSplitter)`
/// allocates a `Box`, which can not be done for initializing `const` and
/// `static`.
///
pub const fn with_splitter(width: usize, splitter: S) -> Self {
Options {
width,
initial_indent: "",
subsequent_indent: "",
break_words: true,
splitter,
}
}
}

impl<'a, S: WordSplitter> Options<'a, S> {
/// Change [`self.initial_indent`]. The initial indentation is
/// used on the very first line of output.
///
Expand All @@ -300,7 +432,7 @@ impl<'a> Options<'a> {
/// ```
///
/// [`self.initial_indent`]: #structfield.initial_indent
pub fn initial_indent(self, indent: &'a str) -> Options<'a> {
pub fn initial_indent(self, indent: &'a str) -> Self {
Options {
initial_indent: indent,
..self
Expand All @@ -325,7 +457,7 @@ impl<'a> Options<'a> {
/// ```
///
/// [`self.subsequent_indent`]: #structfield.subsequent_indent
pub fn subsequent_indent(self, indent: &'a str) -> Options<'a> {
pub fn subsequent_indent(self, indent: &'a str) -> Self {
Options {
subsequent_indent: indent,
..self
Expand All @@ -337,7 +469,7 @@ impl<'a> Options<'a> {
/// sticking out into the right margin.
///
/// [`self.break_words`]: #structfield.break_words
pub fn break_words(self, setting: bool) -> Options<'a> {
pub fn break_words(self, setting: bool) -> Self {
Options {
break_words: setting,
..self
Expand All @@ -349,7 +481,7 @@ impl<'a> Options<'a> {
///
/// [`self.splitter`]: #structfield.splitter
/// [`WordSplitter`]: trait.WordSplitter.html
pub fn splitter(self, splitter: Box<dyn WordSplitter>) -> Options<'a> {
pub fn splitter(self, splitter: S) -> Self {
Options {
splitter: splitter,
..self
Expand Down Expand Up @@ -1057,4 +1189,12 @@ mod tests {
String::from(green_hello) + "\n" + &blue_world
);
}

#[test]
fn cloning_option() {
static OPT: Options<HyphenSplitter> = Options::with_splitter(80, HyphenSplitter);

// just check whether clone works
let opt = OPT.clone();
}
}
15 changes: 15 additions & 0 deletions src/splitting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,21 @@ pub trait WordSplitter: std::fmt::Debug {
fn split<'w>(&self, word: &'w str) -> Vec<(&'w str, &'w str, &'w str)>;
}

impl WordSplitter for Box<dyn WordSplitter> {
fn split<'w>(&self, word: &'w str) -> Vec<(&'w str, &'w str, &'w str)> {
use std::ops::Deref;
self.deref().split(word)
}
}
/* Alternative, also adds impls for specific Box<S> i.e. Box<HyphenSplitter>
impl<S: WordSplitter + ?Sized> WordSplitter for Box<S> {
fn split<'w>(&self, word: &'w str) -> Vec<(&'w str, &'w str, &'w str)> {
use std::ops::Deref;
self.deref().split(word)
}
}
*/

/// Use this as a [`Options.splitter`] to avoid any kind of
/// hyphenation:
///
Expand Down

0 comments on commit f7dfa7d

Please sign in to comment.