Skip to content

Commit

Permalink
Allow using trait objects with fill & wrap.
Browse files Browse the repository at this point in the history
Since, trait objects needs to be at least behind a reference, a generic impl
of `WrapOptions` is added for every reference `&T` where `T` already
implements `WrapOptions`. Thus now the `fill` & `wrap` can be feed with any
reference including reference to trait objects.

Since this new generic impl already provides to pass `Options` by-reference
the `WrapOptions` impl for `Options` has be changed from its reference to
it's value, removing the need for some double referencing here and there.
  • Loading branch information
Cryptjar committed Nov 10, 2020
1 parent b773f83 commit 4b272cc
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 6 deletions.
8 changes: 4 additions & 4 deletions src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,14 +231,14 @@ pub fn find_words(line: &str) -> impl Iterator<Item = Word> {
/// // The default splitter is HyphenSplitter:
/// let options = Options::new(80);
/// assert_eq!(
/// split_words(vec![Word::from("foo-bar")], &&options).collect::<Vec<_>>(),
/// split_words(vec![Word::from("foo-bar")], &options).collect::<Vec<_>>(),
/// vec![Word::from("foo-"), Word::from("bar")]
/// );
///
/// // The NoHyphenation splitter ignores the '-':
/// let options = Options::new(80).splitter(NoHyphenation);
/// assert_eq!(
/// split_words(vec![Word::from("foo-bar")], &&options).collect::<Vec<_>>(),
/// split_words(vec![Word::from("foo-bar")], &options).collect::<Vec<_>>(),
/// vec![Word::from("foo-bar")]
/// );
/// ```
Expand Down Expand Up @@ -550,7 +550,7 @@ mod tests {

let options = Options::new(80).splitter(FixedSplitPoint);
assert_iter_eq!(
split_words(vec![Word::from("foobar")].into_iter(), &&options),
split_words(vec![Word::from("foobar")].into_iter(), &options),
vec![
Word {
word: "foo",
Expand All @@ -568,7 +568,7 @@ mod tests {
);

assert_iter_eq!(
split_words(vec![Word::from("fo-bar")].into_iter(), &&options),
split_words(vec![Word::from("fo-bar")].into_iter(), &options),
vec![
Word {
word: "fo-",
Expand Down
95 changes: 93 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,35 @@ pub trait WrapOptions {
fn split_points(&self, word: &str) -> Vec<usize>;
}

/// Implement WrapOptions on any reference which implements WrapOptions
impl<T: ?Sized + WrapOptions> WrapOptions for &T {
#[inline]
fn width(&self) -> usize {
use std::ops::Deref;
self.deref().width()
}
#[inline]
fn initial_indent(&self) -> &str {
use std::ops::Deref;
self.deref().initial_indent()
}
#[inline]
fn subsequent_indent(&self) -> &str {
use std::ops::Deref;
self.deref().subsequent_indent()
}
#[inline]
fn break_words(&self) -> bool {
use std::ops::Deref;
self.deref().break_words()
}
#[inline]
fn split_points(&self, word: &str) -> Vec<usize> {
use std::ops::Deref;
self.deref().split_points(word)
}
}

/// Holds settings for wrapping and filling text.
#[derive(Debug, Clone)]
pub struct Options<'a, S = Box<dyn WordSplitter>> {
Expand Down Expand Up @@ -159,7 +188,7 @@ pub struct Options<'a, S = Box<dyn WordSplitter>> {
///
/// [`wrap`]: fn.wrap.html
/// [`fill`]: fn.fill.html
impl<S: WordSplitter> WrapOptions for &Options<'_, S> {
impl<S: WordSplitter> WrapOptions for Options<'_, S> {
#[inline]
fn width(&self) -> usize {
self.width
Expand Down Expand Up @@ -748,7 +777,7 @@ mod tests {
#[test]
fn options_agree_with_usize() {
let opt_usize: &dyn WrapOptions = &42;
let opt_options: &dyn WrapOptions = &&Options::new(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());
Expand All @@ -763,6 +792,16 @@ mod tests {
);
}

#[test]
fn options_agree_with_usize_filling() {
let opt_usize: &dyn WrapOptions = &6;
let opt_options: &dyn WrapOptions = &Options::new(6);

let text = "hello-world a b c d e f Internationalization αβγδε Hello, World!";

assert_eq!(fill(text, opt_usize), fill(text, opt_options),);
}

#[test]
fn no_wrap() {
assert_eq!(wrap("foo", 10), vec!["foo"]);
Expand Down Expand Up @@ -1160,5 +1199,57 @@ mod tests {

// just ensure that clone works
let opt = OPT.clone();
}

#[test]
fn trait_object() {
let opt_a: Options<HyphenSplitter> = Options::new(20);
let opt_b: usize = 10;

let mut dyn_opt: &dyn WrapOptions = &opt_a;
assert_eq!(wrap("foo bar-baz", dyn_opt), vec!["foo bar-baz"]);

// Just assign a totally different option
dyn_opt = &opt_b;
assert_eq!(wrap("foo bar-baz", dyn_opt), vec!["foo bar-", "baz"]);
}

#[test]
#[cfg(feature = "hyphenation")]
fn trait_object_with_hyphenation() {
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
let opt_hyp = Options::new(8).splitter(dictionary);
let opt_usize: usize = 10;

let mut dyn_opt: &dyn WrapOptions = &opt_usize;
assert_eq!(
wrap("over-caffinated", &dyn_opt),
vec!["over-", "caffinated"]
);

// Just assign a totally different option
dyn_opt = &opt_hyp;
assert_eq!(
wrap("over-caffinated", &dyn_opt),
vec!["over-", "caffi-", "nated"]
);
}

#[test]
fn proper_outer_boxing() {
let mut wrapper: Box<dyn WrapOptions> = Box::new(Options::new(80));

// We must first deref the Box into a trait object and pass it by-reference
assert_eq!(wrap("foo bar baz", &*wrapper), vec!["foo bar baz"]);

// Replace the `Options` with a `usize`
wrapper = Box::new(5);

// Deref per-se works as well, it already returns a reference
use std::ops::Deref;
assert_eq!(
wrap("foo bar baz", wrapper.deref()),
vec!["foo", "bar", "baz"]
);
}
}

0 comments on commit 4b272cc

Please sign in to comment.