mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
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
This commit is contained in:
committed by
Daniel Hofstetter
parent
b73d67e9bb
commit
a1355f1076
Generated
+6
-6
@@ -991,7 +991,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1708,7 +1708,7 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2011,7 +2011,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2593,7 +2593,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2893,7 +2893,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4681,7 +4681,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+4
-2
@@ -35,6 +35,7 @@ uudoc = [
|
||||
"dep:clap_complete",
|
||||
"dep:clap_mangen",
|
||||
"dep:fluent-syntax",
|
||||
"dep:jiff",
|
||||
"dep:regex",
|
||||
"dep:zip",
|
||||
]
|
||||
@@ -477,13 +478,14 @@ uu_checksum_common = { version = "0.7.0", path = "src/uu/checksum_common" }
|
||||
uutests = { version = "0.7.0", package = "uutests", path = "tests/uutests" }
|
||||
|
||||
[dependencies]
|
||||
clap.workspace = true
|
||||
clap_complete = { workspace = true, optional = true }
|
||||
clap_mangen = { workspace = true, optional = true }
|
||||
clap.workspace = true
|
||||
fluent-syntax = { workspace = true, optional = true }
|
||||
regex = { workspace = true, optional = true }
|
||||
itertools.workspace = true
|
||||
jiff = { workspace = true, optional = true }
|
||||
phf.workspace = true
|
||||
regex = { workspace = true, optional = true }
|
||||
selinux = { workspace = true, optional = true }
|
||||
textwrap.workspace = true
|
||||
uucore.workspace = true
|
||||
|
||||
+73
-43
@@ -6,7 +6,7 @@
|
||||
// spell-checker:ignore mangen tldr mandoc uppercasing uppercased manpages DESTDIR
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
collections::HashMap,
|
||||
ffi::OsString,
|
||||
fs::File,
|
||||
io::{self, Read, Seek, Write},
|
||||
@@ -17,8 +17,8 @@ use clap::{Arg, Command};
|
||||
use clap_complete::Shell;
|
||||
use clap_mangen::Man;
|
||||
use fluent_syntax::ast::{Entry, Message, Pattern};
|
||||
use jiff::Zoned;
|
||||
use fluent_syntax::parser;
|
||||
use jiff::Zoned;
|
||||
use regex::Regex;
|
||||
use textwrap::{fill, indent, termwidth};
|
||||
use zip::ZipArchive;
|
||||
@@ -31,17 +31,16 @@ include!(concat!(env!("OUT_DIR"), "/uutils_map.rs"));
|
||||
/// Post-process a generated manpage to fix mandoc lint issues
|
||||
///
|
||||
/// This function:
|
||||
/// - Fixes the TH header by uppercasing command names and removing invalid date formats
|
||||
/// - Fixes the TH header by uppercasing command names and adding a proper date
|
||||
/// - Removes trailing whitespace from all lines
|
||||
/// - Fixes redundant .br paragraph macros that cause mandoc warnings
|
||||
fn post_process_manpage(manpage: String) -> String {
|
||||
/// - Removes .br before empty lines to avoid "br before sp" warnings
|
||||
/// - Removes .br after empty lines to avoid "br after sp" warnings
|
||||
/// - Fixes escape sequences (e.g., \\\\0 to \\0) to avoid "undefined escape" warnings
|
||||
fn post_process_manpage(manpage: String, date: &str) -> String {
|
||||
// Only match TH headers that have at least a command name on the same line
|
||||
// Use [ \t] instead of \s to avoid matching newlines
|
||||
// Use a date format that satisfies mandoc (YYYY-MM-DD)
|
||||
let date = date.map_or_else(
|
||||
|| Zoned::now().strftime("%Y-%m-%d").to_string(),
|
||||
str::to_string,
|
||||
);
|
||||
|
||||
let th_regex = Regex::new(r"(?m)^\.TH[ \t]+([^ \t\n]+)(?:[ \t]+[^\n]*)?$").unwrap();
|
||||
let mut result = th_regex
|
||||
@@ -52,34 +51,24 @@ fn post_process_manpage(manpage: String) -> String {
|
||||
.to_string();
|
||||
|
||||
// Process lines: remove trailing whitespace and fix .br issues in a single pass
|
||||
let lines: Vec<&str> = result.lines().collect();
|
||||
let mut fixed_lines = Vec::with_capacity(lines.len());
|
||||
let mut skip_indices = HashSet::new();
|
||||
let lines: Vec<&str> = result.lines().map(str::trim_end).collect();
|
||||
let mut fixed_lines: Vec<&str> = Vec::with_capacity(lines.len());
|
||||
|
||||
// First pass: identify lines to skip (redundant .br macros)
|
||||
for i in 0..lines.len() {
|
||||
let line = lines[i].trim_end();
|
||||
let line = lines[i];
|
||||
|
||||
if line == ".br" && !skip_indices.contains(&i) {
|
||||
// Check for consecutive .br macros
|
||||
if i > 0 && lines[i - 1].trim_end() == ".br" {
|
||||
skip_indices.insert(i);
|
||||
}
|
||||
// Check for .br, empty line, .br pattern
|
||||
else if i + 2 < lines.len()
|
||||
&& lines[i + 1].trim().is_empty()
|
||||
&& lines[i + 2].trim_end() == ".br"
|
||||
{
|
||||
skip_indices.insert(i + 2);
|
||||
if line == ".br" {
|
||||
let preceded_by_empty_line = i > 0 && lines[i - 1].is_empty();
|
||||
let followed_by_empty_line = i + 1 < lines.len() && lines[i + 1].is_empty();
|
||||
let followed_by_br = i + 1 < lines.len() && lines[i + 1] == ".br";
|
||||
|
||||
if preceded_by_empty_line || followed_by_empty_line || followed_by_br {
|
||||
// skip this ".br"
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: build the final output
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
if !skip_indices.contains(&i) {
|
||||
fixed_lines.push(line.trim_end());
|
||||
}
|
||||
fixed_lines.push(line);
|
||||
}
|
||||
|
||||
result = fixed_lines.join("\n");
|
||||
@@ -174,7 +163,8 @@ fn gen_manpage<T: Args>(
|
||||
let manpage = String::from_utf8(buffer).expect("Invalid UTF-8 in manpage");
|
||||
|
||||
// Post-process the manpage to fix mandoc lint issues
|
||||
let processed_manpage = post_process_manpage(manpage, None);
|
||||
let date = Zoned::now().strftime("%Y-%m-%d").to_string();
|
||||
let processed_manpage = post_process_manpage(manpage, &date);
|
||||
|
||||
// Write the processed manpage to stdout
|
||||
io::stdout()
|
||||
@@ -726,7 +716,7 @@ mod tests {
|
||||
".TH cat 1 \"cat (uutils coreutils) 0.7.0\"\n.SH NAME\ncat - concatenate files\n";
|
||||
let expected = ".TH CAT 1 \"2024-01-01\"\n.SH NAME\ncat - concatenate files\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -736,7 +726,7 @@ mod tests {
|
||||
let input = ".TH TEST 1 \nSome text with trailing spaces \n.SH SECTION \n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\nSome text with trailing spaces\n.SH SECTION\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -746,7 +736,7 @@ mod tests {
|
||||
let input = ".TH TEST 1\n.br\n.br\nSome text\n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\n.br\nSome text\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -757,7 +747,7 @@ mod tests {
|
||||
let input = ".TH TEST 1\n.br\n\n.br\nSome text\n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\n\nSome text\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -767,7 +757,7 @@ mod tests {
|
||||
let input = ".TH TEST 1\nLine 1\n.br\nLine 2\n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\nLine 1\n.br\nLine 2\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -777,7 +767,7 @@ mod tests {
|
||||
let input = ".TH CaT 1 \"some version info\"\nContent\n";
|
||||
let expected = ".TH CAT 1 \"2024-01-01\"\nContent\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -787,7 +777,7 @@ mod tests {
|
||||
let input = ".SH NAME\ntest - a test utility\n";
|
||||
let expected = ".SH NAME\ntest - a test utility\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -799,7 +789,7 @@ mod tests {
|
||||
// .br followed/preceded by empty lines should be removed, consecutive .br should have one removed
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\nSection 1\n\nMiddle\n.br\nSection 2\n\nEnd\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), Some("2024-01-01"));
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
@@ -808,25 +798,65 @@ mod tests {
|
||||
// Test that malformed TH headers don't cause panics and are handled gracefully
|
||||
let input1 = ".TH\nContent\n"; // Missing command name
|
||||
let expected1 = ".TH\nContent\n";
|
||||
let result1 = post_process_manpage(input1.to_string(), Some("2024-01-01"));
|
||||
let result1 = post_process_manpage(input1.to_string(), "2024-01-01");
|
||||
assert_eq!(result1, expected1);
|
||||
|
||||
// TH header with special characters
|
||||
let input2 = ".TH test-cmd 1 \"version 1.0\"\nContent\n";
|
||||
let expected2 = ".TH TEST-CMD 1 \"2024-01-01\"\nContent\n";
|
||||
let result2 = post_process_manpage(input2.to_string(), Some("2024-01-01"));
|
||||
let result2 = post_process_manpage(input2.to_string(), "2024-01-01");
|
||||
assert_eq!(result2, expected2);
|
||||
|
||||
// TH header at end of file without newline
|
||||
let input3 = "Content\n.TH test 1";
|
||||
let expected3 = "Content\n.TH TEST 1 \"2024-01-01\"\n";
|
||||
let result3 = post_process_manpage(input3.to_string(), Some("2024-01-01"));
|
||||
let result3 = post_process_manpage(input3.to_string(), "2024-01-01");
|
||||
assert_eq!(result3, expected3);
|
||||
|
||||
// Multiple TH headers (only first should be processed due to ^anchor)
|
||||
let input4 = ".TH first 1\nMiddle\n.TH second 1\n";
|
||||
let expected4 = ".TH FIRST 1 \"2024-01-01\"\nMiddle\n.TH SECOND 1 \"2024-01-01\"\n";
|
||||
let result4 = post_process_manpage(input4.to_string(), Some("2024-01-01"));
|
||||
let result4 = post_process_manpage(input4.to_string(), "2024-01-01");
|
||||
assert_eq!(result4, expected4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_post_process_manpage_removes_br_before_empty_line() {
|
||||
// Test that .br is removed when followed by empty line (which becomes .sp)
|
||||
let input = ".TH TEST 1\nSome text\n.br\n\nMore text\n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\nSome text\n\nMore text\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_post_process_manpage_complex_br_before_empty() {
|
||||
// Test multiple .br before empty line patterns
|
||||
let input = ".TH TEST 1\nSection 1\n.br\n\nSection 2\n.br\n\nSection 3\n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\nSection 1\n\nSection 2\n\nSection 3\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_post_process_manpage_removes_br_after_empty_line() {
|
||||
// Test that .br is removed when preceded by empty line (which becomes .sp)
|
||||
let input = ".TH TEST 1\nSome text\n\n.br\nMore text\n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\nSome text\n\nMore text\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_post_process_manpage_fixes_escape_sequences() {
|
||||
// Test that \\\\0 and \\0 are fixed to \e0 (literal backslash-zero)
|
||||
let input = ".TH TEST 1\nText with \\\\\\\\0 and \\\\0 escape\n";
|
||||
let expected = ".TH TEST 1 \"2024-01-01\"\nText with \\e0 and \\e0 escape\n";
|
||||
|
||||
let result = post_process_manpage(input.to_string(), "2024-01-01");
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ fn test_manpage_generation() {
|
||||
);
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(output_str.contains("\n.TH ls"), "{output_str}");
|
||||
assert!(output_str.contains("\n.TH LS"), "{output_str}");
|
||||
assert!(output_str.contains('1'), "{output_str}");
|
||||
assert!(output_str.contains("\n.SH NAME\nls"), "{output_str}");
|
||||
}
|
||||
@@ -62,7 +62,7 @@ fn test_manpage_coreutils() {
|
||||
);
|
||||
|
||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(output_str.contains("\n.TH coreutils"), "{output_str}");
|
||||
assert!(output_str.contains("\n.TH COREUTILS"), "{output_str}");
|
||||
assert!(output_str.contains("coreutils"), "{output_str}");
|
||||
assert!(output_str.contains("\n.SH NAME\ncoreutils"), "{output_str}");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user