Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wordsplitter specialization #77

Merged
merged 3 commits into from
Aug 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ use textwrap::Wrapper;

fn main() {
let corpus = hyphenation::load(Language::English_US).unwrap();
let wrapper = Wrapper::new(18).word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(18, corpus);
let text = "textwrap: a small library for wrapping text.";
println!("{}", wrapper.fill(text))
}
Expand Down Expand Up @@ -173,6 +173,13 @@ cost abstractions.

This section lists the largest changes per release.

### Unreleased

The `Wrapper` stuct now is now generic over the type of word splitter
being used. This means less boxing is needed, which gives a nicer API.
This is a *breaking API change* if you used `Wrapper::word_splitter`
to change the word splitter on the fly.

### Version 0.7.0 — July 20th, 2017

Version 0.7.0 changes the return type of `Wrapper::wrap` from
Expand Down
8 changes: 4 additions & 4 deletions benches/linear.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn wrap_800(b: &mut Bencher) {
fn hyphenation_fill_100(b: &mut Bencher) {
let text = &lorem_ipsum(100);
let corpus = hyphenation::load(Language::Latin).unwrap();
let wrapper = Wrapper::new(LINE_LENGTH).word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(LINE_LENGTH, corpus);
b.iter(|| wrapper.fill(text))
}

Expand All @@ -99,7 +99,7 @@ fn hyphenation_fill_100(b: &mut Bencher) {
fn hyphenation_fill_200(b: &mut Bencher) {
let text = &lorem_ipsum(200);
let corpus = hyphenation::load(Language::Latin).unwrap();
let wrapper = Wrapper::new(LINE_LENGTH).word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(LINE_LENGTH, corpus);
b.iter(|| wrapper.fill(text))
}

Expand All @@ -108,7 +108,7 @@ fn hyphenation_fill_200(b: &mut Bencher) {
fn hyphenation_fill_400(b: &mut Bencher) {
let text = &lorem_ipsum(400);
let corpus = hyphenation::load(Language::Latin).unwrap();
let wrapper = Wrapper::new(LINE_LENGTH).word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(LINE_LENGTH, corpus);
b.iter(|| wrapper.fill(text))
}

Expand All @@ -117,6 +117,6 @@ fn hyphenation_fill_400(b: &mut Bencher) {
fn hyphenation_fill_800(b: &mut Bencher) {
let text = &lorem_ipsum(800);
let corpus = hyphenation::load(Language::Latin).unwrap();
let wrapper = Wrapper::new(LINE_LENGTH).word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(LINE_LENGTH, corpus);
b.iter(|| wrapper.fill(text))
}
6 changes: 3 additions & 3 deletions examples/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ use textwrap::Wrapper;


#[cfg(not(feature = "hyphenation"))]
fn new_wrapper<'a>() -> Wrapper<'a> {
fn new_wrapper<'a>() -> Wrapper<'a, textwrap::HyphenSplitter> {
Wrapper::new(0)
}

#[cfg(feature = "hyphenation")]
fn new_wrapper<'a>() -> Wrapper<'a> {
fn new_wrapper<'a>() -> Wrapper<'a, hyphenation::Corpus> {
let corpus = hyphenation::load(Language::English_US).unwrap();
Wrapper::new(0).word_splitter(Box::new(corpus))
Wrapper::with_splitter(0, corpus)
}

fn main() {
Expand Down
94 changes: 61 additions & 33 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ pub trait WordSplitter {
/// ```
/// use textwrap::{Wrapper, NoHyphenation};
///
/// let wrapper = Wrapper::new(8).word_splitter(Box::new(NoHyphenation));
/// let wrapper = Wrapper::with_splitter(8, NoHyphenation);
/// assert_eq!(wrapper.wrap("foo bar-baz"), vec!["foo", "bar-baz"]);
/// ```
///
Expand Down Expand Up @@ -213,7 +213,7 @@ fn cow_add_assign<'a>(lhs: &mut Cow<'a, str>, rhs: &'a str) {
/// scan over words in the input string and splitting them into one or
/// more lines. The time and memory complexity is O(*n*) where *n* is
/// the length of the input string.
pub struct Wrapper<'a> {
pub struct Wrapper<'a, S: 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 @@ -227,34 +227,53 @@ pub struct Wrapper<'a> {
/// The method for splitting words. If the `hyphenation` feature
/// is enabled, you can use a `hyphenation::language::Corpus` here
/// to get language-aware hyphenation.
pub splitter: Box<WordSplitter>,
pub splitter: S,
}

impl<'a> Wrapper<'a> {
impl<'a> Wrapper<'a, HyphenSplitter> {
/// Create a new Wrapper for wrapping at the specified width. By
/// default, we allow words longer than `width` to be broken. A
/// [`HyphenSplitter`] will be used by default for splitting
/// words. See the [`WordSplitter`] trait for other options.
///
/// [`HyphenSplitter`]: struct.HyphenSplitter.html
/// [`WordSplitter`]: trait.WordSplitter.html
pub fn new(width: usize) -> Wrapper<'a> {
Wrapper {
width: width,
initial_indent: "",
subsequent_indent: "",
break_words: true,
splitter: Box::new(HyphenSplitter),
}
pub fn new(width: usize) -> Wrapper<'a, HyphenSplitter> {
Wrapper::with_splitter(width, HyphenSplitter)
}

/// Create a new Wrapper for wrapping text at the current terminal
/// width. If the terminal width cannot be determined (typically
/// because the standard input and output is not connected to a
/// terminal), a width of 80 characters will be used. Other
/// settings use the same defaults as `Wrapper::new`.
pub fn with_termwidth() -> Wrapper<'a> {
Wrapper::new(term_size::dimensions_stdout().map_or(80, |(w, _)| w))
///
/// Equivalent to:
///
/// ```
/// use textwrap::{Wrapper, termwidth};
///
/// let wrapper = Wrapper::new(termwidth());
/// ```
pub fn with_termwidth() -> Wrapper<'a, HyphenSplitter> {
Wrapper::new(termwidth())
}
}

impl<'a, S: WordSplitter> Wrapper<'a, S> {
/// Use the given [`WordSplitter`] to create a new Wrapper for
/// wrapping at the specified width. By default, we allow words
/// longer than `width` to be broken.
///
/// [`WordSplitter`]: trait.WordSplitter.html
pub fn with_splitter(width: usize, splitter: S) -> Wrapper<'a, S> {
Wrapper {
width: width,
initial_indent: "",
subsequent_indent: "",
break_words: true,
splitter: splitter,
}
}

/// Change [`self.initial_indent`]. The initial indentation is
Expand All @@ -264,7 +283,7 @@ impl<'a> Wrapper<'a> {
/// `self.subsequent_indent` to `" "`.
///
/// [`self.initial_indent`]: #structfield.initial_indent
pub fn initial_indent(self, indent: &'a str) -> Wrapper<'a> {
pub fn initial_indent(self, indent: &'a str) -> Wrapper<'a, S> {
Wrapper { initial_indent: indent, ..self }
}

Expand All @@ -274,7 +293,7 @@ impl<'a> Wrapper<'a> {
/// formatting an item in a bulleted list.
///
/// [`self.subsequent_indent`]: #structfield.subsequent_indent
pub fn subsequent_indent(self, indent: &'a str) -> Wrapper<'a> {
pub fn subsequent_indent(self, indent: &'a str) -> Wrapper<'a, S> {
Wrapper { subsequent_indent: indent, ..self }
}

Expand All @@ -283,22 +302,10 @@ impl<'a> Wrapper<'a> {
/// sticking out into the right margin.
///
/// [`self.break_words`]: #structfield.break_words
pub fn break_words(self, setting: bool) -> Wrapper<'a> {
pub fn break_words(self, setting: bool) -> Wrapper<'a, S> {
Wrapper { break_words: setting, ..self }
}

/// Change [`self.splitter`]. The word splitter is consulted when
/// a word is too wide to fit the current line. By changing this,
/// you can decide if such words should be hyphenated or left
/// alone. Hyphenation can be done using existing hyphens (see
/// [`HyphenSplitter`]) or it can be based on TeX hyphenation
/// patterns, if the `hyphenation` feature is enabled.
///
/// [`self.splitter`]: #structfield.splitter
pub fn word_splitter(self, splitter: Box<WordSplitter>) -> Wrapper<'a> {
Wrapper { splitter: splitter, ..self }
}

/// Fill a line of text at `self.width` characters. Strings are
/// wrapped based on their displayed width, not their size in
/// bytes.
Expand Down Expand Up @@ -444,6 +451,27 @@ impl<'a> Wrapper<'a> {
}
}

/// Return the current terminal width. If the terminal width cannot be
/// determined (typically because the standard output is not connected
/// to a terminal), a default width of 80 characters will be used.
///
/// # Examples
///
/// Create a `Wrapper` for the current terminal with a two column
/// margin:
///
/// ```
/// use textwrap::{Wrapper, NoHyphenation, termwidth};
///
/// let width = termwidth() - 4; // Two columns on each side.
/// let wrapper = Wrapper::with_splitter(width, NoHyphenation)
/// .initial_indent(" ")
/// .subsequent_indent(" ");
/// ```
pub fn termwidth() -> usize {
term_size::dimensions_stdout().map_or(80, |(w, _)| w)
}

/// Fill a line of text at `width` characters. Strings are wrapped
/// based on their displayed width, not their size in bytes.
///
Expand Down Expand Up @@ -728,7 +756,7 @@ mod tests {

#[test]
fn no_hyphenation() {
let wrapper = Wrapper::new(8).word_splitter(Box::new(NoHyphenation));
let wrapper = Wrapper::with_splitter(8, NoHyphenation);
assert_eq!(wrapper.wrap("foo bar-baz"), vec!["foo", "bar-baz"]);
}

Expand All @@ -740,7 +768,7 @@ mod tests {
assert_eq!(wrapper.wrap("Internationalization"),
vec!["Internatio", "nalization"]);

let wrapper = wrapper.word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(10, corpus);
assert_eq!(wrapper.wrap("Internationalization"),
vec!["Interna-", "tionaliza-", "tion"]);
}
Expand All @@ -752,7 +780,7 @@ mod tests {
// line is borrowed.
use std::borrow::Cow::{Borrowed, Owned};
let corpus = hyphenation::load(Language::English_US).unwrap();
let wrapper = Wrapper::new(10).word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(10, corpus);
let lines = wrapper.wrap("Internationalization");
if let Borrowed(s) = lines[0] {
assert!(false, "should not have been borrowed: {:?}", s);
Expand All @@ -772,7 +800,7 @@ mod tests {
let wrapper = Wrapper::new(8).break_words(false);
assert_eq!(wrapper.wrap("over-caffinated"), vec!["over-", "caffinated"]);

let wrapper = wrapper.word_splitter(Box::new(corpus));
let wrapper = Wrapper::with_splitter(8, corpus).break_words(false);
assert_eq!(wrapper.wrap("over-caffinated"),
vec!["over-", "caffi-", "nated"]);
}
Expand Down