Skip to content

Commit b8c10fa

Browse files
committed
fix: remove .br before empty lines to fix mandoc warnings
- Enhanced post-processing to remove .br macros that appear before empty lines - This fixes 'WARNING: skipping paragraph macro: br before sp' from mandoc - Also handles the common pattern of .br-empty-.br by removing both .br macros - Added comprehensive test cases for the new patterns - All mandoc 'br before sp' warnings are now resolved
1 parent f848e3a commit b8c10fa

4 files changed

Lines changed: 69 additions & 18 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ uudoc = [
3535
"dep:clap_complete",
3636
"dep:clap_mangen",
3737
"dep:fluent-syntax",
38+
"dep:jiff",
3839
"dep:regex",
3940
"dep:zip",
4041
]
@@ -481,6 +482,7 @@ clap_complete = { workspace = true, optional = true }
481482
clap_mangen = { workspace = true, optional = true }
482483
clap.workspace = true
483484
fluent-syntax = { workspace = true, optional = true }
485+
jiff = { workspace = true, optional = true }
484486
regex = { workspace = true, optional = true }
485487
itertools.workspace = true
486488
phf.workspace = true

src/bin/uudoc.rs

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use clap::{Arg, Command};
1717
use clap_complete::Shell;
1818
use clap_mangen::Man;
1919
use fluent_syntax::ast::{Entry, Message, Pattern};
20-
use jiff::Zoned;
2120
use fluent_syntax::parser;
21+
use jiff::Zoned;
2222
use regex::Regex;
2323
use textwrap::{fill, indent, termwidth};
2424
use zip::ZipArchive;
@@ -31,10 +31,13 @@ include!(concat!(env!("OUT_DIR"), "/uutils_map.rs"));
3131
/// Post-process a generated manpage to fix mandoc lint issues
3232
///
3333
/// This function:
34-
/// - Fixes the TH header by uppercasing command names and removing invalid date formats
34+
/// - Fixes the TH header by uppercasing command names and adding a proper date
3535
/// - Removes trailing whitespace from all lines
3636
/// - Fixes redundant .br paragraph macros that cause mandoc warnings
37-
fn post_process_manpage(manpage: String) -> String {
37+
/// - Removes .br before empty lines to avoid "br before sp" warnings
38+
/// - Removes .br after empty lines to avoid "br after sp" warnings
39+
/// - Fixes escape sequences (e.g., \\\\0 to \\0) to avoid "undefined escape" warnings
40+
fn post_process_manpage(manpage: String, date: Option<&str>) -> String {
3841
// Only match TH headers that have at least a command name on the same line
3942
// Use [ \t] instead of \s to avoid matching newlines
4043
// Use a date format that satisfies mandoc (YYYY-MM-DD)
@@ -61,16 +64,22 @@ fn post_process_manpage(manpage: String) -> String {
6164
let line = lines[i].trim_end();
6265

6366
if line == ".br" && !skip_indices.contains(&i) {
64-
// Check for consecutive .br macros
65-
if i > 0 && lines[i - 1].trim_end() == ".br" {
67+
// Check for .br followed by empty line
68+
if i + 1 < lines.len() && lines[i + 1].trim().is_empty() {
69+
// Remove the .br when it's followed by an empty line
70+
// This prevents "WARNING: skipping paragraph macro: br before sp"
6671
skip_indices.insert(i);
72+
73+
// Also check if there's another .br after the empty line (common pattern)
74+
if i + 2 < lines.len() && lines[i + 2].trim_end() == ".br" {
75+
skip_indices.insert(i + 2);
76+
}
6777
}
68-
// Check for .br, empty line, .br pattern
69-
else if i + 2 < lines.len()
70-
&& lines[i + 1].trim().is_empty()
71-
&& lines[i + 2].trim_end() == ".br"
78+
// Check for .br preceded by empty line or another .br
79+
// This prevents "WARNING: skipping paragraph macro: br after sp" and consecutive .br
80+
else if i > 0 && (lines[i - 1].trim().is_empty() || lines[i - 1].trim_end() == ".br")
7281
{
73-
skip_indices.insert(i + 2);
82+
skip_indices.insert(i);
7483
}
7584
}
7685
}
@@ -829,4 +838,44 @@ mod tests {
829838
let result4 = post_process_manpage(input4.to_string(), Some("2024-01-01"));
830839
assert_eq!(result4, expected4);
831840
}
841+
842+
#[test]
843+
fn test_post_process_manpage_removes_br_before_empty_line() {
844+
// Test that .br is removed when followed by empty line (which becomes .sp)
845+
let input = ".TH TEST 1\nSome text\n.br\n\nMore text\n";
846+
let expected = ".TH TEST 1 \"2024-01-01\"\nSome text\n\nMore text\n";
847+
848+
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
849+
assert_eq!(result, expected);
850+
}
851+
852+
#[test]
853+
fn test_post_process_manpage_complex_br_before_empty() {
854+
// Test multiple .br before empty line patterns
855+
let input = ".TH TEST 1\nSection 1\n.br\n\nSection 2\n.br\n\nSection 3\n";
856+
let expected = ".TH TEST 1 \"2024-01-01\"\nSection 1\n\nSection 2\n\nSection 3\n";
857+
858+
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
859+
assert_eq!(result, expected);
860+
}
861+
862+
#[test]
863+
fn test_post_process_manpage_removes_br_after_empty_line() {
864+
// Test that .br is removed when preceded by empty line (which becomes .sp)
865+
let input = ".TH TEST 1\nSome text\n\n.br\nMore text\n";
866+
let expected = ".TH TEST 1 \"2024-01-01\"\nSome text\n\nMore text\n";
867+
868+
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
869+
assert_eq!(result, expected);
870+
}
871+
872+
#[test]
873+
fn test_post_process_manpage_fixes_escape_sequences() {
874+
// Test that \\\\0 and \\0 are fixed to \e0 (literal backslash-zero)
875+
let input = ".TH TEST 1\nText with \\\\\\\\0 and \\\\0 escape\n";
876+
let expected = ".TH TEST 1 \"2024-01-01\"\nText with \\e0 and \\e0 escape\n";
877+
878+
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
879+
assert_eq!(result, expected);
880+
}
832881
}

tests/uudoc/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fn test_manpage_generation() {
3636
);
3737

3838
let output_str = String::from_utf8_lossy(&output.stdout);
39-
assert!(output_str.contains("\n.TH ls"), "{output_str}");
39+
assert!(output_str.contains("\n.TH LS"), "{output_str}");
4040
assert!(output_str.contains('1'), "{output_str}");
4141
assert!(output_str.contains("\n.SH NAME\nls"), "{output_str}");
4242
}
@@ -62,7 +62,7 @@ fn test_manpage_coreutils() {
6262
);
6363

6464
let output_str = String::from_utf8_lossy(&output.stdout);
65-
assert!(output_str.contains("\n.TH coreutils"), "{output_str}");
65+
assert!(output_str.contains("\n.TH COREUTILS"), "{output_str}");
6666
assert!(output_str.contains("coreutils"), "{output_str}");
6767
assert!(output_str.contains("\n.SH NAME\ncoreutils"), "{output_str}");
6868
}

0 commit comments

Comments
 (0)