aboutsummaryrefslogtreecommitdiffstats
path: root/crates/fmt/src/fmt.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--crates/fmt/src/fmt.rs137
1 files changed, 137 insertions, 0 deletions
diff --git a/crates/fmt/src/fmt.rs b/crates/fmt/src/fmt.rs
new file mode 100644
index 0000000..3067bea
--- /dev/null
+++ b/crates/fmt/src/fmt.rs
@@ -0,0 +1,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(&para, fmt_opts))
+ .expect("This is in-memory. It should not fail"),
+ }
+ }
+
+ output
+}