about summary refs log tree commit diff stats
path: root/crates/fmt/src/fmt.rs
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-02-16 18:06:15 +0100
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2025-02-16 18:08:10 +0100
commit7cfa6939deb5496a07313a2a34632da1a3fb1b89 (patch)
tree2eb43066ea6e8d14079be1da10f9c5856282e676 /crates/fmt/src/fmt.rs
parentchore(crates/termsize): Vendor (diff)
downloadyt-7cfa6939deb5496a07313a2a34632da1a3fb1b89.zip
refactor(crates/fmt): Init forked `uu_fmt` library
Diffstat (limited to 'crates/fmt/src/fmt.rs')
-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
+}