1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
// yt - A fully featured command line YouTube client
//
// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// Copyright (C) 2025 uutils developers
// SPDX-License-Identifier: MIT
//
// This file is part of Yt.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use std::fmt::Write;
use linebreak::break_lines;
use parasplit::ParagraphStream;
mod linebreak;
mod parasplit;
#[derive(Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct FmtOptions {
/// First and second line of paragraph
/// may have different indentations, in which
/// case the first line's indentation is preserved,
/// and each subsequent line's indentation matches the second line.
pub crown_margin: bool,
/// Like the [`crown_margin`], except that the first and second line of a paragraph *must*
/// have different indentation or they are treated as separate paragraphs.
pub tagged_paragraph: bool,
/// Attempt to detect and preserve mail headers in the input.
/// Be careful when combining this with [`prefix`].
pub mail: bool,
/// Split lines only, do not reflow.
pub split_only: bool,
/// Insert exactly one space between words, and two between sentences.
/// Sentence breaks in the input are detected as [?!.] followed by two spaces or a newline;
/// other punctuation is not interpreted as a sentence break.
pub uniform: bool,
/// Reformat only lines beginning with PREFIX, reattaching PREFIX to reformatted lines.
/// Unless [`exact_prefix`] is specified, leading whitespace will be ignored when matching PREFIX.
pub prefix: Option<String>,
/// Do not reformat lines beginning with ``ANTI_PREFIX``.
/// Unless [`exact_anti_prefix`] is specified, leading whitespace will be ignored when matching ``ANTI_PREFIX``.
pub anti_prefix: Option<String>,
/// [`prefix`] must match at the beginning of the line with no preceding whitespace.
pub exact_prefix: bool,
/// [`anti_prefix`] must match at the beginning of the line with no preceding whitespace.
pub exact_anti_prefix: bool,
/// Fill output lines up to a maximum of WIDTH columns, default 75.
pub width: usize,
/// Goal width, default of 93% of WIDTH.
/// Must be less than or equal to WIDTH.
pub goal: usize,
/// Break lines more quickly at the expense of a potentially more ragged appearance.
pub quick: bool,
/// Treat tabs as TABWIDTH spaces for determining line length, default 8.
/// Note that this is used only for calculating line lengths; tabs are preserved in the output.
pub tabwidth: usize,
}
impl FmtOptions {
#[must_use]
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_precision_loss)]
pub fn new(width: Option<usize>, goal: Option<usize>, tabwidth: Option<usize>) -> Self {
// by default, goal is 93% of width
const DEFAULT_GOAL_TO_WIDTH_RATIO: f64 = 0.93;
const DEFAULT_WIDTH: usize = 75;
FmtOptions {
crown_margin: false,
tagged_paragraph: false,
mail: false,
split_only: false,
uniform: false,
prefix: None,
anti_prefix: None,
exact_prefix: false,
exact_anti_prefix: false,
width: width.unwrap_or(DEFAULT_WIDTH),
goal: goal.unwrap_or(
((width.unwrap_or(DEFAULT_WIDTH) as f64) * DEFAULT_GOAL_TO_WIDTH_RATIO).floor()
as usize,
),
quick: false,
tabwidth: tabwidth.unwrap_or(8),
}
}
}
/// Process text and format it according to the provided options.
///
/// # Arguments
///
/// * `text` - The text to process.
/// * `fmt_opts` - A reference to a [`FmtOptions`] structure containing the formatting options.
///
/// # Returns
///
/// The formatted [`String`].
#[must_use]
pub fn process_text(text: &str, fmt_opts: &FmtOptions) -> String {
let mut output = String::new();
let p_stream = ParagraphStream::new(fmt_opts, text);
for para_result in p_stream {
match para_result {
Err(s) => {
output.push_str(&s);
output.push('\n');
}
Ok(para) => write!(output, "{}", break_lines(¶, fmt_opts))
.expect("This is in-memory. It should not fail"),
}
}
output
}
|