From f54d3880fffc28b1415258fcf8252b96185ec2b5 Mon Sep 17 00:00:00 2001 From: Forge Recovery Date: Sun, 1 Nov 2026 19:42:19 +0000 Subject: [PATCH 1/2] fix(ci): repair fmt, clippy and flower box rendering for central CI Co-Authored-By: Claude Sonnet 5.5 Co-Authored-By: Codex --- .gitignore | 1 - .rustfmt.toml | 67 ++------ Cargo.lock | 344 ++++++++++++++++++++++++++++++++++++++++ clippy.toml | 64 ++++---- rustfmt.toml | 67 ++------ src/box_renderer.rs | 153 ++++++------------ src/cli.rs | 3 +- src/comment_detector.rs | 136 ++++++++-------- src/config.rs | 59 ++++--- src/main.rs | 27 ++-- src/text_wrapper.rs | 111 +++++-------- src/verbose.rs | 31 ++-- 12 files changed, 603 insertions(+), 460 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 7a768b9..af0a413 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ # Rust /target/ **/*.rs.bk -Cargo.lock # IDE .vscode/ diff --git a/.rustfmt.toml b/.rustfmt.toml index e170315..49e1591 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,58 +1,13 @@ -# Rustfmt configuration file -# Based on your .clang-format preferences -# Configured for nightly Rust with Allman brace style - -# Indentation - matches your IndentWidth: 3, TabWidth: 3, UseTab: Never -tab_spaces = 3 - -# Line length - matches your ColumnLimit: 100 -max_width = 100 - -# Edition +# KofTwentyTwo rustfmt configuration (K22-CODE-RS). Stable-channel options only, so +# `cargo fmt --check` behaves the same on every toolchain. edition = "2021" - -# Brace style - matches your BreakBeforeBraces: Allman -brace_style = "AlwaysNextLine" # Opening braces on new lines -control_brace_style = "AlwaysNextLine" # Control structures with braces on new lines - -# Comments - matches your AlignTrailingComments: true -wrap_comments = true -comment_width = 100 -normalize_comments = false # ReflowComments: false - -# Imports and modules -imports_granularity = "Crate" -imports_layout = "Mixed" -group_imports = "StdExternalCrate" - -# Functions and expressions -fn_single_line = false # AllowShortFunctionsOnASingleLine: None -where_single_line = false - -# Structs and enums -struct_field_align_threshold = 0 -enum_discrim_align_threshold = 0 - -# Spacing -space_before_colon = false -space_after_colon = true -spaces_around_ranges = false - -# Trailing commas -trailing_comma = "Vertical" - -# Empty lines -empty_item_single_line = true -# max_empty_lines_to_keep = 3 # Not available in current nightly - -# String formatting -format_strings = true -format_code_in_doc_comments = true - -# Misc -remove_nested_parens = true -use_try_shorthand = true +# Kingsrook: 3-space indentation (tab_spaces is a stable option). +tab_spaces = 3 +hard_tabs = false +newline_style = "Unix" +# Kingsrook: no practical line limit. 200 keeps rustfmt's wrapping heuristics sane. +max_width = 200 use_field_init_shorthand = true -force_explicit_abi = true - -# This configuration enforces your C++ Allman brace style in Rust \ No newline at end of file +use_try_shorthand = true +# Not set: brace_style = "AlwaysNextLine" and blank_lines_upper_bound are nightly-only. +# Rust therefore keeps rustfmt's same-line braces (profile rule K22-CODE-RS-02). diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5d44418 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,344 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "kfmtbox" +version = "0.1.0" +dependencies = [ + "clap", + "dirs", + "serde", + "serde_yaml", +] + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_users" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" +dependencies = [ + "libredox", + "thiserror", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/clippy.toml b/clippy.toml index 02c9a8c..605f8eb 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,32 +1,34 @@ -# Clippy configuration file -# This file should be in the project root - -# Allow explicit return statements -# Note: needless-return is not a valid clippy.toml option -# Instead, we use the Cargo.toml configuration - -# Other valid clippy.toml options: -cognitive-complexity-threshold = 10 -too-many-arguments-threshold = 7 -too-many-lines-threshold = 100 -type-complexity-threshold = 200 -large-error-threshold = 128 -stack-size-threshold = 256 -enum-variant-size-threshold = 200 -array-size-threshold = 512 -max-fn-params-bools = 3 -max-struct-bools = 3 -max-trait-bounds = 3 -literal-representation-threshold = 10 -min-ident-chars-threshold = 3 -single-char-binding-names-threshold = 2 -missing-docs-in-crate-items = true -check-private-items = true -arithmetic-side-effects-allowed = [] -allow-unwrap-in-tests = false -allow-expect-in-tests = false -allow-panic-in-tests = false -allow-dbg-in-tests = false -allow-print-in-tests = false -semicolon-inside-block-ignore-singleline = false +# Clippy configuration file +# This file should be in the project root + +# Allow explicit return statements +# Note: needless-return is not a valid clippy.toml option +# Instead, we use the Cargo.toml configuration + +# Other valid clippy.toml options: +cognitive-complexity-threshold = 10 +too-many-arguments-threshold = 7 +too-many-lines-threshold = 100 +type-complexity-threshold = 200 +large-error-threshold = 128 +# Bytes (clippy default 512000). 256 cannot be met by ordinary frames: measured debug-build frames are 297-1633 bytes +# (largest: render_flower_box at 1633, extract_content_from_box at 1551). 2048 keeps a gate just above them. +stack-size-threshold = 2048 +enum-variant-size-threshold = 200 +array-size-threshold = 512 +max-fn-params-bools = 3 +max-struct-bools = 3 +max-trait-bounds = 3 +literal-representation-threshold = 10 +min-ident-chars-threshold = 3 +single-char-binding-names-threshold = 2 +missing-docs-in-crate-items = true +check-private-items = true +arithmetic-side-effects-allowed = [] +allow-unwrap-in-tests = false +allow-expect-in-tests = false +allow-panic-in-tests = false +allow-dbg-in-tests = false +allow-print-in-tests = false +semicolon-inside-block-ignore-singleline = false semicolon-outside-block-ignore-multiline = false \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index e170315..49e1591 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,58 +1,13 @@ -# Rustfmt configuration file -# Based on your .clang-format preferences -# Configured for nightly Rust with Allman brace style - -# Indentation - matches your IndentWidth: 3, TabWidth: 3, UseTab: Never -tab_spaces = 3 - -# Line length - matches your ColumnLimit: 100 -max_width = 100 - -# Edition +# KofTwentyTwo rustfmt configuration (K22-CODE-RS). Stable-channel options only, so +# `cargo fmt --check` behaves the same on every toolchain. edition = "2021" - -# Brace style - matches your BreakBeforeBraces: Allman -brace_style = "AlwaysNextLine" # Opening braces on new lines -control_brace_style = "AlwaysNextLine" # Control structures with braces on new lines - -# Comments - matches your AlignTrailingComments: true -wrap_comments = true -comment_width = 100 -normalize_comments = false # ReflowComments: false - -# Imports and modules -imports_granularity = "Crate" -imports_layout = "Mixed" -group_imports = "StdExternalCrate" - -# Functions and expressions -fn_single_line = false # AllowShortFunctionsOnASingleLine: None -where_single_line = false - -# Structs and enums -struct_field_align_threshold = 0 -enum_discrim_align_threshold = 0 - -# Spacing -space_before_colon = false -space_after_colon = true -spaces_around_ranges = false - -# Trailing commas -trailing_comma = "Vertical" - -# Empty lines -empty_item_single_line = true -# max_empty_lines_to_keep = 3 # Not available in current nightly - -# String formatting -format_strings = true -format_code_in_doc_comments = true - -# Misc -remove_nested_parens = true -use_try_shorthand = true +# Kingsrook: 3-space indentation (tab_spaces is a stable option). +tab_spaces = 3 +hard_tabs = false +newline_style = "Unix" +# Kingsrook: no practical line limit. 200 keeps rustfmt's wrapping heuristics sane. +max_width = 200 use_field_init_shorthand = true -force_explicit_abi = true - -# This configuration enforces your C++ Allman brace style in Rust \ No newline at end of file +use_try_shorthand = true +# Not set: brace_style = "AlwaysNextLine" and blank_lines_upper_bound are nightly-only. +# Rust therefore keeps rustfmt's same-line braces (profile rule K22-CODE-RS-02). diff --git a/src/box_renderer.rs b/src/box_renderer.rs index 8e77051..03cc2a3 100644 --- a/src/box_renderer.rs +++ b/src/box_renderer.rs @@ -1,17 +1,13 @@ - use crate::{comment_detector::CommentStyle, text_wrapper::TextWrapper}; -pub struct BoxRenderer -{ +pub struct BoxRenderer { comment_style: CommentStyle, text_wrapper: TextWrapper, } -impl BoxRenderer -{ +impl BoxRenderer { #[must_use] - pub fn new(comment_style: CommentStyle, max_width: usize) -> Self - { + pub const fn new(comment_style: CommentStyle, max_width: usize) -> Self { #[allow(clippy::needless_return)] return Self { comment_style, @@ -20,8 +16,7 @@ impl BoxRenderer } #[must_use] - pub fn render_flower_box(&self, content: &str) -> String - { + pub fn render_flower_box(&self, content: &str) -> String { // Check if content is already in a flower box let clean_content = self.extract_content_from_box(content); @@ -29,130 +24,82 @@ impl BoxRenderer let wrapped_lines = self.text_wrapper.wrap_text(&clean_content); let balanced_lines = self.text_wrapper.balance_lines(wrapped_lines); - // Find the maximum line length for box width - let max_line_len = balanced_lines - .iter() - .map(|line| line.len()) - .max() - .unwrap_or(0); + // Find the widest line, in characters, for the box width + let max_line_len = balanced_lines.iter().map(|line| return line.chars().count()).max().unwrap_or(0); - let box_width = (max_line_len + 4).max(10); // Minimum width of 10 + // Inner width: one space either side of the text, minimum of 10 + let inner_width = max_line_len.saturating_add(2).max(10); let prefix = &self.comment_style.prefix; + let border = "─".repeat(inner_width); // Build the flower box let mut result = Vec::new(); // Top border - result.push(format!( - "{prefix} {}", - "/".repeat(box_width) - )); + result.push(format!("{prefix} ╭{border}╮")); // Content lines - for line in balanced_lines - { - let padding = box_width - line.len() - 2; - let padded_line = format!( - "{prefix} {line}{} {prefix}", - " ".repeat(padding) - ); - result.push(padded_line); + for line in balanced_lines { + let padding = inner_width.saturating_sub(line.chars().count()).saturating_sub(2); + result.push(format!("{prefix} │ {line}{} │", " ".repeat(padding))); } // Bottom border - result.push(format!( - "{prefix} {}", - "/".repeat(box_width) - )); + result.push(format!("{prefix} ╰{border}╯")); - result.join("\n") + return result.join("\n"); } - /// Extracts content from an existing flower box comment. - /// - /// # Panics - /// - /// This function may panic if the input contains malformed flower box syntax - /// or if string slicing operations fail due to UTF-8 boundary issues. - fn extract_content_from_box(&self, content: &str) -> String - { + /// Extracts content from an existing flower box comment, or returns the input unchanged. + fn extract_content_from_box(&self, content: &str) -> String { let lines: Vec<&str> = content.lines().collect(); - if lines.len() < 3 - { + let (Some(first), Some(last)) = (lines.first(), lines.last()) else { + return content.to_string(); + }; + if lines.len() < 3 { return content.to_string(); } - let prefix = &self.comment_style.prefix; + // Check if this looks like a flower box + let (first_line, last_line) = (first.trim(), last.trim()); + if !(first_line.contains('╭') && first_line.contains('╮') && last_line.contains('╰') && last_line.contains('╯')) { + return content.to_string(); + } + + let prefix = self.comment_style.prefix.as_str(); let mut content_lines = Vec::new(); - // Check if this looks like a flower box - let first_line = lines[0].trim(); - let last_line = lines[lines.len() - 1].trim(); + // Extract content from the middle lines + for line in lines.iter().skip(1).take(lines.len().saturating_sub(2)) { + // Remove the comment prefix first + let clean_line = if line.trim_start().starts_with(prefix) { + line.split_once(prefix).map_or(*line, |(_, rest)| return rest) + } else { + line + }; - if first_line.contains("╭") - && first_line.contains("╮") - && last_line.contains("╰") - && last_line.contains("╯") - { - // Extract content from middle lines - for line in lines.iter().skip(1).take(lines.len() - 2) - { - // Remove the comment prefix first - let clean_line = if line.trim_start().starts_with(prefix) - { - &line[line.find(prefix).unwrap() + prefix.len()..] - } - else - { - line - }; - - // Find the content between the pipe characters using char-based approach - let chars: Vec = clean_line.chars().collect(); - let mut start_idx = None; - let mut end_idx = None; - - for (i, ch) in chars.iter().enumerate() - { - if *ch == '│' - { - if start_idx.is_none() - { - start_idx = Some(i + 1); - } - else - { - end_idx = Some(i); - break; - } - } - } - - if let (Some(start), Some(end)) = (start_idx, end_idx) - { - if end > start - { - let content_part: String = chars[start..end].iter().collect(); - content_lines.push(content_part.trim().to_string()); - } + // The content sits between the first two pipe characters + let mut parts = clean_line.split('│').skip(1); + if let (Some(inner), Some(_)) = (parts.next(), parts.next()) { + if !inner.is_empty() { + content_lines.push(inner.trim().to_string()); } } - return content_lines.join(" "); } - #[allow(clippy::needless_return)] - return content.to_string(); + return content_lines.join(" "); } } #[cfg(test)] -mod tests -{ +mod tests { use super::*; use crate::comment_detector::CommentStyle; + /// # Panics + /// + /// Panics if an assertion fails. #[test] - fn test_render_flower_box() - { + fn test_render_flower_box() { let comment_style = CommentStyle::new("//".to_string()); let renderer = BoxRenderer::new(comment_style, 80); let content = "Hello, world!"; @@ -165,9 +112,11 @@ mod tests assert!(result.contains("Hello, world!")); } + /// # Panics + /// + /// Panics if an assertion fails. #[test] - fn test_extract_content_from_box() - { + fn test_extract_content_from_box() { let comment_style = CommentStyle::new("//".to_string()); let renderer = BoxRenderer::new(comment_style, 80); diff --git a/src/cli.rs b/src/cli.rs index 392821d..9ab2692 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,8 +4,7 @@ use clap::Parser; #[command(name = "kfmtbox")] #[command(about = "A command-line utility that wraps text in flower box style comments")] #[command(version)] -pub struct Cli -{ +pub struct Cli { /// Input filename for file type detection #[arg(short = 'f', long = "filename")] pub filename: Option, diff --git a/src/comment_detector.rs b/src/comment_detector.rs index c27b4c1..04ce9dd 100644 --- a/src/comment_detector.rs +++ b/src/comment_detector.rs @@ -1,101 +1,93 @@ - use std::collections::HashMap; use crate::config::Config; #[derive(Debug, Clone)] -pub struct CommentStyle -{ +pub struct CommentStyle { pub prefix: String, pub is_block: bool, } -impl CommentStyle -{ - pub fn new(prefix: String) -> Self - { - return Self { - prefix, - is_block: false, - }; +impl CommentStyle { + #[must_use] + pub const fn new(prefix: String) -> Self { + return Self { prefix, is_block: false }; } } -pub struct CommentDetector -{ +/// Default comment prefixes for common file types. +const DEFAULT_STYLES: &[(&str, &str)] = &[ + ("rs", "//"), + ("py", "#"), + ("js", "//"), + ("ts", "//"), + ("go", "//"), + ("java", "//"), + ("cpp", "//"), + ("c", "//"), + ("h", "//"), + ("hpp", "//"), + ("cs", "//"), + ("php", "//"), + ("rb", "#"), + ("sh", "#"), + ("bash", "#"), + ("zsh", "#"), + ("fish", "#"), + ("vim", "\""), + ("lua", "--"), + ("sql", "--"), + ("yaml", "#"), + ("yml", "#"), + ("toml", "#"), + ("ini", ";"), + ("cfg", ";"), + ("conf", "#"), + ("dockerfile", "#"), + ("makefile", "#"), + ("cmake", "#"), +]; + +pub struct CommentDetector { pub default_style: CommentStyle, style_map: HashMap, } -impl CommentDetector -{ - pub fn new() -> Self - { - let mut style_map = HashMap::new(); +impl CommentDetector { + #[must_use] + pub fn new() -> Self { + let style_map = DEFAULT_STYLES + .iter() + .map(|&(filetype, prefix)| return (filetype.to_string(), CommentStyle::new(prefix.to_string()))) + .collect(); - // Default comment styles for common file types - style_map.insert("rs".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("py".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("js".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("ts".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("go".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("java".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("cpp".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("c".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("h".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("hpp".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("cs".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("php".to_string(), CommentStyle::new("//".to_string())); - style_map.insert("rb".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("sh".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("bash".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("zsh".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("fish".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("vim".to_string(), CommentStyle::new("\"".to_string())); - style_map.insert("lua".to_string(), CommentStyle::new("--".to_string())); - style_map.insert("sql".to_string(), CommentStyle::new("--".to_string())); - style_map.insert("yaml".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("yml".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("toml".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("ini".to_string(), CommentStyle::new(";".to_string())); - style_map.insert("cfg".to_string(), CommentStyle::new(";".to_string())); - style_map.insert("conf".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("dockerfile".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("makefile".to_string(), CommentStyle::new("#".to_string())); - style_map.insert("cmake".to_string(), CommentStyle::new("#".to_string())); - - Self { + return Self { default_style: CommentStyle::new("#".to_string()), style_map, - } + }; } - pub fn detect_from_filename(&self, filename: &str) -> &CommentStyle - { - if let Some(ext) = filename.split('.').last() - { - let ext = ext.to_lowercase(); - self.style_map.get(&ext).unwrap_or(&self.default_style) - } - else - { - &self.default_style - } + #[must_use] + pub fn detect_from_filename(&self, filename: &str) -> &CommentStyle { + let ext = filename.rsplit('.').next().unwrap_or_default().to_lowercase(); + return self.style_map.get(&ext).unwrap_or(&self.default_style); } - pub fn detect_from_filetype(&self, filetype: &str) -> &CommentStyle - { + #[must_use] + pub fn detect_from_filetype(&self, filetype: &str) -> &CommentStyle { let filetype = filetype.to_lowercase(); - self.style_map.get(&filetype).unwrap_or(&self.default_style) + return self.style_map.get(&filetype).unwrap_or(&self.default_style); } - pub fn load_config(&mut self, config: &Config) - { - for (filetype, prefix) in &config.comment_styles - { - self - .style_map - .insert(filetype.clone(), CommentStyle::new(prefix.clone())); + pub fn load_config(&mut self, config: &Config) { + for (filetype, prefix) in &config.comment_styles { + self.style_map.insert(filetype.clone(), CommentStyle::new(prefix.clone())); } } } + +impl Default for CommentDetector { + fn default() -> Self { + return Self::new(); + } +} diff --git a/src/config.rs b/src/config.rs index c52e720..f527c8c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,63 +1,60 @@ - use std::{collections::HashMap, path::PathBuf}; use dirs; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] -pub struct Config -{ +pub struct Config { pub comment_styles: HashMap, } -impl Default for Config -{ - fn default() -> Self - { - Self { - comment_styles: HashMap::new(), - } +impl Default for Config { + fn default() -> Self { + return Self { comment_styles: HashMap::new() }; } } -impl Config -{ - pub fn load() -> Result> - { +impl Config { + /// Loads the configuration from the user's config directory, or returns the default when no file exists. + /// + /// # Errors + /// + /// Returns an error if the config file cannot be read or is not valid YAML. + pub fn load() -> Result> { let config_path = Self::get_config_path(); - if config_path.exists() - { + if config_path.exists() { let content = std::fs::read_to_string(&config_path)?; - let config: Config = serde_yaml::from_str(&content)?; - Ok(config) - } - else - { - Ok(Config::default()) + let config: Self = serde_yaml::from_str(&content)?; + return Ok(config); + } else { + return Ok(Self::default()); } } - pub fn get_config_path() -> PathBuf - { - let mut config_dir = dirs::config_dir().unwrap_or_else(|| PathBuf::from(".")); + #[must_use] + pub fn get_config_path() -> PathBuf { + let mut config_dir = dirs::config_dir().unwrap_or_else(|| return PathBuf::from(".")); config_dir.push("kfmtbox"); config_dir.push("comments.yaml"); - config_dir + return config_dir; } - pub fn save(&self) -> Result<(), Box> - { + /// Writes the configuration to the user's config directory, creating it if needed. + /// + /// # Errors + /// + /// Returns an error if the directory or file cannot be written, or the config cannot be serialized. + pub fn save(&self) -> Result<(), Box> { let config_path = Self::get_config_path(); // Create config directory if it doesn't exist - if let Some(parent) = config_path.parent() - { + if let Some(parent) = config_path.parent() { std::fs::create_dir_all(parent)?; } let content = serde_yaml::to_string(self)?; std::fs::write(&config_path, content)?; - Ok(()) + return Ok(()); } } diff --git a/src/main.rs b/src/main.rs index 2170668..797f480 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ use std::io::{self, Read}; use clap::Parser; -use kfmtbox::{BoxRenderer, Cli, CommentDetector, Config}; use kfmtbox::verbose::{set_verbose, verbose_msg}; +use kfmtbox::{BoxRenderer, Cli, CommentDetector, Config}; /// Main entry point for the kfmtbox application. /// @@ -15,15 +15,14 @@ use kfmtbox::verbose::{set_verbose, verbose_msg}; /// - Input cannot be read from stdin /// - Configuration file cannot be loaded or parsed /// - Output cannot be written to stdout -fn main() -> Result<(), Box> -{ +fn main() -> Result<(), Box> { let cli = Cli::parse(); set_verbose(cli.verbose); // Load configuration verbose_msg("Loading Config"); - let config = Config::load().unwrap_or_else(|_| Config::default()); + let config = Config::load().unwrap_or_else(|_| return Config::default()); // Initialize comment detector verbose_msg("Detecting Comment Type"); @@ -31,16 +30,11 @@ fn main() -> Result<(), Box> detector.load_config(&config); // Determine comment style - let comment_style = if let Some(filename) = &cli.filename - { + let comment_style = if let Some(filename) = &cli.filename { detector.detect_from_filename(filename).clone() - } - else if let Some(filetype) = &cli.filetype - { + } else if let Some(filetype) = &cli.filetype { detector.detect_from_filetype(filetype).clone() - } - else - { + } else { detector.default_style.clone() }; @@ -52,10 +46,9 @@ fn main() -> Result<(), Box> // Remove trailing newline if present and normalize line endings verbose_msg("Cleaning Comment (Removing Newlines and Returns"); let input = input.trim_end(); - let input = input.replace("\r\n", "\n").replace("\n\r", "\n").replace("\r", "\n"); + let input = input.replace("\r\n", "\n").replace("\n\r", "\n").replace('\r', "\n"); - if input.is_empty() - { + if input.is_empty() { verbose_msg("Input was empty - returning nothing successfully"); return Ok(()); } @@ -67,7 +60,7 @@ fn main() -> Result<(), Box> // Output to stdout verbose_msg("Outputting rendered comment"); - println!("{}", result); + println!("{result}"); - Ok(()) + return Ok(()); } diff --git a/src/text_wrapper.rs b/src/text_wrapper.rs index f4472fc..d00c002 100644 --- a/src/text_wrapper.rs +++ b/src/text_wrapper.rs @@ -1,113 +1,84 @@ -#[allow(clippy::needless_return)] - -pub struct TextWrapper -{ +pub struct TextWrapper { max_width: usize, } -impl TextWrapper -{ - pub fn new(max_width: usize) -> Self - { +impl TextWrapper { + #[must_use] + pub const fn new(max_width: usize) -> Self { return Self { max_width }; } - pub fn wrap_text(&self, text: &str) -> Vec - { + #[must_use] + pub fn wrap_text(&self, text: &str) -> Vec { // First, split by newlines to preserve existing line breaks let input_lines: Vec<&str> = text.split('\n').collect(); let mut all_lines = Vec::new(); - for line in input_lines - { + for line in input_lines { let words: Vec<&str> = line.split_whitespace().collect(); - if words.is_empty() - { + if words.is_empty() { all_lines.push(String::new()); continue; } let mut current_line = String::new(); - for word in words - { - let test_line = if current_line.is_empty() - { - word.to_string() - } - else - { - format!("{} {}", current_line, word) - }; + for word in words { + let test_line = if current_line.is_empty() { word.to_string() } else { format!("{current_line} {word}") }; // Use a smaller effective width for better wrapping let effective_width = 20; // Force wrapping at 20 characters - if test_line.len() <= effective_width - { + if test_line.len() <= effective_width { current_line = test_line; - } - else - { - if !current_line.is_empty() - { + } else { + if !current_line.is_empty() { all_lines.push(current_line); } current_line = word.to_string(); } } - if !current_line.is_empty() - { + if !current_line.is_empty() { all_lines.push(current_line); } } - if all_lines.is_empty() - { + if all_lines.is_empty() { return vec![String::new()]; } return all_lines; } - pub fn balance_lines(&self, lines: Vec) -> Vec - { - if lines.len() <= 1 - { + #[must_use] + pub fn balance_lines(&self, lines: Vec) -> Vec { + if lines.len() <= 1 { return lines; } - let total_chars: usize = lines.iter().map(|line| line.len()).sum(); - let avg_chars_per_line = total_chars / lines.len(); - let target_width = (avg_chars_per_line + self.max_width) / 2; + let total_chars: usize = lines.iter().map(|line| return line.len()).sum(); + let avg_chars_per_line = total_chars.checked_div(lines.len()).unwrap_or(0); + let target_width = usize::midpoint(avg_chars_per_line, self.max_width); let max_combined_width = 25; // Don't combine lines if result would be longer than 25 chars let mut balanced = Vec::new(); let mut current_line = String::new(); - for line in lines - { - if current_line.is_empty() - { + for line in lines { + if current_line.is_empty() { current_line = line; - } - else - { - let test_line = format!("{} {}", current_line, line); - if test_line.len() <= target_width && test_line.len() <= max_combined_width - { + } else { + let test_line = format!("{current_line} {line}"); + if test_line.len() <= target_width && test_line.len() <= max_combined_width { current_line = test_line; - } - else - { + } else { balanced.push(current_line); current_line = line; } } } - if !current_line.is_empty() - { + if !current_line.is_empty() { balanced.push(current_line); } @@ -116,35 +87,33 @@ impl TextWrapper } #[cfg(test)] -mod tests -{ +mod tests { use super::*; + /// # Panics + /// + /// Panics if an assertion fails. #[test] - fn test_wrap_text() - { + fn test_wrap_text() { let wrapper = TextWrapper::new(20); let text = "This is a very long line that should be wrapped into multiple lines"; let result = wrapper.wrap_text(text); assert!(result.len() > 1); - for line in &result - { + for line in &result { assert!(line.len() <= 20); } } + /// # Panics + /// + /// Panics if an assertion fails. #[test] - fn test_balance_lines() - { + fn test_balance_lines() { let wrapper = TextWrapper::new(20); - let lines = vec![ - "Short".to_string(), - "Very long line that exceeds".to_string(), - "Another short".to_string(), - ]; + let lines = vec!["Short".to_string(), "Very long line that exceeds".to_string(), "Another short".to_string()]; let result = wrapper.balance_lines(lines); - assert!(result.len() >= 1); + assert!(!result.is_empty()); } } diff --git a/src/verbose.rs b/src/verbose.rs index 7476b67..07167dc 100644 --- a/src/verbose.rs +++ b/src/verbose.rs @@ -1,41 +1,30 @@ - use std::sync::atomic::{AtomicBool, Ordering}; // global flag pub static IS_VERBOSE: AtomicBool = AtomicBool::new(false); // set from anywhere -pub fn set_verbose(on: bool) -{ +pub fn set_verbose(on: bool) { IS_VERBOSE.store(on, Ordering::Relaxed); } // read from anywhere -pub fn is_verbose() -> bool -{ - IS_VERBOSE.load(Ordering::Relaxed) +pub fn is_verbose() -> bool { + return IS_VERBOSE.load(Ordering::Relaxed); } - - -pub fn verbose_msg(msg: &str) -{ - if!is_verbose() - { +pub fn verbose_msg(msg: &str) { + if !is_verbose() { return; } - println!("[VERBOSE] {}", msg); + println!("[VERBOSE] {msg}"); } - - -pub fn verbose_value(label: &str, val: T) -{ - if!is_verbose() - { +pub fn verbose_value(label: &str, val: T) { + if !is_verbose() { return; } - println!("[VERBOSE] {} = {:?}", label, val); -} \ No newline at end of file + println!("[VERBOSE] {label} = {val:?}"); +} -- 2.54.0 From dc730517198adbd54c6066e49e2a04b87b3736b8 Mon Sep 17 00:00:00 2001 From: Forge Recovery Date: Sun, 1 Nov 2026 19:47:59 +0000 Subject: [PATCH 2/2] fix(test): assert exact balanced line count (clippy assert_is_empty on 1.99) Co-Authored-By: Claude Sonnet 5.5 Co-Authored-By: Codex --- src/text_wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/text_wrapper.rs b/src/text_wrapper.rs index d00c002..b241196 100644 --- a/src/text_wrapper.rs +++ b/src/text_wrapper.rs @@ -114,6 +114,6 @@ mod tests { let lines = vec!["Short".to_string(), "Very long line that exceeds".to_string(), "Another short".to_string()]; let result = wrapper.balance_lines(lines); - assert!(!result.is_empty()); + assert_eq!(result.len(), 3); } } -- 2.54.0