textwrap/
options.rs

1//! Options for wrapping text.
2
3use crate::{LineEnding, WordSeparator, WordSplitter, WrapAlgorithm};
4
5/// Holds configuration options for wrapping and filling text.
6#[non_exhaustive]
7#[derive(Debug, Clone)]
8pub struct Options<'a> {
9    /// The width in columns at which the text will be wrapped.
10    pub width: usize,
11    /// Line ending used for breaking lines.
12    pub line_ending: LineEnding,
13    /// Indentation used for the first line of output. See the
14    /// [`Options::initial_indent`] method.
15    pub initial_indent: &'a str,
16    /// Indentation used for subsequent lines of output. See the
17    /// [`Options::subsequent_indent`] method.
18    pub subsequent_indent: &'a str,
19    /// Allow long words to be broken if they cannot fit on a line.
20    /// When set to `false`, some lines may be longer than
21    /// `self.width`. See the [`Options::break_words`] method.
22    pub break_words: bool,
23    /// Wrapping algorithm to use, see the implementations of the
24    /// [`WrapAlgorithm`] trait for details.
25    pub wrap_algorithm: WrapAlgorithm,
26    /// The line breaking algorithm to use, see the [`WordSeparator`]
27    /// trait for an overview and possible implementations.
28    pub word_separator: WordSeparator,
29    /// The method for splitting words. This can be used to prohibit
30    /// splitting words on hyphens, or it can be used to implement
31    /// language-aware machine hyphenation.
32    pub word_splitter: WordSplitter,
33}
34
35impl<'a> From<&'a Options<'a>> for Options<'a> {
36    fn from(options: &'a Options<'a>) -> Self {
37        Self {
38            width: options.width,
39            line_ending: options.line_ending,
40            initial_indent: options.initial_indent,
41            subsequent_indent: options.subsequent_indent,
42            break_words: options.break_words,
43            word_separator: options.word_separator,
44            wrap_algorithm: options.wrap_algorithm,
45            word_splitter: options.word_splitter.clone(),
46        }
47    }
48}
49
50impl From<usize> for Options<'_> {
51    fn from(width: usize) -> Self {
52        Options::new(width)
53    }
54}
55
56impl<'a> Options<'a> {
57    /// Creates a new [`Options`] with the specified width.
58    ///
59    /// The other fields are given default values as follows:
60    ///
61    /// ```
62    /// # use textwrap::{LineEnding, Options, WordSplitter, WordSeparator, WrapAlgorithm};
63    /// # let width = 80;
64    /// let options = Options::new(width);
65    /// assert_eq!(options.line_ending, LineEnding::LF);
66    /// assert_eq!(options.initial_indent, "");
67    /// assert_eq!(options.subsequent_indent, "");
68    /// assert_eq!(options.break_words, true);
69    ///
70    /// #[cfg(feature = "unicode-linebreak")]
71    /// assert_eq!(options.word_separator, WordSeparator::UnicodeBreakProperties);
72    /// #[cfg(not(feature = "unicode-linebreak"))]
73    /// assert_eq!(options.word_separator, WordSeparator::AsciiSpace);
74    ///
75    /// #[cfg(feature = "smawk")]
76    /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::new_optimal_fit());
77    /// #[cfg(not(feature = "smawk"))]
78    /// assert_eq!(options.wrap_algorithm, WrapAlgorithm::FirstFit);
79    ///
80    /// assert_eq!(options.word_splitter, WordSplitter::HyphenSplitter);
81    /// ```
82    ///
83    /// Note that the default word separator and wrap algorithms
84    /// changes based on the available Cargo features. The best
85    /// available algorithms are used by default.
86    pub const fn new(width: usize) -> Self {
87        Options {
88            width,
89            line_ending: LineEnding::LF,
90            initial_indent: "",
91            subsequent_indent: "",
92            break_words: true,
93            word_separator: WordSeparator::new(),
94            wrap_algorithm: WrapAlgorithm::new(),
95            word_splitter: WordSplitter::HyphenSplitter,
96        }
97    }
98
99    /// Change [`self.line_ending`]. This specifies which of the
100    /// supported line endings should be used to break the lines of the
101    /// input text.
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use textwrap::{refill, LineEnding, Options};
107    ///
108    /// let options = Options::new(15).line_ending(LineEnding::CRLF);
109    /// assert_eq!(refill("This is a little example.", options),
110    ///            "This is a\r\nlittle example.");
111    /// ```
112    ///
113    /// [`self.line_ending`]: #structfield.line_ending
114    pub fn line_ending(self, line_ending: LineEnding) -> Self {
115        Options {
116            line_ending,
117            ..self
118        }
119    }
120
121    /// Set [`self.width`] to the given value.
122    ///
123    /// [`self.width`]: #structfield.width
124    pub fn width(self, width: usize) -> Self {
125        Options { width, ..self }
126    }
127
128    /// Change [`self.initial_indent`]. The initial indentation is
129    /// used on the very first line of output.
130    ///
131    /// # Examples
132    ///
133    /// Classic paragraph indentation can be achieved by specifying an
134    /// initial indentation and wrapping each paragraph by itself:
135    ///
136    /// ```
137    /// use textwrap::{wrap, Options};
138    ///
139    /// let options = Options::new(16).initial_indent("    ");
140    /// assert_eq!(wrap("This is a little example.", options),
141    ///            vec!["    This is a",
142    ///                 "little example."]);
143    /// ```
144    ///
145    /// [`self.initial_indent`]: #structfield.initial_indent
146    pub fn initial_indent(self, initial_indent: &'a str) -> Self {
147        Options {
148            initial_indent,
149            ..self
150        }
151    }
152
153    /// Change [`self.subsequent_indent`]. The subsequent indentation
154    /// is used on lines following the first line of output.
155    ///
156    /// # Examples
157    ///
158    /// Combining initial and subsequent indentation lets you format a
159    /// single paragraph as a bullet list:
160    ///
161    /// ```
162    /// use textwrap::{wrap, Options};
163    ///
164    /// let options = Options::new(12)
165    ///     .initial_indent("* ")
166    ///     .subsequent_indent("  ");
167    /// #[cfg(feature = "smawk")]
168    /// assert_eq!(wrap("This is a little example.", options),
169    ///            vec!["* This is",
170    ///                 "  a little",
171    ///                 "  example."]);
172    ///
173    /// // Without the `smawk` feature, the wrapping is a little different:
174    /// #[cfg(not(feature = "smawk"))]
175    /// assert_eq!(wrap("This is a little example.", options),
176    ///            vec!["* This is a",
177    ///                 "  little",
178    ///                 "  example."]);
179    /// ```
180    ///
181    /// [`self.subsequent_indent`]: #structfield.subsequent_indent
182    pub fn subsequent_indent(self, subsequent_indent: &'a str) -> Self {
183        Options {
184            subsequent_indent,
185            ..self
186        }
187    }
188
189    /// Change [`self.break_words`]. This controls if words longer
190    /// than `self.width` can be broken, or if they will be left
191    /// sticking out into the right margin.
192    ///
193    /// See [`Options::word_splitter`] instead if you want to control
194    /// hyphenation.
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use textwrap::{wrap, Options};
200    ///
201    /// let options = Options::new(4).break_words(true);
202    /// assert_eq!(wrap("This is a little example.", options),
203    ///            vec!["This",
204    ///                 "is a",
205    ///                 "litt",
206    ///                 "le",
207    ///                 "exam",
208    ///                 "ple."]);
209    /// ```
210    ///
211    /// [`self.break_words`]: #structfield.break_words
212    pub fn break_words(self, break_words: bool) -> Self {
213        Options {
214            break_words,
215            ..self
216        }
217    }
218
219    /// Change [`self.word_separator`].
220    ///
221    /// See the [`WordSeparator`] trait for details on the choices.
222    ///
223    /// [`self.word_separator`]: #structfield.word_separator
224    pub fn word_separator(self, word_separator: WordSeparator) -> Options<'a> {
225        Options {
226            word_separator,
227            ..self
228        }
229    }
230
231    /// Change [`self.wrap_algorithm`].
232    ///
233    /// See the [`WrapAlgorithm`] trait for details on the choices.
234    ///
235    /// [`self.wrap_algorithm`]: #structfield.wrap_algorithm
236    pub fn wrap_algorithm(self, wrap_algorithm: WrapAlgorithm) -> Options<'a> {
237        Options {
238            wrap_algorithm,
239            ..self
240        }
241    }
242
243    /// Change [`self.word_splitter`]. The [`WordSplitter`] is used to
244    /// fit part of a word into the current line when wrapping text.
245    ///
246    /// See [`Options::break_words`] instead if you want to control the
247    /// handling of words longer than the line width.
248    ///
249    /// # Examples
250    ///
251    /// ```
252    /// use textwrap::{wrap, Options, WordSplitter};
253    ///
254    /// // The default is WordSplitter::HyphenSplitter.
255    /// let options = Options::new(5);
256    /// assert_eq!(wrap("foo-bar-baz", &options),
257    ///            vec!["foo-", "bar-", "baz"]);
258    ///
259    /// // The word is now so long that break_words kick in:
260    /// let options = Options::new(5)
261    ///     .word_splitter(WordSplitter::NoHyphenation);
262    /// assert_eq!(wrap("foo-bar-baz", &options),
263    ///            vec!["foo-b", "ar-ba", "z"]);
264    ///
265    /// // If you want to breaks at all, disable both:
266    /// let options = Options::new(5)
267    ///     .break_words(false)
268    ///     .word_splitter(WordSplitter::NoHyphenation);
269    /// assert_eq!(wrap("foo-bar-baz", &options),
270    ///            vec!["foo-bar-baz"]);
271    /// ```
272    ///
273    /// [`self.word_splitter`]: #structfield.word_splitter
274    pub fn word_splitter(self, word_splitter: WordSplitter) -> Options<'a> {
275        Options {
276            word_splitter,
277            ..self
278        }
279    }
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn options_agree_with_usize() {
288        let opt_usize = Options::from(42_usize);
289        let opt_options = Options::new(42);
290
291        assert_eq!(opt_usize.width, opt_options.width);
292        assert_eq!(opt_usize.initial_indent, opt_options.initial_indent);
293        assert_eq!(opt_usize.subsequent_indent, opt_options.subsequent_indent);
294        assert_eq!(opt_usize.break_words, opt_options.break_words);
295        assert_eq!(
296            opt_usize.word_splitter.split_points("hello-world"),
297            opt_options.word_splitter.split_points("hello-world")
298        );
299    }
300}