Skip to content

Commit f0e0de7

Browse files
authored
Merge pull request #10059 from jfinkels/pr-header-spacing
pr: use 72 char line width for all page headers
2 parents 666c6df + bfdbf59 commit f0e0de7

31 files changed

+532
-171
lines changed

src/uu/pr/src/pr.rs

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,34 +1180,23 @@ fn header_content(options: &OutputOptions, page: usize) -> Vec<String> {
11801180
// Use the line width if available, otherwise use default of 72
11811181
let total_width = options.line_width.unwrap_or(DEFAULT_COLUMN_WIDTH);
11821182

1183-
// GNU pr uses a specific layout:
1184-
// Date takes up the left part, filename is centered, page is right-aligned
11851183
let date_len = date_part.chars().count();
11861184
let filename_len = filename.chars().count();
11871185
let page_len = page_part.chars().count();
11881186

11891187
let header_line = if date_len + filename_len + page_len + 2 < total_width {
1190-
// Check if we're using a custom date format that needs centered alignment
1191-
// This preserves backward compatibility while fixing the GNU time-style test
1192-
if date_part.starts_with('+') {
1193-
// GNU pr uses centered layout for headers with custom date formats
1194-
// The filename should be centered between the date and page parts
1195-
let space_for_filename = total_width - date_len - page_len;
1196-
let padding_before_filename = (space_for_filename - filename_len) / 2;
1197-
let padding_after_filename =
1198-
space_for_filename - filename_len - padding_before_filename;
1199-
1200-
format!(
1201-
"{date_part}{:width1$}{filename}{:width2$}{page_part}",
1202-
"",
1203-
"",
1204-
width1 = padding_before_filename,
1205-
width2 = padding_after_filename
1206-
)
1207-
} else {
1208-
// For standard date formats, use simple spacing for backward compatibility
1209-
format!("{date_part} {filename} {page_part}")
1210-
}
1188+
// The filename should be centered between the date and page parts
1189+
let space_for_filename = total_width - date_len - page_len;
1190+
let padding_before_filename = (space_for_filename - filename_len) / 2;
1191+
let padding_after_filename = space_for_filename - filename_len - padding_before_filename;
1192+
1193+
format!(
1194+
"{date_part}{:width1$}{filename}{:width2$}{page_part}",
1195+
"",
1196+
"",
1197+
width1 = padding_before_filename,
1198+
width2 = padding_after_filename
1199+
)
12111200
} else {
12121201
// If content is too long, just use single spaces
12131202
format!("{date_part} {filename} {page_part}")

tests/by-util/test_pr.rs

Lines changed: 70 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// spell-checker:ignore (ToDO) Sdivide
66

77
use chrono::{DateTime, Duration, Utc};
8+
use regex::Regex;
89
use std::fs::metadata;
910
use uutests::new_ucmd;
1011
use uutests::util::UCommand;
@@ -78,21 +79,22 @@ fn test_with_numbering_option_with_number_width() {
7879

7980
#[test]
8081
fn test_with_long_header_option() {
81-
let test_file_path = "test_one_page.log";
82-
let expected_test_file_path = "test_one_page_header.log.expected";
83-
let header = "new file";
84-
for args in [&["-h", header][..], &["--header=new file"][..]] {
85-
let mut scenario = new_ucmd!();
86-
let value = file_last_modified_time(&scenario, test_file_path);
87-
scenario
88-
.args(args)
89-
.arg(test_file_path)
90-
.succeeds()
91-
.stdout_is_templated_fixture(
92-
expected_test_file_path,
93-
&[("{last_modified_time}", &value), ("{header}", header)],
94-
);
95-
}
82+
let whitespace = " ".repeat(21);
83+
let blank_lines = "\n".repeat(61);
84+
let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d";
85+
let pattern =
86+
format!("\n\n{datetime_pattern}{whitespace}new file{whitespace}Page 1\n\n\na{blank_lines}");
87+
let regex = Regex::new(&pattern).unwrap();
88+
new_ucmd!()
89+
.args(&["-h", "new file"])
90+
.pipe_in("a")
91+
.succeeds()
92+
.stdout_matches(&regex);
93+
new_ucmd!()
94+
.args(&["--header=new file"])
95+
.pipe_in("a")
96+
.succeeds()
97+
.stdout_matches(&regex);
9698
}
9799

98100
#[test]
@@ -400,99 +402,92 @@ fn test_with_offset_space_option() {
400402

401403
#[test]
402404
fn test_with_date_format() {
403-
let test_file_path = "test_one_page.log";
404-
let expected_test_file_path = "test_one_page.log.expected";
405-
let mut scenario = new_ucmd!();
406-
let value = file_last_modified_time_format(&scenario, test_file_path, "%Y__%s");
407-
scenario
408-
.args(&[test_file_path, "-D", "%Y__%s"])
405+
let whitespace = " ".repeat(50);
406+
let blank_lines = "\n".repeat(61);
407+
let datetime_pattern = r"\d{4}__\d{10}";
408+
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
409+
let regex = Regex::new(&pattern).unwrap();
410+
new_ucmd!()
411+
.args(&["-D", "%Y__%s"])
412+
.pipe_in("a")
409413
.succeeds()
410-
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
414+
.stdout_matches(&regex);
411415

412416
// "Format" doesn't need to contain any replaceable token.
417+
let whitespace = " ".repeat(60);
418+
let blank_lines = "\n".repeat(61);
413419
new_ucmd!()
414-
.args(&[test_file_path, "-D", "Hello!"])
420+
.args(&["-D", "Hello!"])
421+
.pipe_in("a")
415422
.succeeds()
416-
.stdout_is_templated_fixture(
417-
expected_test_file_path,
418-
&[("{last_modified_time}", "Hello!")],
419-
);
423+
.stdout_only(format!("\n\nHello!{whitespace}Page 1\n\n\na{blank_lines}"));
420424

421425
// Long option also works
422426
new_ucmd!()
423-
.args(&[test_file_path, "--date-format=Hello!"])
427+
.args(&["--date-format=Hello!"])
428+
.pipe_in("a")
424429
.succeeds()
425-
.stdout_is_templated_fixture(
426-
expected_test_file_path,
427-
&[("{last_modified_time}", "Hello!")],
428-
);
430+
.stdout_only(format!("\n\nHello!{whitespace}Page 1\n\n\na{blank_lines}"));
429431

430432
// Option takes precedence over environment variables
431433
new_ucmd!()
432434
.env("POSIXLY_CORRECT", "1")
433435
.env("LC_TIME", "POSIX")
434-
.args(&[test_file_path, "-D", "Hello!"])
436+
.args(&["--date-format=Hello!"])
437+
.pipe_in("a")
435438
.succeeds()
436-
.stdout_is_templated_fixture(
437-
expected_test_file_path,
438-
&[("{last_modified_time}", "Hello!")],
439-
);
439+
.stdout_only(format!("\n\nHello!{whitespace}Page 1\n\n\na{blank_lines}"));
440440
}
441441

442442
#[test]
443443
fn test_with_date_format_env() {
444-
const POSIXLY_FORMAT: &str = "%b %e %H:%M %Y";
445-
446444
// POSIXLY_CORRECT + LC_ALL/TIME=POSIX uses "%b %e %H:%M %Y" date format
447-
let test_file_path = "test_one_page.log";
448-
let expected_test_file_path = "test_one_page.log.expected";
449-
let mut scenario = new_ucmd!();
450-
let value = file_last_modified_time_format(&scenario, test_file_path, POSIXLY_FORMAT);
451-
scenario
445+
let whitespace = " ".repeat(49);
446+
let blank_lines = "\n".repeat(61);
447+
let datetime_pattern = r"[A-Z][a-z][a-z] [ \d]\d \d\d:\d\d \d{4}";
448+
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
449+
let regex = Regex::new(&pattern).unwrap();
450+
new_ucmd!()
452451
.env("POSIXLY_CORRECT", "1")
453452
.env("LC_ALL", "POSIX")
454-
.args(&[test_file_path])
453+
.pipe_in("a")
455454
.succeeds()
456-
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
457-
458-
let mut scenario = new_ucmd!();
459-
let value = file_last_modified_time_format(&scenario, test_file_path, POSIXLY_FORMAT);
460-
scenario
455+
.stdout_matches(&regex);
456+
new_ucmd!()
461457
.env("POSIXLY_CORRECT", "1")
462458
.env("LC_TIME", "POSIX")
463-
.args(&[test_file_path])
459+
.pipe_in("a")
464460
.succeeds()
465-
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
461+
.stdout_matches(&regex);
466462

467463
// But not if POSIXLY_CORRECT/LC_ALL is something else.
468-
let mut scenario = new_ucmd!();
469-
let value = file_last_modified_time_format(&scenario, test_file_path, DATE_TIME_FORMAT_DEFAULT);
470-
scenario
464+
let whitespace = " ".repeat(50);
465+
let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d";
466+
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
467+
let regex = Regex::new(&pattern).unwrap();
468+
new_ucmd!()
471469
.env("LC_TIME", "POSIX")
472-
.args(&[test_file_path])
470+
.pipe_in("a")
473471
.succeeds()
474-
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
475-
476-
let mut scenario = new_ucmd!();
477-
let value = file_last_modified_time_format(&scenario, test_file_path, DATE_TIME_FORMAT_DEFAULT);
478-
scenario
472+
.stdout_matches(&regex);
473+
new_ucmd!()
479474
.env("POSIXLY_CORRECT", "1")
480475
.env("LC_TIME", "C")
481-
.args(&[test_file_path])
476+
.pipe_in("a")
482477
.succeeds()
483-
.stdout_is_templated_fixture(expected_test_file_path, &[("{last_modified_time}", &value)]);
478+
.stdout_matches(&regex);
484479
}
485480

486481
#[test]
487482
fn test_with_pr_core_utils_tests() {
488483
let test_cases = vec![
489484
("", vec!["0Ft"], vec!["0F"], 0),
490-
("", vec!["0Fnt"], vec!["0F"], 0),
485+
("", vec!["0Fnt"], vec!["0Fnt-expected"], 0),
491486
("+3", vec!["0Ft"], vec!["3-0F"], 0),
492487
("+3 -f", vec!["0Ft"], vec!["3f-0F"], 0),
493488
("-a -3", vec!["0Ft"], vec!["a3-0F"], 0),
494489
("-a -3 -f", vec!["0Ft"], vec!["a3f-0F"], 0),
495-
("-a -3 -f", vec!["0Fnt"], vec!["a3f-0F"], 0),
490+
("-a -3 -f", vec!["0Fnt"], vec!["a3f-0Fnt-expected"], 0),
496491
("+3 -a -3 -f", vec!["0Ft"], vec!["3a3f-0F"], 0),
497492
("-l 24", vec!["FnFn"], vec!["l24-FF"], 0),
498493
("-W 20 -l24 -f", vec!["tFFt-ll"], vec!["W20l24f-ll"], 0),
@@ -622,3 +617,13 @@ fn test_b_flag_backwards_compat() {
622617
// -b is a no-op for backwards compatibility (column-down is now the default)
623618
new_ucmd!().args(&["-b", "-t"]).pipe_in("a\nb\n").succeeds();
624619
}
620+
621+
#[test]
622+
fn test_page_header_width() {
623+
let whitespace = " ".repeat(50);
624+
let blank_lines = "\n".repeat(61);
625+
let datetime_pattern = r"\d\d\d\d-\d\d-\d\d \d\d:\d\d";
626+
let pattern = format!("\n\n{datetime_pattern}{whitespace}Page 1\n\n\na{blank_lines}");
627+
let regex = Regex::new(&pattern).unwrap();
628+
new_ucmd!().pipe_in("a").succeeds().stdout_matches(&regex);
629+
}

tests/fixtures/pr/0F

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11

22

3-
{last_modified_time} {file_name} Page 1
3+
{last_modified_time} {file_name} Page 1
44

55

66

@@ -66,7 +66,7 @@
6666

6767

6868

69-
{last_modified_time} {file_name} Page 2
69+
{last_modified_time} {file_name} Page 2
7070

7171

7272
1 FF-Test: FF's at Start of File V
@@ -132,7 +132,7 @@
132132

133133

134134

135-
{last_modified_time} {file_name} Page 3
135+
{last_modified_time} {file_name} Page 3
136136

137137

138138

@@ -198,7 +198,7 @@
198198

199199

200200

201-
{last_modified_time} {file_name} Page 4
201+
{last_modified_time} {file_name} Page 4
202202

203203

204204
15 xyzxyzxyz XYZXYZXYZ abcabcab
@@ -264,7 +264,7 @@
264264

265265

266266

267-
{last_modified_time} {file_name} Page 5
267+
{last_modified_time} {file_name} Page 5
268268

269269

270270
29 xyzxyzxyz XYZXYZXYZ abcabcab

0 commit comments

Comments
 (0)