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}