pr: use 72 char line width for all page headers

Set the default line width to 72 for all page headers in `pr`,
regardless of whether a custom date format is being used. Before, a
single space was used to separate the three components of the header
(date, filename, and page number) if a custom date format was not given.
This commit is contained in:
Jeffrey Finkelstein
2026-01-04 18:09:57 -05:00
parent 666c6df2c6
commit bfdbf59646
31 changed files with 531 additions and 170 deletions
+11 -22
View File
@@ -1180,34 +1180,23 @@ fn header_content(options: &OutputOptions, page: usize) -> Vec<String> {
// Use the line width if available, otherwise use default of 72
let total_width = options.line_width.unwrap_or(DEFAULT_COLUMN_WIDTH);
// GNU pr uses a specific layout:
// Date takes up the left part, filename is centered, page is right-aligned
let date_len = date_part.chars().count();
let filename_len = filename.chars().count();
let page_len = page_part.chars().count();
let header_line = if date_len + filename_len + page_len + 2 < total_width {
// Check if we're using a custom date format that needs centered alignment
// This preserves backward compatibility while fixing the GNU time-style test
if date_part.starts_with('+') {
// GNU pr uses centered layout for headers with custom date formats
// The filename should be centered between the date and page parts
let space_for_filename = total_width - date_len - page_len;
let padding_before_filename = (space_for_filename - filename_len) / 2;
let padding_after_filename =
space_for_filename - filename_len - padding_before_filename;
// The filename should be centered between the date and page parts
let space_for_filename = total_width - date_len - page_len;
let padding_before_filename = (space_for_filename - filename_len) / 2;
let padding_after_filename = space_for_filename - filename_len - padding_before_filename;
format!(
"{date_part}{:width1$}{filename}{:width2$}{page_part}",
"",
"",
width1 = padding_before_filename,
width2 = padding_after_filename
)
} else {
// For standard date formats, use simple spacing for backward compatibility
format!("{date_part} {filename} {page_part}")
}
format!(
"{date_part}{:width1$}{filename}{:width2$}{page_part}",
"",
"",
width1 = padding_before_filename,
width2 = padding_after_filename
)
} else {
// If content is too long, just use single spaces
format!("{date_part} {filename} {page_part}")
+70 -65
View File
@@ -5,6 +5,7 @@
// spell-checker:ignore (ToDO) Sdivide
use chrono::{DateTime, Duration, Utc};
use regex::Regex;
use std::fs::metadata;
use uutests::new_ucmd;
use uutests::util::UCommand;
@@ -78,21 +79,22 @@ fn test_with_numbering_option_with_number_width() {
#[test]
fn test_with_long_header_option() {
let test_file_path = "test_one_page.log";
let expected_test_file_path = "test_one_page_header.log.expected";
let header = "new file";
for args in [&["-h", header][..], &["--header=new file"][..]] {
let mut scenario = new_ucmd!();
let value = file_last_modified_time(&scenario, test_file_path);
scenario
.args(args)
.arg(test_file_path)
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
&[("{last_modified_time}", &value), ("{header}", header)],
);
}
let whitespace = " ".repeat(21);
let blank_lines = "\n".repeat(61);
let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d";
let pattern =
format!("\n\n{datetime_pattern}{whitespace}new file{whitespace}Page 1\n\n\na{blank_lines}");
let regex = Regex::new(&pattern).unwrap();
new_ucmd!()
.args(&["-h", "new file"])
.pipe_in("a")
.succeeds()
.stdout_matches(&regex);
new_ucmd!()
.args(&["--header=new file"])
.pipe_in("a")
.succeeds()
.stdout_matches(&regex);
}
#[test]
@@ -400,99 +402,92 @@ fn test_with_offset_space_option() {
#[test]
fn test_with_date_format() {
let test_file_path = "test_one_page.log";
let expected_test_file_path = "test_one_page.log.expected";
let mut scenario = new_ucmd!();
let value = file_last_modified_time_format(&scenario, test_file_path, "%Y__%s");
scenario
.args(&[test_file_path, "-D", "%Y__%s"])
let whitespace = " ".repeat(50);
let blank_lines = "\n".repeat(61);
let datetime_pattern = r"\d{4}__\d{10}";
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
let regex = Regex::new(&pattern).unwrap();
new_ucmd!()
.args(&["-D", "%Y__%s"])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
.stdout_matches(&regex);
// "Format" doesn't need to contain any replaceable token.
let whitespace = " ".repeat(60);
let blank_lines = "\n".repeat(61);
new_ucmd!()
.args(&[test_file_path, "-D", "Hello!"])
.args(&["-D", "Hello!"])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
&[("{last_modified_time}", "Hello!")],
);
.stdout_only(format!("\n\nHello!{whitespace}Page 1\n\n\na{blank_lines}"));
// Long option also works
new_ucmd!()
.args(&[test_file_path, "--date-format=Hello!"])
.args(&["--date-format=Hello!"])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
&[("{last_modified_time}", "Hello!")],
);
.stdout_only(format!("\n\nHello!{whitespace}Page 1\n\n\na{blank_lines}"));
// Option takes precedence over environment variables
new_ucmd!()
.env("POSIXLY_CORRECT", "1")
.env("LC_TIME", "POSIX")
.args(&[test_file_path, "-D", "Hello!"])
.args(&["--date-format=Hello!"])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(
expected_test_file_path,
&[("{last_modified_time}", "Hello!")],
);
.stdout_only(format!("\n\nHello!{whitespace}Page 1\n\n\na{blank_lines}"));
}
#[test]
fn test_with_date_format_env() {
const POSIXLY_FORMAT: &str = "%b %e %H:%M %Y";
// POSIXLY_CORRECT + LC_ALL/TIME=POSIX uses "%b %e %H:%M %Y" date format
let test_file_path = "test_one_page.log";
let expected_test_file_path = "test_one_page.log.expected";
let mut scenario = new_ucmd!();
let value = file_last_modified_time_format(&scenario, test_file_path, POSIXLY_FORMAT);
scenario
let whitespace = " ".repeat(49);
let blank_lines = "\n".repeat(61);
let datetime_pattern = r"[A-Z][a-z][a-z] [ \d]\d \d\d:\d\d \d{4}";
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
let regex = Regex::new(&pattern).unwrap();
new_ucmd!()
.env("POSIXLY_CORRECT", "1")
.env("LC_ALL", "POSIX")
.args(&[test_file_path])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
let mut scenario = new_ucmd!();
let value = file_last_modified_time_format(&scenario, test_file_path, POSIXLY_FORMAT);
scenario
.stdout_matches(&regex);
new_ucmd!()
.env("POSIXLY_CORRECT", "1")
.env("LC_TIME", "POSIX")
.args(&[test_file_path])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
.stdout_matches(&regex);
// But not if POSIXLY_CORRECT/LC_ALL is something else.
let mut scenario = new_ucmd!();
let value = file_last_modified_time_format(&scenario, test_file_path, DATE_TIME_FORMAT_DEFAULT);
scenario
let whitespace = " ".repeat(50);
let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d";
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
let regex = Regex::new(&pattern).unwrap();
new_ucmd!()
.env("LC_TIME", "POSIX")
.args(&[test_file_path])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
let mut scenario = new_ucmd!();
let value = file_last_modified_time_format(&scenario, test_file_path, DATE_TIME_FORMAT_DEFAULT);
scenario
.stdout_matches(&regex);
new_ucmd!()
.env("POSIXLY_CORRECT", "1")
.env("LC_TIME", "C")
.args(&[test_file_path])
.pipe_in("a")
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
.stdout_matches(&regex);
}
#[test]
fn test_with_pr_core_utils_tests() {
let test_cases = vec![
("", vec!["0Ft"], vec!["0F"], 0),
("", vec!["0Fnt"], vec!["0F"], 0),
("", vec!["0Fnt"], vec!["0Fnt-expected"], 0),
("+3", vec!["0Ft"], vec!["3-0F"], 0),
("+3 -f", vec!["0Ft"], vec!["3f-0F"], 0),
("-a -3", vec!["0Ft"], vec!["a3-0F"], 0),
("-a -3 -f", vec!["0Ft"], vec!["a3f-0F"], 0),
("-a -3 -f", vec!["0Fnt"], vec!["a3f-0F"], 0),
("-a -3 -f", vec!["0Fnt"], vec!["a3f-0Fnt-expected"], 0),
("+3 -a -3 -f", vec!["0Ft"], vec!["3a3f-0F"], 0),
("-l 24", vec!["FnFn"], vec!["l24-FF"], 0),
("-W 20 -l24 -f", vec!["tFFt-ll"], vec!["W20l24f-ll"], 0),
@@ -622,3 +617,13 @@ fn test_b_flag_backwards_compat() {
// -b is a no-op for backwards compatibility (column-down is now the default)
new_ucmd!().args(&["-b", "-t"]).pipe_in("a\nb\n").succeeds();
}
#[test]
fn test_page_header_width() {
let whitespace = " ".repeat(50);
let blank_lines = "\n".repeat(61);
let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d";
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
let regex = Regex::new(&pattern).unwrap();
new_ucmd!().pipe_in("a").succeeds().stdout_matches(&regex);
}
+5 -5
View File
@@ -1,6 +1,6 @@
{last_modified_time} {file_name} Page 1
{last_modified_time} {file_name} Page 1
@@ -66,7 +66,7 @@
{last_modified_time} {file_name} Page 2
{last_modified_time} {file_name} Page 2
1 FF-Test: FF's at Start of File V
@@ -132,7 +132,7 @@
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 3
@@ -198,7 +198,7 @@
{last_modified_time} {file_name} Page 4
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ abcabcab
@@ -264,7 +264,7 @@
{last_modified_time} {file_name} Page 5
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ abcabcab
+330
View File
@@ -0,0 +1,330 @@
{last_modified_time} {file_name} Page 1
{last_modified_time} {file_name} Page 2
1 FF-Test: FF's at Start of File V
2 Options -b -3 / -a -3 / ...
3 --------------------------------------------
4 3456789 123456789 123456789 123456789 12345678
5 3 Columns downwards ..., <= 5 lines per page
6 FF-Arangements: Empty Pages at start
7 \ftext; \f\ntext;
8 \f\ftext; \f\f\ntext; \f\n\ftext; \f\n\f\n;
9 3456789 123456789 123456789
10 zzzzzzzzzzzzzzzzzzzzzzzzzz123456789
1 12345678
2 12345678
3 line truncation before FF; r_r_o_l-test:
14 456789 123456789 123456789 123456789
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ abcabcab
16 456789 123456789 xyzxyzxyz XYZXYZXYZ
7 12345678
8 12345678
9 3456789 ab
20 DEFGHI 123
1 12345678
2 12345678
3 12345678
4 12345678
5 12345678
6 12345678
27 no truncation before FF; (r_l-test):
28 no trunc
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ abcabcab
30 456789 123456789 xyzxyzxyz XYZXYZXYZ
1 12345678
2 3456789 abcdefghi
3 12345678
+3 -3
View File
@@ -1,6 +1,6 @@
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 3
@@ -66,7 +66,7 @@
{last_modified_time} {file_name} Page 4
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ abcabcab
@@ -132,7 +132,7 @@
{last_modified_time} {file_name} Page 5
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ abcabcab
+3 -3
View File
@@ -1,11 +1,11 @@
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 4
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ 16 456789 123456789 xyz 7
@@ -15,7 +15,7 @@
27 no truncation before 28 no trunc
{last_modified_time} {file_name} Page 5
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ 30 456789 123456789 xyz 1
+3 -3
View File
@@ -1,11 +1,11 @@
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 4
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ abcabcab
@@ -25,7 +25,7 @@
{last_modified_time} {file_name} Page 5
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ abcabcab
+5 -5
View File
@@ -1,6 +1,6 @@
{last_modified_time} {file_name} Page 1
{last_modified_time} {file_name} Page 1
@@ -66,7 +66,7 @@
{last_modified_time} {file_name} Page 2
{last_modified_time} {file_name} Page 2
1 FF-Test: FF's at St 2 Options -b -3 / -a 3 -------------------
@@ -132,7 +132,7 @@
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 3
@@ -198,7 +198,7 @@
{last_modified_time} {file_name} Page 4
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ 16 456789 123456789 xyz 7
@@ -264,7 +264,7 @@
{last_modified_time} {file_name} Page 5
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ 30 456789 123456789 xyz 1
+5 -5
View File
@@ -1,11 +1,11 @@
{last_modified_time} {file_name} Page 1
{last_modified_time} {file_name} Page 1
{last_modified_time} {file_name} Page 2
{last_modified_time} {file_name} Page 2
1 FF-Test: FF's at St 2 Options -b -3 / -a 3 -------------------
@@ -15,12 +15,12 @@
3 line truncation befor 14 456789 123456789 123
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 4
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ 16 456789 123456789 xyz 7
@@ -30,7 +30,7 @@
27 no truncation before 28 no trunc
{last_modified_time} {file_name} Page 5
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ 30 456789 123456789 xyz 1
+37
View File
@@ -0,0 +1,37 @@
{last_modified_time} {file_name} Page 1
{last_modified_time} {file_name} Page 2
1 FF-Test: FF's at St 2 Options -b -3 / -a 3 -------------------
4 3456789 123456789 123 5 3 Columns downwards 6 FF-Arangements: Emp
7 \ftext; \f\ntext; 8 \f\ftext; \f\f\ntex 9 3456789 123456789 123
10 zzzzzzzzzzzzzzzzzzz 1 2
3 line truncation befor 14 456789 123456789 123
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ 16 456789 123456789 xyz 7
8 9 3456789 ab 20 DEFGHI 123
1 2 3
4 5 6
27 no truncation before 28 no trunc
{last_modified_time} {file_name} Page 5
29 xyzxyzxyz XYZXYZXYZ 30 456789 123456789 xyz 1
2 3456789 abcdefghi 3
+3 -3
View File
@@ -1,6 +1,6 @@
{last_modified_time} column.log Page 3
{last_modified_time} column.log Page 3
337 337 393 393 449 449
@@ -66,7 +66,7 @@
{last_modified_time} column.log Page 4
{last_modified_time} column.log Page 4
505 505 561 561 617 617
@@ -132,7 +132,7 @@
{last_modified_time} column.log Page 5
{last_modified_time} column.log Page 5
673 673 729 729 785 785
+3 -3
View File
@@ -1,6 +1,6 @@
{last_modified_time} column.log Page 3
{last_modified_time} column.log Page 3
337 337 338 338 339 339
@@ -66,7 +66,7 @@
{last_modified_time} column.log Page 4
{last_modified_time} column.log Page 4
505 505 506 506 507 507
@@ -132,7 +132,7 @@
{last_modified_time} column.log Page 5
{last_modified_time} column.log Page 5
673 673 674 674 675 675
+3 -3
View File
@@ -1,6 +1,6 @@
{last_modified_time} column.log Page 3
{last_modified_time} column.log Page 3
337 337 | 338 338 | 339 339
@@ -66,7 +66,7 @@
{last_modified_time} column.log Page 4
{last_modified_time} column.log Page 4
505 505 | 506 506 | 507 507
@@ -132,7 +132,7 @@
{last_modified_time} column.log Page 5
{last_modified_time} column.log Page 5
673 673 | 674 674 | 675 675
+3 -3
View File
@@ -1,6 +1,6 @@
{last_modified_time} column.log Page 3
{last_modified_time} column.log Page 3
337 337 divide 338 338 divide 339 339
@@ -66,7 +66,7 @@
{last_modified_time} column.log Page 4
{last_modified_time} column.log Page 4
505 505 divide 506 506 divide 507 507
@@ -132,7 +132,7 @@
{last_modified_time} column.log Page 5
{last_modified_time} column.log Page 5
673 673 divide 674 674 divide 675 675
+3 -3
View File
@@ -1,6 +1,6 @@
{last_modified_time} column.log Page 3
{last_modified_time} column.log Page 3
337 337 338 338 339 339
@@ -66,7 +66,7 @@
{last_modified_time} column.log Page 4
{last_modified_time} column.log Page 4
505 505 506 506 507 507
@@ -132,7 +132,7 @@
{last_modified_time} column.log Page 5
{last_modified_time} column.log Page 5
673 673 674 674 675 675
+2 -2
View File
@@ -1,6 +1,6 @@
{last_modified_time} Page 1
{last_modified_time} Page 1
##ntation processAirPortStateChanges]: pppConnectionState 0
@@ -66,7 +66,7 @@ Mon Dec 10 11:42:59.352 Info: <Wi-Fi Menu Extra[335]> 802.1X changed
{last_modified_time} Page 2
{last_modified_time} Page 2
Mon Dec 10 11:42:59.354 Info: <Wi-Fi Menu Extra[335]> -[AirPortExtraImplementation processAirPortStateChanges]: pppConnectionState 0
+13 -13
View File
@@ -1,6 +1,6 @@
{last_modified_time} {file_name} Page 1
{last_modified_time} {file_name} Page 1
1 FF-Test: FF's in Text V
@@ -24,7 +24,7 @@
{last_modified_time} {file_name} Page 2
{last_modified_time} {file_name} Page 2
@@ -48,7 +48,7 @@
{last_modified_time} {file_name} Page 3
{last_modified_time} {file_name} Page 3
@@ -72,7 +72,7 @@
{last_modified_time} {file_name} Page 4
{last_modified_time} {file_name} Page 4
15 xyzxyzxyz XYZXYZXYZ abcabcab
@@ -96,7 +96,7 @@
{last_modified_time} {file_name} Page 5
{last_modified_time} {file_name} Page 5
@@ -120,7 +120,7 @@
{last_modified_time} {file_name} Page 6
{last_modified_time} {file_name} Page 6
@@ -144,7 +144,7 @@
{last_modified_time} {file_name} Page 7
{last_modified_time} {file_name} Page 7
29 xyzxyzxyz XYZXYZXYZ abcabcab
@@ -168,7 +168,7 @@
{last_modified_time} {file_name} Page 8
{last_modified_time} {file_name} Page 8
@@ -192,7 +192,7 @@
{last_modified_time} {file_name} Page 9
{last_modified_time} {file_name} Page 9
@@ -216,7 +216,7 @@
{last_modified_time} {file_name} Page 10
{last_modified_time} {file_name} Page 10
@@ -240,7 +240,7 @@
{last_modified_time} {file_name} Page 11
{last_modified_time} {file_name} Page 11
43 xyzxyzxyz XYZXYZXYZ abcabcab
@@ -264,7 +264,7 @@
{last_modified_time} {file_name} Page 12
{last_modified_time} {file_name} Page 12
@@ -288,7 +288,7 @@
{last_modified_time} {file_name} Page 13
{last_modified_time} {file_name} Page 13
57 xyzxyzxyz XYZXYZXYZ abcabcab
+2 -2
View File
@@ -1,6 +1,6 @@
{last_modified_time} Page 1
{last_modified_time} Page 1
1 1 ##
@@ -66,7 +66,7 @@
{last_modified_time} Page 2
{last_modified_time} Page 2
57 57
+3 -3
View File
@@ -1,6 +1,6 @@
{last_modified_time} Page 2
{last_modified_time} Page 2
57 57
@@ -66,7 +66,7 @@
{last_modified_time} Page 3
{last_modified_time} Page 3
113 113
@@ -132,7 +132,7 @@
{last_modified_time} Page 4
{last_modified_time} Page 4
169 169
+2 -2
View File
@@ -1,6 +1,6 @@
{last_modified_time} Page 1
{last_modified_time} Page 1
1 1 ## 1
@@ -100,7 +100,7 @@
{last_modified_time} Page 2
{last_modified_time} Page 2
91 91 91

Some files were not shown because too many files have changed in this diff Show More