diff --git a/examples/multi-layouts.rs b/examples/multi-layouts.rs new file mode 100644 index 00000000..b38f9d1e --- /dev/null +++ b/examples/multi-layouts.rs @@ -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>>, + } + + 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>>(&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); +} diff --git a/src/lib.rs b/src/lib.rs index 65076f4b..1d71a78b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,7 +108,7 @@ pub mod core; /// Holds settings for wrapping and filling text. #[derive(Debug, Clone)] -pub struct Options<'a, S = Box> { +pub struct Options<'a, S: ?Sized = Box> { /// The width in columns at which the text will be wrapped. pub width: usize, /// Indentation used for the first line of output. @@ -125,7 +125,7 @@ pub struct Options<'a, S = Box> { 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, @@ -1239,4 +1239,72 @@ mod tests { fill_inplace(&mut text, 10); assert_eq!(text, "foo bar \nbaz"); } + + #[test] + fn trait_object() { + let opt_a: Options = Options::with_splitter(20, NoHyphenation); + let opt_b: Options = 10.into(); + + let mut dyn_opt: &Options = &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> = 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> + 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> = 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"] + ); + } } diff --git a/src/splitting.rs b/src/splitting.rs index 0d1b46f8..1f3f83b7 100644 --- a/src/splitting.rs +++ b/src/splitting.rs @@ -47,7 +47,7 @@ impl WordSplitter for Box { } } */ -impl WordSplitter for &T { +impl WordSplitter for &T { fn split_points(&self, word: &str) -> Vec { (*self).split_points(word) }