1 //! `textwrap` provides functions for word wrapping and filling text.
2 //!
3 //! Wrapping text can be very useful in commandline programs where you
4 //! want to format dynamic output nicely so it looks good in a
5 //! terminal. A quick example:
6 //!
7 //! ```no_run
8 //! extern crate textwrap;
9 //! use textwrap::fill;
10 //!
11 //! fn main() {
12 //! let text = "textwrap: a small library for wrapping text.";
13 //! println!("{}", fill(text, 18));
14 //! }
15 //! ```
16 //!
17 //! This will display the following output:
18 //!
19 //! ```text
20 //! textwrap: a small
21 //! library for
22 //! wrapping text.
23 //! ```
24 //!
25 //! # Displayed Width vs Byte Size
26 //!
27 //! To word wrap text, one must know the width of each word so one can
28 //! know when to break lines. This library measures the width of text
29 //! using the [displayed width][unicode-width], not the size in bytes.
30 //!
31 //! This is important for non-ASCII text. ASCII characters such as `a`
32 //! and `!` are simple and take up one column each. This means that
33 //! the displayed width is equal to the string length in bytes.
34 //! However, non-ASCII characters and symbols take up more than one
35 //! byte when UTF-8 encoded: `é` is `0xc3 0xa9` (two bytes) and `⚙` is
36 //! `0xe2 0x9a 0x99` (three bytes) in UTF-8, respectively.
37 //!
38 //! This is why we take care to use the displayed width instead of the
39 //! byte count when computing line lengths. All functions in this
40 //! library handle Unicode characters like this.
41 //!
42 //! [unicode-width]: https://docs.rs/unicode-width/
43
44 #![doc(html_root_url = "https://docs.rs/textwrap/0.11.0")]
45 #![deny(missing_docs)]
46 #![deny(missing_debug_implementations)]
47
48 #[cfg(feature = "hyphenation")]
49 extern crate hyphenation;
50 #[cfg(feature = "term_size")]
51 extern crate term_size;
52 extern crate unicode_width;
53
54 use std::borrow::Cow;
55 use std::str::CharIndices;
56
57 use unicode_width::UnicodeWidthChar;
58 use unicode_width::UnicodeWidthStr;
59
60 /// A non-breaking space.
61 const NBSP: char = '\u{a0}';
62
63 mod indentation;
64 pub use indentation::dedent;
65 pub use indentation::indent;
66
67 mod splitting;
68 pub use splitting::{HyphenSplitter, NoHyphenation, WordSplitter};
69
70 /// A Wrapper holds settings for wrapping and filling text. Use it
71 /// when the convenience [`wrap_iter`], [`wrap`] and [`fill`] functions
72 /// are not flexible enough.
73 ///
74 /// [`wrap_iter`]: fn.wrap_iter.html
75 /// [`wrap`]: fn.wrap.html
76 /// [`fill`]: fn.fill.html
77 ///
78 /// The algorithm used by the `WrapIter` iterator (returned from the
79 /// `wrap_iter` method) works by doing successive partial scans over
80 /// words in the input string (where each single scan yields a single
81 /// line) so that the overall time and memory complexity is O(*n*) where
82 /// *n* is the length of the input string.
83 #[derive(Clone, Debug)]
84 pub struct Wrapper<'a, S: WordSplitter> {
85 /// The width in columns at which the text will be wrapped.
86 pub width: usize,
87 /// Indentation used for the first line of output.
88 pub initial_indent: &'a str,
89 /// Indentation used for subsequent lines of output.
90 pub subsequent_indent: &'a str,
91 /// Allow long words to be broken if they cannot fit on a line.
92 /// When set to `false`, some lines may be longer than
93 /// `self.width`.
94 pub break_words: bool,
95 /// The method for splitting words. If the `hyphenation` feature
96 /// is enabled, you can use a `hyphenation::Standard` dictionary
97 /// here to get language-aware hyphenation.
98 pub splitter: S,
99 }
100
101 impl<'a> Wrapper<'a, HyphenSplitter> {
102 /// Create a new Wrapper for wrapping at the specified width. By
103 /// default, we allow words longer than `width` to be broken. A
104 /// [`HyphenSplitter`] will be used by default for splitting
105 /// words. See the [`WordSplitter`] trait for other options.
106 ///
107 /// [`HyphenSplitter`]: struct.HyphenSplitter.html
108 /// [`WordSplitter`]: trait.WordSplitter.html
new(width: usize) -> Wrapper<'a, HyphenSplitter>109 pub fn new(width: usize) -> Wrapper<'a, HyphenSplitter> {
110 Wrapper::with_splitter(width, HyphenSplitter)
111 }
112
113 /// Create a new Wrapper for wrapping text at the current terminal
114 /// width. If the terminal width cannot be determined (typically
115 /// because the standard input and output is not connected to a
116 /// terminal), a width of 80 characters will be used. Other
117 /// settings use the same defaults as `Wrapper::new`.
118 ///
119 /// Equivalent to:
120 ///
121 /// ```no_run
122 /// # #![allow(unused_variables)]
123 /// use textwrap::{Wrapper, termwidth};
124 ///
125 /// let wrapper = Wrapper::new(termwidth());
126 /// ```
127 #[cfg(feature = "term_size")]
with_termwidth() -> Wrapper<'a, HyphenSplitter>128 pub fn with_termwidth() -> Wrapper<'a, HyphenSplitter> {
129 Wrapper::new(termwidth())
130 }
131 }
132
133 impl<'a, S: WordSplitter> Wrapper<'a, S> {
134 /// Use the given [`WordSplitter`] to create a new Wrapper for
135 /// wrapping at the specified width. By default, we allow words
136 /// longer than `width` to be broken.
137 ///
138 /// [`WordSplitter`]: trait.WordSplitter.html
with_splitter(width: usize, splitter: S) -> Wrapper<'a, S>139 pub fn with_splitter(width: usize, splitter: S) -> Wrapper<'a, S> {
140 Wrapper {
141 width: width,
142 initial_indent: "",
143 subsequent_indent: "",
144 break_words: true,
145 splitter: splitter,
146 }
147 }
148
149 /// Change [`self.initial_indent`]. The initial indentation is
150 /// used on the very first line of output.
151 ///
152 /// # Examples
153 ///
154 /// Classic paragraph indentation can be achieved by specifying an
155 /// initial indentation and wrapping each paragraph by itself:
156 ///
157 /// ```no_run
158 /// # #![allow(unused_variables)]
159 /// use textwrap::Wrapper;
160 ///
161 /// let wrapper = Wrapper::new(15).initial_indent(" ");
162 /// ```
163 ///
164 /// [`self.initial_indent`]: #structfield.initial_indent
initial_indent(self, indent: &'a str) -> Wrapper<'a, S>165 pub fn initial_indent(self, indent: &'a str) -> Wrapper<'a, S> {
166 Wrapper {
167 initial_indent: indent,
168 ..self
169 }
170 }
171
172 /// Change [`self.subsequent_indent`]. The subsequent indentation
173 /// is used on lines following the first line of output.
174 ///
175 /// # Examples
176 ///
177 /// Combining initial and subsequent indentation lets you format a
178 /// single paragraph as a bullet list:
179 ///
180 /// ```no_run
181 /// # #![allow(unused_variables)]
182 /// use textwrap::Wrapper;
183 ///
184 /// let wrapper = Wrapper::new(15)
185 /// .initial_indent("* ")
186 /// .subsequent_indent(" ");
187 /// ```
188 ///
189 /// [`self.subsequent_indent`]: #structfield.subsequent_indent
subsequent_indent(self, indent: &'a str) -> Wrapper<'a, S>190 pub fn subsequent_indent(self, indent: &'a str) -> Wrapper<'a, S> {
191 Wrapper {
192 subsequent_indent: indent,
193 ..self
194 }
195 }
196
197 /// Change [`self.break_words`]. This controls if words longer
198 /// than `self.width` can be broken, or if they will be left
199 /// sticking out into the right margin.
200 ///
201 /// [`self.break_words`]: #structfield.break_words
break_words(self, setting: bool) -> Wrapper<'a, S>202 pub fn break_words(self, setting: bool) -> Wrapper<'a, S> {
203 Wrapper {
204 break_words: setting,
205 ..self
206 }
207 }
208
209 /// Fill a line of text at `self.width` characters. Strings are
210 /// wrapped based on their displayed width, not their size in
211 /// bytes.
212 ///
213 /// The result is a string with newlines between each line. Use
214 /// the `wrap` method if you need access to the individual lines.
215 ///
216 /// # Complexities
217 ///
218 /// This method simply joins the lines produced by `wrap_iter`. As
219 /// such, it inherits the O(*n*) time and memory complexity where
220 /// *n* is the input string length.
221 ///
222 /// # Examples
223 ///
224 /// ```
225 /// use textwrap::Wrapper;
226 ///
227 /// let wrapper = Wrapper::new(15);
228 /// assert_eq!(wrapper.fill("Memory safety without garbage collection."),
229 /// "Memory safety\nwithout garbage\ncollection.");
230 /// ```
fill(&self, s: &str) -> String231 pub fn fill(&self, s: &str) -> String {
232 // This will avoid reallocation in simple cases (no
233 // indentation, no hyphenation).
234 let mut result = String::with_capacity(s.len());
235
236 for (i, line) in self.wrap_iter(s).enumerate() {
237 if i > 0 {
238 result.push('\n');
239 }
240 result.push_str(&line);
241 }
242
243 result
244 }
245
246 /// Wrap a line of text at `self.width` characters. Strings are
247 /// wrapped based on their displayed width, not their size in
248 /// bytes.
249 ///
250 /// # Complexities
251 ///
252 /// This method simply collects the lines produced by `wrap_iter`.
253 /// As such, it inherits the O(*n*) overall time and memory
254 /// complexity where *n* is the input string length.
255 ///
256 /// # Examples
257 ///
258 /// ```
259 /// use textwrap::Wrapper;
260 ///
261 /// let wrap15 = Wrapper::new(15);
262 /// assert_eq!(wrap15.wrap("Concurrency without data races."),
263 /// vec!["Concurrency",
264 /// "without data",
265 /// "races."]);
266 ///
267 /// let wrap20 = Wrapper::new(20);
268 /// assert_eq!(wrap20.wrap("Concurrency without data races."),
269 /// vec!["Concurrency without",
270 /// "data races."]);
271 /// ```
272 ///
273 /// Notice that newlines in the input are preserved. This means
274 /// that they force a line break, regardless of how long the
275 /// current line is:
276 ///
277 /// ```
278 /// use textwrap::Wrapper;
279 ///
280 /// let wrapper = Wrapper::new(40);
281 /// assert_eq!(wrapper.wrap("First line.\nSecond line."),
282 /// vec!["First line.", "Second line."]);
283 /// ```
284 ///
wrap(&self, s: &'a str) -> Vec<Cow<'a, str>>285 pub fn wrap(&self, s: &'a str) -> Vec<Cow<'a, str>> {
286 self.wrap_iter(s).collect::<Vec<_>>()
287 }
288
289 /// Lazily wrap a line of text at `self.width` characters. Strings
290 /// are wrapped based on their displayed width, not their size in
291 /// bytes.
292 ///
293 /// The [`WordSplitter`] stored in [`self.splitter`] is used
294 /// whenever when a word is too large to fit on the current line.
295 /// By changing the field, different hyphenation strategies can be
296 /// implemented.
297 ///
298 /// # Complexities
299 ///
300 /// This method returns a [`WrapIter`] iterator which borrows this
301 /// `Wrapper`. The algorithm used has a linear complexity, so
302 /// getting the next line from the iterator will take O(*w*) time,
303 /// where *w* is the wrapping width. Fully processing the iterator
304 /// will take O(*n*) time for an input string of length *n*.
305 ///
306 /// When no indentation is used, each line returned is a slice of
307 /// the input string and the memory overhead is thus constant.
308 /// Otherwise new memory is allocated for each line returned.
309 ///
310 /// # Examples
311 ///
312 /// ```
313 /// use std::borrow::Cow;
314 /// use textwrap::Wrapper;
315 ///
316 /// let wrap20 = Wrapper::new(20);
317 /// let mut wrap20_iter = wrap20.wrap_iter("Zero-cost abstractions.");
318 /// assert_eq!(wrap20_iter.next(), Some(Cow::from("Zero-cost")));
319 /// assert_eq!(wrap20_iter.next(), Some(Cow::from("abstractions.")));
320 /// assert_eq!(wrap20_iter.next(), None);
321 ///
322 /// let wrap25 = Wrapper::new(25);
323 /// let mut wrap25_iter = wrap25.wrap_iter("Zero-cost abstractions.");
324 /// assert_eq!(wrap25_iter.next(), Some(Cow::from("Zero-cost abstractions.")));
325 /// assert_eq!(wrap25_iter.next(), None);
326 /// ```
327 ///
328 /// [`self.splitter`]: #structfield.splitter
329 /// [`WordSplitter`]: trait.WordSplitter.html
330 /// [`WrapIter`]: struct.WrapIter.html
wrap_iter<'w>(&'w self, s: &'a str) -> WrapIter<'w, 'a, S>331 pub fn wrap_iter<'w>(&'w self, s: &'a str) -> WrapIter<'w, 'a, S> {
332 WrapIter {
333 wrapper: self,
334 inner: WrapIterImpl::new(self, s),
335 }
336 }
337
338 /// Lazily wrap a line of text at `self.width` characters. Strings
339 /// are wrapped based on their displayed width, not their size in
340 /// bytes.
341 ///
342 /// The [`WordSplitter`] stored in [`self.splitter`] is used
343 /// whenever when a word is too large to fit on the current line.
344 /// By changing the field, different hyphenation strategies can be
345 /// implemented.
346 ///
347 /// # Complexities
348 ///
349 /// This method consumes the `Wrapper` and returns a
350 /// [`IntoWrapIter`] iterator. Fully processing the iterator has
351 /// the same O(*n*) time complexity as [`wrap_iter`], where *n* is
352 /// the length of the input string.
353 ///
354 /// # Examples
355 ///
356 /// ```
357 /// use std::borrow::Cow;
358 /// use textwrap::Wrapper;
359 ///
360 /// let wrap20 = Wrapper::new(20);
361 /// let mut wrap20_iter = wrap20.into_wrap_iter("Zero-cost abstractions.");
362 /// assert_eq!(wrap20_iter.next(), Some(Cow::from("Zero-cost")));
363 /// assert_eq!(wrap20_iter.next(), Some(Cow::from("abstractions.")));
364 /// assert_eq!(wrap20_iter.next(), None);
365 /// ```
366 ///
367 /// [`self.splitter`]: #structfield.splitter
368 /// [`WordSplitter`]: trait.WordSplitter.html
369 /// [`IntoWrapIter`]: struct.IntoWrapIter.html
370 /// [`wrap_iter`]: #method.wrap_iter
into_wrap_iter(self, s: &'a str) -> IntoWrapIter<'a, S>371 pub fn into_wrap_iter(self, s: &'a str) -> IntoWrapIter<'a, S> {
372 let inner = WrapIterImpl::new(&self, s);
373
374 IntoWrapIter {
375 wrapper: self,
376 inner: inner,
377 }
378 }
379 }
380
381 /// An iterator over the lines of the input string which owns a
382 /// `Wrapper`. An instance of `IntoWrapIter` is typically obtained
383 /// through either [`wrap_iter`] or [`Wrapper::into_wrap_iter`].
384 ///
385 /// Each call of `.next()` method yields a line wrapped in `Some` if the
386 /// input hasn't been fully processed yet. Otherwise it returns `None`.
387 ///
388 /// [`wrap_iter`]: fn.wrap_iter.html
389 /// [`Wrapper::into_wrap_iter`]: struct.Wrapper.html#method.into_wrap_iter
390 #[derive(Debug)]
391 pub struct IntoWrapIter<'a, S: WordSplitter> {
392 wrapper: Wrapper<'a, S>,
393 inner: WrapIterImpl<'a>,
394 }
395
396 impl<'a, S: WordSplitter> Iterator for IntoWrapIter<'a, S> {
397 type Item = Cow<'a, str>;
398
next(&mut self) -> Option<Cow<'a, str>>399 fn next(&mut self) -> Option<Cow<'a, str>> {
400 self.inner.next(&self.wrapper)
401 }
402 }
403
404 /// An iterator over the lines of the input string which borrows a
405 /// `Wrapper`. An instance of `WrapIter` is typically obtained
406 /// through the [`Wrapper::wrap_iter`] method.
407 ///
408 /// Each call of `.next()` method yields a line wrapped in `Some` if the
409 /// input hasn't been fully processed yet. Otherwise it returns `None`.
410 ///
411 /// [`Wrapper::wrap_iter`]: struct.Wrapper.html#method.wrap_iter
412 #[derive(Debug)]
413 pub struct WrapIter<'w, 'a: 'w, S: WordSplitter + 'w> {
414 wrapper: &'w Wrapper<'a, S>,
415 inner: WrapIterImpl<'a>,
416 }
417
418 impl<'w, 'a: 'w, S: WordSplitter> Iterator for WrapIter<'w, 'a, S> {
419 type Item = Cow<'a, str>;
420
next(&mut self) -> Option<Cow<'a, str>>421 fn next(&mut self) -> Option<Cow<'a, str>> {
422 self.inner.next(self.wrapper)
423 }
424 }
425
426 /// Like `char::is_whitespace`, but non-breaking spaces don't count.
427 #[inline]
is_whitespace(ch: char) -> bool428 fn is_whitespace(ch: char) -> bool {
429 ch.is_whitespace() && ch != NBSP
430 }
431
432 /// Common implementation details for `WrapIter` and `IntoWrapIter`.
433 #[derive(Debug)]
434 struct WrapIterImpl<'a> {
435 // String to wrap.
436 source: &'a str,
437 // CharIndices iterator over self.source.
438 char_indices: CharIndices<'a>,
439 // Byte index where the current line starts.
440 start: usize,
441 // Byte index of the last place where the string can be split.
442 split: usize,
443 // Size in bytes of the character at self.source[self.split].
444 split_len: usize,
445 // Width of self.source[self.start..idx].
446 line_width: usize,
447 // Width of self.source[self.start..self.split].
448 line_width_at_split: usize,
449 // Tracking runs of whitespace characters.
450 in_whitespace: bool,
451 // Has iterator finished producing elements?
452 finished: bool,
453 }
454
455 impl<'a> WrapIterImpl<'a> {
new<S: WordSplitter>(wrapper: &Wrapper<'a, S>, s: &'a str) -> WrapIterImpl<'a>456 fn new<S: WordSplitter>(wrapper: &Wrapper<'a, S>, s: &'a str) -> WrapIterImpl<'a> {
457 WrapIterImpl {
458 source: s,
459 char_indices: s.char_indices(),
460 start: 0,
461 split: 0,
462 split_len: 0,
463 line_width: wrapper.initial_indent.width(),
464 line_width_at_split: wrapper.initial_indent.width(),
465 in_whitespace: false,
466 finished: false,
467 }
468 }
469
create_result_line<S: WordSplitter>(&self, wrapper: &Wrapper<'a, S>) -> Cow<'a, str>470 fn create_result_line<S: WordSplitter>(&self, wrapper: &Wrapper<'a, S>) -> Cow<'a, str> {
471 if self.start == 0 {
472 Cow::from(wrapper.initial_indent)
473 } else {
474 Cow::from(wrapper.subsequent_indent)
475 }
476 }
477
next<S: WordSplitter>(&mut self, wrapper: &Wrapper<'a, S>) -> Option<Cow<'a, str>>478 fn next<S: WordSplitter>(&mut self, wrapper: &Wrapper<'a, S>) -> Option<Cow<'a, str>> {
479 if self.finished {
480 return None;
481 }
482
483 while let Some((idx, ch)) = self.char_indices.next() {
484 let char_width = ch.width().unwrap_or(0);
485 let char_len = ch.len_utf8();
486
487 if ch == '\n' {
488 self.split = idx;
489 self.split_len = char_len;
490 self.line_width_at_split = self.line_width;
491 self.in_whitespace = false;
492
493 // If this is not the final line, return the current line. Otherwise,
494 // we will return the line with its line break after exiting the loop
495 if self.split + self.split_len < self.source.len() {
496 let mut line = self.create_result_line(wrapper);
497 line += &self.source[self.start..self.split];
498
499 self.start = self.split + self.split_len;
500 self.line_width = wrapper.subsequent_indent.width();
501
502 return Some(line);
503 }
504 } else if is_whitespace(ch) {
505 // Extend the previous split or create a new one.
506 if self.in_whitespace {
507 self.split_len += char_len;
508 } else {
509 self.split = idx;
510 self.split_len = char_len;
511 }
512 self.line_width_at_split = self.line_width + char_width;
513 self.in_whitespace = true;
514 } else if self.line_width + char_width > wrapper.width {
515 // There is no room for this character on the current
516 // line. Try to split the final word.
517 self.in_whitespace = false;
518 let remaining_text = &self.source[self.split + self.split_len..];
519 let final_word = match remaining_text.find(is_whitespace) {
520 Some(i) => &remaining_text[..i],
521 None => remaining_text,
522 };
523
524 let mut hyphen = "";
525 let splits = wrapper.splitter.split(final_word);
526 for &(head, hyp, _) in splits.iter().rev() {
527 if self.line_width_at_split + head.width() + hyp.width() <= wrapper.width {
528 // We can fit head into the current line.
529 // Advance the split point by the width of the
530 // whitespace and the head length.
531 self.split += self.split_len + head.len();
532 self.split_len = 0;
533 hyphen = hyp;
534 break;
535 }
536 }
537
538 if self.start >= self.split {
539 // The word is too big to fit on a single line, so we
540 // need to split it at the current index.
541 if wrapper.break_words {
542 // Break work at current index.
543 self.split = idx;
544 self.split_len = 0;
545 self.line_width_at_split = self.line_width;
546 } else {
547 // Add smallest split.
548 self.split = self.start + splits[0].0.len();
549 self.split_len = 0;
550 self.line_width_at_split = self.line_width;
551 }
552 }
553
554 if self.start < self.split {
555 let mut line = self.create_result_line(wrapper);
556 line += &self.source[self.start..self.split];
557 line += hyphen;
558
559 self.start = self.split + self.split_len;
560 self.line_width += wrapper.subsequent_indent.width();
561 self.line_width -= self.line_width_at_split;
562 self.line_width += char_width;
563
564 return Some(line);
565 }
566 } else {
567 self.in_whitespace = false;
568 }
569 self.line_width += char_width;
570 }
571
572 self.finished = true;
573
574 // Add final line.
575 if self.start < self.source.len() {
576 let mut line = self.create_result_line(wrapper);
577 line += &self.source[self.start..];
578 return Some(line);
579 }
580
581 None
582 }
583 }
584
585 /// Return the current terminal width. If the terminal width cannot be
586 /// determined (typically because the standard output is not connected
587 /// to a terminal), a default width of 80 characters will be used.
588 ///
589 /// # Examples
590 ///
591 /// Create a `Wrapper` for the current terminal with a two column
592 /// margin:
593 ///
594 /// ```no_run
595 /// # #![allow(unused_variables)]
596 /// use textwrap::{Wrapper, NoHyphenation, termwidth};
597 ///
598 /// let width = termwidth() - 4; // Two columns on each side.
599 /// let wrapper = Wrapper::with_splitter(width, NoHyphenation)
600 /// .initial_indent(" ")
601 /// .subsequent_indent(" ");
602 /// ```
603 #[cfg(feature = "term_size")]
termwidth() -> usize604 pub fn termwidth() -> usize {
605 term_size::dimensions_stdout().map_or(80, |(w, _)| w)
606 }
607
608 /// Fill a line of text at `width` characters. Strings are wrapped
609 /// based on their displayed width, not their size in bytes.
610 ///
611 /// The result is a string with newlines between each line. Use
612 /// [`wrap`] if you need access to the individual lines or
613 /// [`wrap_iter`] for its iterator counterpart.
614 ///
615 /// ```
616 /// use textwrap::fill;
617 ///
618 /// assert_eq!(fill("Memory safety without garbage collection.", 15),
619 /// "Memory safety\nwithout garbage\ncollection.");
620 /// ```
621 ///
622 /// This function creates a Wrapper on the fly with default settings.
623 /// If you need to set a language corpus for automatic hyphenation, or
624 /// need to fill many strings, then it is suggested to create a Wrapper
625 /// and call its [`fill` method].
626 ///
627 /// [`wrap`]: fn.wrap.html
628 /// [`wrap_iter`]: fn.wrap_iter.html
629 /// [`fill` method]: struct.Wrapper.html#method.fill
fill(s: &str, width: usize) -> String630 pub fn fill(s: &str, width: usize) -> String {
631 Wrapper::new(width).fill(s)
632 }
633
634 /// Wrap a line of text at `width` characters. Strings are wrapped
635 /// based on their displayed width, not their size in bytes.
636 ///
637 /// This function creates a Wrapper on the fly with default settings.
638 /// If you need to set a language corpus for automatic hyphenation, or
639 /// need to wrap many strings, then it is suggested to create a Wrapper
640 /// and call its [`wrap` method].
641 ///
642 /// The result is a vector of strings. Use [`wrap_iter`] if you need an
643 /// iterator version.
644 ///
645 /// # Examples
646 ///
647 /// ```
648 /// use textwrap::wrap;
649 ///
650 /// assert_eq!(wrap("Concurrency without data races.", 15),
651 /// vec!["Concurrency",
652 /// "without data",
653 /// "races."]);
654 ///
655 /// assert_eq!(wrap("Concurrency without data races.", 20),
656 /// vec!["Concurrency without",
657 /// "data races."]);
658 /// ```
659 ///
660 /// [`wrap_iter`]: fn.wrap_iter.html
661 /// [`wrap` method]: struct.Wrapper.html#method.wrap
wrap(s: &str, width: usize) -> Vec<Cow<str>>662 pub fn wrap(s: &str, width: usize) -> Vec<Cow<str>> {
663 Wrapper::new(width).wrap(s)
664 }
665
666 /// Lazily wrap a line of text at `width` characters. Strings are
667 /// wrapped based on their displayed width, not their size in bytes.
668 ///
669 /// This function creates a Wrapper on the fly with default settings.
670 /// It then calls the [`into_wrap_iter`] method. Hence, the return
671 /// value is an [`IntoWrapIter`], not a [`WrapIter`] as the function
672 /// name would otherwise suggest.
673 ///
674 /// If you need to set a language corpus for automatic hyphenation, or
675 /// need to wrap many strings, then it is suggested to create a Wrapper
676 /// and call its [`wrap_iter`] or [`into_wrap_iter`] methods.
677 ///
678 /// # Examples
679 ///
680 /// ```
681 /// use std::borrow::Cow;
682 /// use textwrap::wrap_iter;
683 ///
684 /// let mut wrap20_iter = wrap_iter("Zero-cost abstractions.", 20);
685 /// assert_eq!(wrap20_iter.next(), Some(Cow::from("Zero-cost")));
686 /// assert_eq!(wrap20_iter.next(), Some(Cow::from("abstractions.")));
687 /// assert_eq!(wrap20_iter.next(), None);
688 ///
689 /// let mut wrap25_iter = wrap_iter("Zero-cost abstractions.", 25);
690 /// assert_eq!(wrap25_iter.next(), Some(Cow::from("Zero-cost abstractions.")));
691 /// assert_eq!(wrap25_iter.next(), None);
692 /// ```
693 ///
694 /// [`wrap_iter`]: struct.Wrapper.html#method.wrap_iter
695 /// [`into_wrap_iter`]: struct.Wrapper.html#method.into_wrap_iter
696 /// [`IntoWrapIter`]: struct.IntoWrapIter.html
697 /// [`WrapIter`]: struct.WrapIter.html
wrap_iter(s: &str, width: usize) -> IntoWrapIter<HyphenSplitter>698 pub fn wrap_iter(s: &str, width: usize) -> IntoWrapIter<HyphenSplitter> {
699 Wrapper::new(width).into_wrap_iter(s)
700 }
701
702 #[cfg(test)]
703 mod tests {
704 #[cfg(feature = "hyphenation")]
705 extern crate hyphenation;
706
707 use super::*;
708 #[cfg(feature = "hyphenation")]
709 use hyphenation::{Language, Load, Standard};
710
711 #[test]
no_wrap()712 fn no_wrap() {
713 assert_eq!(wrap("foo", 10), vec!["foo"]);
714 }
715
716 #[test]
simple()717 fn simple() {
718 assert_eq!(wrap("foo bar baz", 5), vec!["foo", "bar", "baz"]);
719 }
720
721 #[test]
multi_word_on_line()722 fn multi_word_on_line() {
723 assert_eq!(wrap("foo bar baz", 10), vec!["foo bar", "baz"]);
724 }
725
726 #[test]
long_word()727 fn long_word() {
728 assert_eq!(wrap("foo", 0), vec!["f", "o", "o"]);
729 }
730
731 #[test]
long_words()732 fn long_words() {
733 assert_eq!(wrap("foo bar", 0), vec!["f", "o", "o", "b", "a", "r"]);
734 }
735
736 #[test]
max_width()737 fn max_width() {
738 assert_eq!(wrap("foo bar", usize::max_value()), vec!["foo bar"]);
739 }
740
741 #[test]
leading_whitespace()742 fn leading_whitespace() {
743 assert_eq!(wrap(" foo bar", 6), vec![" foo", "bar"]);
744 }
745
746 #[test]
trailing_whitespace()747 fn trailing_whitespace() {
748 assert_eq!(wrap("foo bar ", 6), vec!["foo", "bar "]);
749 }
750
751 #[test]
interior_whitespace()752 fn interior_whitespace() {
753 assert_eq!(wrap("foo: bar baz", 10), vec!["foo: bar", "baz"]);
754 }
755
756 #[test]
extra_whitespace_start_of_line()757 fn extra_whitespace_start_of_line() {
758 // Whitespace is only significant inside a line. After a line
759 // gets too long and is broken, the first word starts in
760 // column zero and is not indented. The line before might end
761 // up with trailing whitespace.
762 assert_eq!(wrap("foo bar", 5), vec!["foo", "bar"]);
763 }
764
765 #[test]
issue_99()766 fn issue_99() {
767 // We did not reset the in_whitespace flag correctly and did
768 // not handle single-character words after a line break.
769 assert_eq!(
770 wrap("aaabbbccc x yyyzzzwww", 9),
771 vec!["aaabbbccc", "x", "yyyzzzwww"]
772 );
773 }
774
775 #[test]
issue_129()776 fn issue_129() {
777 // The dash is an em-dash which takes up four bytes. We used
778 // to panic since we tried to index into the character.
779 assert_eq!(wrap("x – x", 1), vec!["x", "–", "x"]);
780 }
781
782 #[test]
wide_character_handling()783 fn wide_character_handling() {
784 assert_eq!(wrap("Hello, World!", 15), vec!["Hello, World!"]);
785 assert_eq!(
786 wrap("Hello, World!", 15),
787 vec!["Hello,", "World!"]
788 );
789 }
790
791 #[test]
empty_input_not_indented()792 fn empty_input_not_indented() {
793 let wrapper = Wrapper::new(10).initial_indent("!!!");
794 assert_eq!(wrapper.fill(""), "");
795 }
796
797 #[test]
indent_single_line()798 fn indent_single_line() {
799 let wrapper = Wrapper::new(10).initial_indent(">>>"); // No trailing space
800 assert_eq!(wrapper.fill("foo"), ">>>foo");
801 }
802
803 #[test]
indent_multiple_lines()804 fn indent_multiple_lines() {
805 let wrapper = Wrapper::new(6).initial_indent("* ").subsequent_indent(" ");
806 assert_eq!(wrapper.wrap("foo bar baz"), vec!["* foo", " bar", " baz"]);
807 }
808
809 #[test]
indent_break_words()810 fn indent_break_words() {
811 let wrapper = Wrapper::new(5).initial_indent("* ").subsequent_indent(" ");
812 assert_eq!(wrapper.wrap("foobarbaz"), vec!["* foo", " bar", " baz"]);
813 }
814
815 #[test]
hyphens()816 fn hyphens() {
817 assert_eq!(wrap("foo-bar", 5), vec!["foo-", "bar"]);
818 }
819
820 #[test]
trailing_hyphen()821 fn trailing_hyphen() {
822 let wrapper = Wrapper::new(5).break_words(false);
823 assert_eq!(wrapper.wrap("foobar-"), vec!["foobar-"]);
824 }
825
826 #[test]
multiple_hyphens()827 fn multiple_hyphens() {
828 assert_eq!(wrap("foo-bar-baz", 5), vec!["foo-", "bar-", "baz"]);
829 }
830
831 #[test]
hyphens_flag()832 fn hyphens_flag() {
833 let wrapper = Wrapper::new(5).break_words(false);
834 assert_eq!(
835 wrapper.wrap("The --foo-bar flag."),
836 vec!["The", "--foo-", "bar", "flag."]
837 );
838 }
839
840 #[test]
repeated_hyphens()841 fn repeated_hyphens() {
842 let wrapper = Wrapper::new(4).break_words(false);
843 assert_eq!(wrapper.wrap("foo--bar"), vec!["foo--bar"]);
844 }
845
846 #[test]
hyphens_alphanumeric()847 fn hyphens_alphanumeric() {
848 assert_eq!(wrap("Na2-CH4", 5), vec!["Na2-", "CH4"]);
849 }
850
851 #[test]
hyphens_non_alphanumeric()852 fn hyphens_non_alphanumeric() {
853 let wrapper = Wrapper::new(5).break_words(false);
854 assert_eq!(wrapper.wrap("foo(-)bar"), vec!["foo(-)bar"]);
855 }
856
857 #[test]
multiple_splits()858 fn multiple_splits() {
859 assert_eq!(wrap("foo-bar-baz", 9), vec!["foo-bar-", "baz"]);
860 }
861
862 #[test]
forced_split()863 fn forced_split() {
864 let wrapper = Wrapper::new(5).break_words(false);
865 assert_eq!(wrapper.wrap("foobar-baz"), vec!["foobar-", "baz"]);
866 }
867
868 #[test]
no_hyphenation()869 fn no_hyphenation() {
870 let wrapper = Wrapper::with_splitter(8, NoHyphenation);
871 assert_eq!(wrapper.wrap("foo bar-baz"), vec!["foo", "bar-baz"]);
872 }
873
874 #[test]
875 #[cfg(feature = "hyphenation")]
auto_hyphenation()876 fn auto_hyphenation() {
877 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
878 let wrapper = Wrapper::new(10);
879 assert_eq!(
880 wrapper.wrap("Internationalization"),
881 vec!["Internatio", "nalization"]
882 );
883
884 let wrapper = Wrapper::with_splitter(10, dictionary);
885 assert_eq!(
886 wrapper.wrap("Internationalization"),
887 vec!["Interna-", "tionaliza-", "tion"]
888 );
889 }
890
891 #[test]
892 #[cfg(feature = "hyphenation")]
split_len_hyphenation()893 fn split_len_hyphenation() {
894 // Test that hyphenation takes the width of the wihtespace
895 // into account.
896 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
897 let wrapper = Wrapper::with_splitter(15, dictionary);
898 assert_eq!(
899 wrapper.wrap("garbage collection"),
900 vec!["garbage col-", "lection"]
901 );
902 }
903
904 #[test]
905 #[cfg(feature = "hyphenation")]
borrowed_lines()906 fn borrowed_lines() {
907 // Lines that end with an extra hyphen are owned, the final
908 // line is borrowed.
909 use std::borrow::Cow::{Borrowed, Owned};
910 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
911 let wrapper = Wrapper::with_splitter(10, dictionary);
912 let lines = wrapper.wrap("Internationalization");
913 if let Borrowed(s) = lines[0] {
914 assert!(false, "should not have been borrowed: {:?}", s);
915 }
916 if let Borrowed(s) = lines[1] {
917 assert!(false, "should not have been borrowed: {:?}", s);
918 }
919 if let Owned(ref s) = lines[2] {
920 assert!(false, "should not have been owned: {:?}", s);
921 }
922 }
923
924 #[test]
925 #[cfg(feature = "hyphenation")]
auto_hyphenation_with_hyphen()926 fn auto_hyphenation_with_hyphen() {
927 let dictionary = Standard::from_embedded(Language::EnglishUS).unwrap();
928 let wrapper = Wrapper::new(8).break_words(false);
929 assert_eq!(wrapper.wrap("over-caffinated"), vec!["over-", "caffinated"]);
930
931 let wrapper = Wrapper::with_splitter(8, dictionary).break_words(false);
932 assert_eq!(
933 wrapper.wrap("over-caffinated"),
934 vec!["over-", "caffi-", "nated"]
935 );
936 }
937
938 #[test]
break_words()939 fn break_words() {
940 assert_eq!(wrap("foobarbaz", 3), vec!["foo", "bar", "baz"]);
941 }
942
943 #[test]
break_words_wide_characters()944 fn break_words_wide_characters() {
945 assert_eq!(wrap("Hello", 5), vec!["He", "ll", "o"]);
946 }
947
948 #[test]
break_words_zero_width()949 fn break_words_zero_width() {
950 assert_eq!(wrap("foobar", 0), vec!["f", "o", "o", "b", "a", "r"]);
951 }
952
953 #[test]
break_words_line_breaks()954 fn break_words_line_breaks() {
955 assert_eq!(fill("ab\ncdefghijkl", 5), "ab\ncdefg\nhijkl");
956 assert_eq!(fill("abcdefgh\nijkl", 5), "abcde\nfgh\nijkl");
957 }
958
959 #[test]
preserve_line_breaks()960 fn preserve_line_breaks() {
961 assert_eq!(fill("test\n", 11), "test\n");
962 assert_eq!(fill("test\n\na\n\n", 11), "test\n\na\n\n");
963 assert_eq!(fill("1 3 5 7\n1 3 5 7", 7), "1 3 5 7\n1 3 5 7");
964 }
965
966 #[test]
wrap_preserve_line_breaks()967 fn wrap_preserve_line_breaks() {
968 assert_eq!(fill("1 3 5 7\n1 3 5 7", 5), "1 3 5\n7\n1 3 5\n7");
969 }
970
971 #[test]
non_breaking_space()972 fn non_breaking_space() {
973 let wrapper = Wrapper::new(5).break_words(false);
974 assert_eq!(wrapper.fill("foo bar baz"), "foo bar baz");
975 }
976
977 #[test]
non_breaking_hyphen()978 fn non_breaking_hyphen() {
979 let wrapper = Wrapper::new(5).break_words(false);
980 assert_eq!(wrapper.fill("foo‑bar‑baz"), "foo‑bar‑baz");
981 }
982
983 #[test]
fill_simple()984 fn fill_simple() {
985 assert_eq!(fill("foo bar baz", 10), "foo bar\nbaz");
986 }
987 }
988