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 mgeisler#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 fns `new_const` and
`with_splitter`.

This change is actually backwards compatible with the previous commit,
as seen by the unchanged examples and tests.
  • Loading branch information
Cryptjar committed Oct 24, 2020
1 parent e49a2a3 commit 6584907
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 13 deletions.
101 changes: 88 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub trait WrapOptions {

/// Holds settings for wrapping and filling text.
#[derive(Debug)]
pub struct Options<'a> {
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,51 @@ impl WrapOptions for usize {
}
}

impl<'a> Options<'a> {
/// Creates a new `Options` with the specified width. Equivalent
/// Const constructors for a good old HyphenSplitter Options, specifically.
impl<'a> Options<'a, HyphenSplitter> {
/// Creates a new `Options` with the specified width and a static dispatch `HyphenSplitter`. Equivalent
/// to
///
/// ```
/// # use textwrap::{Options, HyphenSplitter};
/// # const width: usize = 80;
/// # const actual: Options<'static, HyphenSplitter> = Options::new_const(width);
/// # let expected =
/// Options {
/// width: width,
/// initial_indent: "",
/// subsequent_indent: "",
/// break_words: true,
/// splitter: 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);
/// # let expected_coerced: Options<'static, HyphenSplitter> = expected;
/// ```
pub const fn new_const(width: usize) -> Options<'static, HyphenSplitter> {
Options {
width: width,
initial_indent: "",
subsequent_indent: "",
break_words: true,
splitter: HyphenSplitter,
}
}
}

/// Constructors for boxed Options, specifically.
impl<'a> Options<'a> {
/// Creates a new `Options` with the specified width and dynamic dispatch. Equivalent
/// to
///
/// ```
/// # 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,8 +287,9 @@ 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> {
pub fn new(width: usize) -> Self {
Options {
width: width,
initial_indent: "",
Expand Down Expand Up @@ -280,9 +317,47 @@ 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;
/// ```
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 +375,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 +400,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 +412,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 +424,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
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 6584907

Please sign in to comment.