Skip to content

Commit

Permalink
Merge pull request #219 from Cryptjar/impl-proper-outer-boxing
Browse files Browse the repository at this point in the history
Allow using trait objects with `fill` & `wrap`.
  • Loading branch information
mgeisler authored Nov 17, 2020
2 parents adf149b + a733f92 commit 9abb53c
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 3 deletions.
99 changes: 99 additions & 0 deletions examples/multi-layouts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use textwrap::{NoHyphenation, Options};

#[cfg(feature = "hyphenation")]
use hyphenation::{Language, Load, Standard};

// Pretend this is an external crate
mod library {
use textwrap::{Options, WordSplitter};

#[derive(Debug, Default)]
pub struct Layout<'a> {
// Use trait-objects which can be easily converted from any concrete `Options`
styles: Vec<Box<Options<'a, dyn WordSplitter + 'a>>>,
}

impl<'a> Layout<'a> {
pub fn new() -> Self {
Default::default()
}

// Similar signature like `wrap` has, so it takes (nearly) everything that `warp` takes.
pub fn add<S: WordSplitter + 'a, T: Into<Options<'a, S>>>(&mut self, option: T) {
self.styles.push(Box::new(option.into()));
}

pub fn print(&self, text: &str) {
// Now we can easily go over all the arbitrary Options and use them for layouting.
for opt in self.styles.iter() {
println!();

// the debug output of the hyphenation is very long
//println!("Options: {:#?}", opt);

println!("{:0width$}", 0, width = opt.width);

// Just use the textwrap functions as usual.
// However, we have to first coerce it into a trait-object
let dyn_opt: &Options<'a, dyn WordSplitter> = opt;
println!("{}", textwrap::fill(text, dyn_opt));
}
}
}
}

pub fn main() {
// pretend we are a user of the library module above

// Just some options (see below for usage)
let some_opt = Options::new(25).initial_indent("----");

// The struct from the 'library' that we are using
let mut layout = library::Layout::new();

// Add some arbitrary options. We can use here the same as for `fill` & `wrap`.

// Plain Options
layout.add(Options::new(20));

// usize
layout.add(30);

// Customized Options
let opt = Options::new(30);
let opt = opt.subsequent_indent("****");
layout.add(opt.clone()); // notice, here we pass opt by-value instead of by-reference

// We can use boxed splitters too (however, we have to coerce the Options)
let opt: Options = opt.splitter(Box::new(NoHyphenation));
layout.add(opt);

// We can also pass-in references, however, those need to outlive the local
// `layout`, so here, it must be declared before `layout` (drop order).
layout.add(&some_opt);

// If you like, you can download more dictionaries from
// https://github.com/tapeinosyne/hyphenation/tree/master/dictionaries
// Place the dictionaries in the examples/ directory. Here we
// just load the embedded en-us dictionary.
#[cfg(feature = "hyphenation")]
for lang in &[Language::EnglishUS] {
let dictionary = Standard::from_embedded(*lang).or_else(|_| {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("examples")
.join(format!("{}.standard.bincode", lang.code()));
Standard::from_path(*lang, &path)
});

if let Ok(dict) = dictionary {
layout.add(Options::with_splitter(25, dict));
}
}

let example = "Memory safety without garbage collection. \
Concurrency without data races. \
Zero-cost abstractions.";

// Printout above text in all different layouts
layout.print(example);
}
72 changes: 70 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub mod core;

/// Holds settings for wrapping and filling text.
#[derive(Debug, Clone)]
pub struct Options<'a, S = Box<dyn WordSplitter>> {
pub struct Options<'a, S: ?Sized = 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 @@ -125,7 +125,7 @@ pub struct Options<'a, S = Box<dyn WordSplitter>> {
pub splitter: S,
}

impl<'a, S> From<&'a Options<'a, S>> for Options<'a, &'a S> {
impl<'a, S: ?Sized> From<&'a Options<'a, S>> for Options<'a, &'a S> {
fn from(options: &'a Options<'a, S>) -> Self {
Self {
width: options.width,
Expand Down Expand Up @@ -1206,4 +1206,72 @@ mod tests {
fill_inplace(&mut text, 10);
assert_eq!(text, "foo bar \nbaz");
}

#[test]
fn trait_object() {
let opt_a: Options<NoHyphenation> = Options::with_splitter(20, NoHyphenation);
let opt_b: Options<HyphenSplitter> = 10.into();

let mut dyn_opt: &Options<dyn WordSplitter> = &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]
fn trait_object_vec() {
// Create a vector of referenced trait-objects
let mut vector: Vec<&Options<dyn WordSplitter>> = Vec::new();
// Expected result from each options
let mut results = Vec::new();

let opt_usize: Options<_> = 10.into();
vector.push(&opt_usize);
results.push(vec!["over-", "caffinated"]);

#[cfg(feature = "hyphenation")]
let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
#[cfg(feature = "hyphenation")]
let opt_hyp = Options::new(8).splitter(dictionary);
#[cfg(feature = "hyphenation")]
vector.push(&opt_hyp);
#[cfg(feature = "hyphenation")]
results.push(vec!["over-", "caffi-", "nated"]);

// Actually: Options<Box<dyn WordSplitter>>
let opt_box: Options = Options::new(10)
.break_words(false)
.splitter(Box::new(NoHyphenation));
vector.push(&opt_box);
results.push(vec!["over-caffinated"]);

// Test each entry
for (opt, expected) in vector.into_iter().zip(results) {
assert_eq!(
// Just all the totally different options
wrap("over-caffinated", opt),
expected
);
}
}

#[test]
fn outer_boxing() {
let mut wrapper: Box<Options<dyn WordSplitter>> = 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(Options::from(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"]
);
}
}
2 changes: 1 addition & 1 deletion src/splitting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl<S: WordSplitter + ?Sized> WordSplitter for Box<S> {
}
}
*/
impl<T: WordSplitter> WordSplitter for &T {
impl<T: ?Sized + WordSplitter> WordSplitter for &T {
fn split_points(&self, word: &str) -> Vec<usize> {
(*self).split_points(word)
}
Expand Down

0 comments on commit 9abb53c

Please sign in to comment.