1use std::io::{self, BufRead, Write};
2use std::time::{SystemTime, UNIX_EPOCH};
3
4pub const DEFAULT_PAGE_LENGTH: usize = 66;
6pub const DEFAULT_PAGE_WIDTH: usize = 72;
8pub const HEADER_LINES: usize = 5;
10pub const FOOTER_LINES: usize = 5;
12
13#[derive(Clone)]
15pub struct PrConfig {
16 pub first_page: usize,
18 pub last_page: usize,
20 pub columns: usize,
22 pub across: bool,
24 pub show_control_chars: bool,
26 pub double_space: bool,
28 pub date_format: String,
30 pub expand_tabs: Option<(char, usize)>,
32 pub form_feed: bool,
34 pub header: Option<String>,
36 pub output_tabs: Option<(char, usize)>,
38 pub join_lines: bool,
40 pub page_length: usize,
42 pub merge: bool,
44 pub number_lines: Option<(char, usize)>,
46 pub first_line_number: usize,
48 pub indent: usize,
50 pub no_file_warnings: bool,
52 pub separator: Option<char>,
54 pub sep_string: Option<String>,
56 pub omit_header: bool,
58 pub omit_pagination: bool,
60 pub show_nonprinting: bool,
62 pub page_width: usize,
64 pub truncate_lines: bool,
66}
67
68impl Default for PrConfig {
69 fn default() -> Self {
70 Self {
71 first_page: 1,
72 last_page: 0,
73 columns: 1,
74 across: false,
75 show_control_chars: false,
76 double_space: false,
77 date_format: "%Y-%m-%d %H:%M".to_string(),
78 expand_tabs: None,
79 form_feed: false,
80 header: None,
81 output_tabs: None,
82 join_lines: false,
83 page_length: DEFAULT_PAGE_LENGTH,
84 merge: false,
85 number_lines: None,
86 first_line_number: 1,
87 indent: 0,
88 no_file_warnings: false,
89 separator: None,
90 sep_string: None,
91 omit_header: false,
92 omit_pagination: false,
93 show_nonprinting: false,
94 page_width: DEFAULT_PAGE_WIDTH,
95 truncate_lines: false,
96 }
97 }
98}
99
100fn format_header_date(time: &SystemTime, format: &str) -> String {
102 let secs = time
103 .duration_since(UNIX_EPOCH)
104 .unwrap_or_default()
105 .as_secs() as i64;
106 let mut tm: libc::tm = unsafe { std::mem::zeroed() };
107 unsafe {
108 libc::localtime_r(&secs, &mut tm);
109 }
110
111 let c_format = std::ffi::CString::new(format).unwrap_or_default();
113 let mut buf = vec![0u8; 256];
114 let len = unsafe {
115 libc::strftime(
116 buf.as_mut_ptr() as *mut libc::c_char,
117 buf.len(),
118 c_format.as_ptr(),
119 &tm,
120 )
121 };
122 if len == 0 {
123 return String::new();
124 }
125 buf.truncate(len);
126 String::from_utf8_lossy(&buf).into_owned()
127}
128
129fn expand_tabs_in_line(line: &str, tab_char: char, tab_width: usize) -> String {
131 if tab_width == 0 {
132 return line.replace(tab_char, "");
133 }
134 let mut result = String::with_capacity(line.len() + line.len() / 4);
136 let tab_byte = tab_char as u8;
137 let bytes = line.as_bytes();
138 let mut col = 0;
139 let mut seg_start = 0;
140
141 for (i, &b) in bytes.iter().enumerate() {
142 if b == tab_byte {
143 if i > seg_start {
145 result.push_str(&line[seg_start..i]);
146 col += i - seg_start;
147 }
148 let spaces = tab_width - (col % tab_width);
149 let space_buf = " ";
151 let mut remaining = spaces;
152 while remaining > 0 {
153 let chunk = remaining.min(space_buf.len());
154 result.push_str(&space_buf[..chunk]);
155 remaining -= chunk;
156 }
157 col += spaces;
158 seg_start = i + 1;
159 }
160 }
161 if seg_start < bytes.len() {
163 result.push_str(&line[seg_start..]);
164 }
165 result
166}
167
168#[inline]
170fn push_hat_notation(result: &mut String, ch: char) {
171 let b = ch as u32;
172 if b < 32 {
173 result.push('^');
174 result.push((b as u8 + b'@') as char);
175 } else if b == 127 {
176 result.push_str("^?");
177 } else {
178 result.push(ch);
179 }
180}
181
182#[inline]
184fn push_nonprinting(result: &mut String, ch: char) {
185 let b = ch as u32;
186 if b < 32 && b != 9 && b != 10 {
187 result.push('^');
188 result.push((b as u8 + b'@') as char);
189 } else if b == 127 {
190 result.push_str("^?");
191 } else if b >= 128 && b < 160 {
192 result.push_str("M-^");
193 result.push((b as u8 - 128 + b'@') as char);
194 } else if b >= 160 && b < 255 {
195 result.push_str("M-");
196 result.push((b as u8 - 128) as char);
197 } else if b == 255 {
198 result.push_str("M-^?");
199 } else {
200 result.push(ch);
201 }
202}
203
204fn process_control_chars(line: &str, show_control: bool, show_nonprinting: bool) -> String {
206 if !show_control && !show_nonprinting {
207 return line.to_string();
208 }
209 let mut result = String::with_capacity(line.len() + line.len() / 4);
210 for ch in line.chars() {
211 if show_nonprinting {
212 push_nonprinting(&mut result, ch);
213 } else if show_control {
214 push_hat_notation(&mut result, ch);
215 } else {
216 result.push(ch);
217 }
218 }
219 result
220}
221
222fn get_column_separator(config: &PrConfig) -> String {
224 if let Some(ref s) = config.sep_string {
225 s.clone()
226 } else if let Some(c) = config.separator {
227 c.to_string()
228 } else {
229 " ".to_string()
230 }
231}
232
233fn has_explicit_separator(config: &PrConfig) -> bool {
235 config.sep_string.is_some() || config.separator.is_some()
236}
237
238const SPACES: [u8; 256] = [b' '; 256];
244
245#[inline]
247fn write_spaces<W: Write>(output: &mut W, n: usize) -> io::Result<()> {
248 let mut remaining = n;
249 while remaining > 0 {
250 let chunk = remaining.min(SPACES.len());
251 output.write_all(&SPACES[..chunk])?;
252 remaining -= chunk;
253 }
254 Ok(())
255}
256
257fn write_column_padding<W: Write>(
258 output: &mut W,
259 abs_pos: usize,
260 target_abs_pos: usize,
261) -> io::Result<()> {
262 let n = target_abs_pos.saturating_sub(abs_pos);
266 if n == 0 {
267 return Ok(());
268 }
269 let next_tab = (abs_pos / 8 + 1) * 8;
270 if next_tab > target_abs_pos {
271 return write_spaces(output, n);
273 }
274 output.write_all(b"\t")?;
276 let mut col = next_tab;
277 while col < target_abs_pos {
278 let nt = (col / 8 + 1) * 8;
279 if nt <= target_abs_pos {
280 output.write_all(b"\t")?;
281 col = nt;
282 } else {
283 write_spaces(output, target_abs_pos - col)?;
284 col = target_abs_pos;
285 }
286 }
287 Ok(())
288}
289
290pub fn pr_data<W: Write>(
294 data: &[u8],
295 output: &mut W,
296 config: &PrConfig,
297 filename: &str,
298 file_date: Option<SystemTime>,
299) -> io::Result<()> {
300 let needs_transform =
301 config.expand_tabs.is_some() || config.show_control_chars || config.show_nonprinting;
302
303 if needs_transform {
304 let reader = io::Cursor::new(data);
306 return pr_file(reader, output, config, filename, file_date);
307 }
308
309 let has_cr_or_ff = memchr::memchr2(b'\r', b'\x0c', data).is_some();
312
313 let is_simple = config.columns <= 1
315 && config.number_lines.is_none()
316 && config.indent == 0
317 && !config.truncate_lines
318 && !config.double_space
319 && !config.across
320 && !has_cr_or_ff;
321
322 if is_simple {
323 if config.omit_pagination && config.first_page == 1 && config.last_page == 0 {
325 return output.write_all(data);
326 }
327 return pr_data_contiguous(data, output, config, filename, file_date);
328 }
329
330 if config.columns <= 1
332 && config.number_lines.is_some()
333 && config.indent == 0
334 && !config.truncate_lines
335 && !config.double_space
336 && !has_cr_or_ff
337 {
338 return pr_data_numbered(data, output, config, filename, file_date);
339 }
340
341 if config.columns > 1
343 && config.columns <= 32
344 && !config.across
345 && config.number_lines.is_none()
346 && config.indent == 0
347 && !config.double_space
348 && !config.join_lines
349 && data.len() <= u32::MAX as usize
350 && !has_cr_or_ff
351 {
352 return pr_data_multicolumn_fast(data, output, config, filename, file_date);
353 }
354
355 let mut lines: Vec<&[u8]> = Vec::with_capacity(data.len() / 40 + 64);
357 let mut start = 0;
358 for pos in memchr::memchr_iter(b'\n', data) {
359 let end = if pos > start && data[pos - 1] == b'\r' {
360 pos - 1
361 } else {
362 pos
363 };
364 lines.push(&data[start..end]);
365 start = pos + 1;
366 }
367 if start < data.len() {
369 let end = if data.last() == Some(&b'\r') {
370 data.len() - 1
371 } else {
372 data.len()
373 };
374 lines.push(&data[start..end]);
375 }
376
377 pr_lines_generic(&lines, output, config, filename, file_date)
378}
379
380fn pr_data_multicolumn_fast<W: Write>(
385 data: &[u8],
386 output: &mut W,
387 config: &PrConfig,
388 filename: &str,
389 file_date: Option<SystemTime>,
390) -> io::Result<()> {
391 let date = file_date.unwrap_or_else(SystemTime::now);
392 let header_str = config.header.as_deref().unwrap_or(filename);
393 let date_str = format_header_date(&date, &config.date_format);
394
395 let columns = config.columns.max(1);
396 let explicit_sep = has_explicit_separator(config);
397 let col_sep = get_column_separator(config);
398 let col_sep_bytes = col_sep.as_bytes();
399 let col_width = if explicit_sep {
400 if columns > 1 {
401 (config
402 .page_width
403 .saturating_sub(col_sep.len() * (columns - 1)))
404 / columns
405 } else {
406 config.page_width
407 }
408 } else {
409 config.page_width / columns
410 };
411 let content_width = if explicit_sep {
412 col_width
413 } else {
414 col_width.saturating_sub(1)
415 };
416
417 let suppress_header = !config.omit_header
418 && !config.omit_pagination
419 && config.page_length <= HEADER_LINES + FOOTER_LINES;
420 let body_lines_per_page = if config.omit_header || config.omit_pagination {
421 if config.page_length > 0 {
422 config.page_length
423 } else {
424 DEFAULT_PAGE_LENGTH
425 }
426 } else if suppress_header {
427 config.page_length
428 } else {
429 config.page_length - HEADER_LINES - FOOTER_LINES
430 };
431 let show_header = !config.omit_header && !config.omit_pagination && !suppress_header;
432 let lines_consumed_per_page = body_lines_per_page * columns;
433
434 let mut line_offsets: Vec<(u32, u32)> = Vec::with_capacity(data.len() / 40 + 64);
436 let mut start = 0usize;
437 for pos in memchr::memchr_iter(b'\n', data) {
438 line_offsets.push((start as u32, (pos - start) as u32));
439 start = pos + 1;
440 }
441 if start < data.len() {
442 line_offsets.push((start as u32, (data.len() - start) as u32));
443 }
444 let total_lines = line_offsets.len();
445
446 let out_cap = (data.len() + data.len() / 3 + 4096).min(64 * 1024 * 1024);
448 let mut out_buf: Vec<u8> = Vec::with_capacity(out_cap);
449
450 let mut line_idx = 0usize;
451 let mut page_num = 1usize;
452 let src = data.as_ptr();
453
454 while line_idx < total_lines || (line_idx == 0 && total_lines == 0) {
455 if total_lines == 0 {
456 if show_header
457 && page_num >= config.first_page
458 && (config.last_page == 0 || page_num <= config.last_page)
459 {
460 write_header(&mut out_buf, &date_str, header_str, page_num, config)?;
461 write_footer(&mut out_buf, config)?;
462 }
463 break;
464 }
465
466 let page_end = (line_idx + lines_consumed_per_page).min(total_lines);
467 let page_lines = &line_offsets[line_idx..page_end];
468 let n = page_lines.len();
469
470 if page_num >= config.first_page && (config.last_page == 0 || page_num <= config.last_page)
471 {
472 if show_header {
473 write_header(&mut out_buf, &date_str, header_str, page_num, config)?;
474 }
475
476 let base = n / columns;
478 let extra = n % columns;
479 let mut col_starts = [0u32; 33];
481 let max_cols = columns;
482 for col in 0..max_cols {
483 let col_lines = base + if col < extra { 1 } else { 0 };
484 col_starts[col + 1] = col_starts[col] + col_lines as u32;
485 }
486 let num_rows = if extra > 0 { base + 1 } else { base };
487
488 for row in 0..num_rows {
490 let mut last_data_col = 0;
492 for col in 0..max_cols {
493 let col_lines = (col_starts[col + 1] - col_starts[col]) as usize;
494 if row < col_lines {
495 last_data_col = col;
496 }
497 }
498
499 let mut abs_pos = 0usize;
500 for col in 0..max_cols {
501 let col_lines = (col_starts[col + 1] - col_starts[col]) as usize;
502 if row < col_lines {
503 let li = col_starts[col] as usize + row;
504 let (off, len) = page_lines[li];
505 let content_len = (len as usize).min(content_width);
506
507 if explicit_sep && col > 0 {
508 out_buf.extend_from_slice(col_sep_bytes);
509 abs_pos += col_sep_bytes.len();
510 }
511
512 if content_len > 0 {
521 let wp = out_buf.len();
522 out_buf.reserve(content_len);
523 unsafe {
524 std::ptr::copy_nonoverlapping(
525 src.add(off as usize),
526 out_buf.as_mut_ptr().add(wp),
527 content_len,
528 );
529 out_buf.set_len(wp + content_len);
530 }
531 }
532
533 if col == last_data_col && !explicit_sep {
535 while out_buf.last() == Some(&b' ') {
536 out_buf.pop();
537 }
538 }
539
540 abs_pos += content_len;
544
545 if col < last_data_col && !explicit_sep {
547 let target = (col + 1) * col_width;
548 if abs_pos < target {
549 write_column_padding_buf(&mut out_buf, abs_pos, target);
550 abs_pos = target;
551 }
552 }
553 } else if col <= last_data_col {
554 if explicit_sep {
556 if col > 0 {
557 out_buf.extend_from_slice(col_sep_bytes);
558 abs_pos += col_sep_bytes.len();
559 }
560 } else {
561 let target = (col + 1) * col_width;
562 write_column_padding_buf(&mut out_buf, abs_pos, target);
563 abs_pos = target;
564 }
565 }
566 }
567 out_buf.push(b'\n');
568 }
569
570 let body_lines_written = num_rows;
572 if show_header {
573 let pad = body_lines_per_page.saturating_sub(body_lines_written);
574 out_buf.resize(out_buf.len() + pad, b'\n');
575 write_footer(&mut out_buf, config)?;
576 }
577
578 if out_buf.len() >= 64 * 1024 * 1024 {
579 output.write_all(&out_buf)?;
580 out_buf.clear();
581 }
582 }
583
584 line_idx = page_end;
585 page_num += 1;
586 }
587
588 if !out_buf.is_empty() {
589 output.write_all(&out_buf)?;
590 }
591 Ok(())
592}
593
594#[inline]
597fn write_column_padding_buf(buf: &mut Vec<u8>, abs_pos: usize, target: usize) {
598 let n = target.saturating_sub(abs_pos);
599 if n == 0 {
600 return;
601 }
602 let next_tab = (abs_pos / 8 + 1) * 8;
603 if next_tab > target {
604 buf.resize(buf.len() + n, b' ');
606 return;
607 }
608 buf.push(b'\t');
609 let mut col = next_tab;
610 while col < target {
611 let nt = (col / 8 + 1) * 8;
612 if nt <= target {
613 buf.push(b'\t');
614 col = nt;
615 } else {
616 buf.resize(buf.len() + (target - col), b' ');
617 col = target;
618 }
619 }
620}
621
622#[inline(always)]
628fn count_newlines_u64(word: u64) -> u32 {
629 let b = word.to_ne_bytes();
630 (b[0] == b'\n') as u32
631 + (b[1] == b'\n') as u32
632 + (b[2] == b'\n') as u32
633 + (b[3] == b'\n') as u32
634 + (b[4] == b'\n') as u32
635 + (b[5] == b'\n') as u32
636 + (b[6] == b'\n') as u32
637 + (b[7] == b'\n') as u32
638}
639
640fn pr_data_contiguous<W: Write>(
645 data: &[u8],
646 output: &mut W,
647 config: &PrConfig,
648 filename: &str,
649 file_date: Option<SystemTime>,
650) -> io::Result<()> {
651 let date = file_date.unwrap_or_else(SystemTime::now);
652 let header_str = config.header.as_deref().unwrap_or(filename);
653 let date_str = format_header_date(&date, &config.date_format);
654
655 let suppress_header = !config.omit_header
656 && !config.omit_pagination
657 && config.page_length <= HEADER_LINES + FOOTER_LINES;
658 let body_lines_per_page = if config.omit_header || config.omit_pagination {
659 if config.page_length > 0 {
660 config.page_length
661 } else {
662 DEFAULT_PAGE_LENGTH
663 }
664 } else if suppress_header {
665 config.page_length
666 } else {
667 config.page_length - HEADER_LINES - FOOTER_LINES
668 };
669 let show_header = !config.omit_header && !config.omit_pagination && !suppress_header;
670
671 if data.is_empty() {
672 if show_header {
673 let mut page_buf: Vec<u8> = Vec::with_capacity(256);
674 write_header(&mut page_buf, &date_str, header_str, 1, config)?;
675 write_footer(&mut page_buf, config)?;
676 output.write_all(&page_buf)?;
677 }
678 return Ok(());
679 }
680
681 let est_pages = data.len() / (body_lines_per_page * 40) + 2;
687 let mut page_bounds: Vec<usize> = Vec::with_capacity(est_pages + 1);
688 let mut page_line_counts: Vec<usize> = Vec::with_capacity(est_pages);
689 page_bounds.push(0);
690 let mut lines_count = 0usize;
691 let block_size = 64usize;
692 let mut block_start = 0usize;
693 let ptr = data.as_ptr();
694 while block_start + block_size <= data.len() {
695 let mut block_nl = 0u32;
696 unsafe {
697 for i in 0..8 {
698 let word = std::ptr::read_unaligned(ptr.add(block_start + i * 8) as *const u64);
699 block_nl += count_newlines_u64(word);
700 }
701 }
702 let block_nl = block_nl as usize;
703 if lines_count + block_nl < body_lines_per_page {
704 lines_count += block_nl;
705 block_start += block_size;
706 } else {
707 for nl_pos in memchr::memchr_iter(b'\n', &data[block_start..block_start + block_size]) {
708 lines_count += 1;
709 if lines_count >= body_lines_per_page {
710 page_bounds.push(block_start + nl_pos + 1);
711 page_line_counts.push(lines_count);
712 lines_count = 0;
713 }
714 }
715 block_start += block_size;
716 }
717 }
718 for nl_pos in memchr::memchr_iter(b'\n', &data[block_start..]) {
720 lines_count += 1;
721 if lines_count >= body_lines_per_page {
722 page_bounds.push(block_start + nl_pos + 1);
723 page_line_counts.push(lines_count);
724 lines_count = 0;
725 }
726 }
727 if *page_bounds.last().unwrap() < data.len() {
728 page_bounds.push(data.len());
729 page_line_counts.push(lines_count);
730 }
731 let total_pages = page_bounds.len() - 1;
732
733 let first_visible = config.first_page.max(1) - 1;
734 let last_visible = if config.last_page == 0 {
735 total_pages
736 } else {
737 config.last_page.min(total_pages)
738 };
739 if first_visible >= total_pages {
740 return Ok(());
741 }
742
743 if !show_header {
744 let start = page_bounds[first_visible];
746 let end = page_bounds[last_visible];
747 return output.write_all(&data[start..end]);
748 }
749
750 let footer_bytes: &[u8] = if config.form_feed {
755 b"\x0c"
756 } else {
757 b"\n\n\n\n\n"
758 };
759 let date_bytes = date_str.as_bytes();
760 let header_bytes = header_str.as_bytes();
761 let line_width = config.page_width;
762 let left_len = date_bytes.len();
763 let center_len = header_bytes.len();
764 let page_prefix = b"Page ";
765 let flush_threshold = 4 * 1024 * 1024;
766 let mut out_buf: Vec<u8> = Vec::with_capacity(flush_threshold + 256 * 1024);
767 let mut num_tmp = [0u8; 20];
768
769 for pi in first_visible..last_visible {
770 let page_num = pi + 1;
771 let body_start = page_bounds[pi];
772 let body_end = page_bounds[pi + 1];
773
774 let mut n = page_num;
776 let mut pos = 19usize;
777 loop {
778 num_tmp[pos] = b'0' + (n % 10) as u8;
779 n /= 10;
780 if n == 0 {
781 break;
782 }
783 pos -= 1;
784 }
785 let num_digits = 20 - pos;
786 let right_len = page_prefix.len() + num_digits;
787
788 let overflow = left_len + center_len + right_len + 2 >= line_width;
791
792 if overflow {
793 out_buf.extend_from_slice(b"\n\n");
794 out_buf.extend_from_slice(date_bytes);
795 out_buf.push(b' ');
796 out_buf.extend_from_slice(header_bytes);
797 out_buf.push(b' ');
798 out_buf.extend_from_slice(page_prefix);
799 out_buf.extend_from_slice(&num_tmp[pos..20]);
800 out_buf.extend_from_slice(b"\n\n\n");
801 } else {
802 let total_spaces = line_width - left_len - center_len - right_len;
806 let left_spaces = total_spaces / 2;
807 let right_spaces = total_spaces - left_spaces;
808 let hdr_len = 2
809 + left_len
810 + left_spaces
811 + center_len
812 + right_spaces
813 + page_prefix.len()
814 + num_digits
815 + 3;
816 out_buf.reserve(hdr_len);
817 let wp = out_buf.len();
818 unsafe {
819 let dst = out_buf.as_mut_ptr().add(wp);
820 *dst = b'\n';
821 *dst.add(1) = b'\n';
822 let mut off = 2;
823 std::ptr::copy_nonoverlapping(date_bytes.as_ptr(), dst.add(off), left_len);
824 off += left_len;
825 std::ptr::write_bytes(dst.add(off), b' ', left_spaces);
826 off += left_spaces;
827 std::ptr::copy_nonoverlapping(header_bytes.as_ptr(), dst.add(off), center_len);
828 off += center_len;
829 std::ptr::write_bytes(dst.add(off), b' ', right_spaces);
830 off += right_spaces;
831 std::ptr::copy_nonoverlapping(
832 page_prefix.as_ptr(),
833 dst.add(off),
834 page_prefix.len(),
835 );
836 off += page_prefix.len();
837 std::ptr::copy_nonoverlapping(num_tmp.as_ptr().add(pos), dst.add(off), num_digits);
838 off += num_digits;
839 *dst.add(off) = b'\n';
840 *dst.add(off + 1) = b'\n';
841 *dst.add(off + 2) = b'\n';
842 off += 3;
843 out_buf.set_len(wp + off);
844 }
845 }
846
847 out_buf.extend_from_slice(&data[body_start..body_end]);
848
849 let actual_lines = page_line_counts[pi];
850 let has_unterminated = body_end > body_start && data[body_end - 1] != b'\n';
851 if has_unterminated {
852 out_buf.push(b'\n');
853 }
854 let effective_lines = actual_lines + has_unterminated as usize;
855 let pad = body_lines_per_page.saturating_sub(effective_lines);
856 out_buf.resize(out_buf.len() + pad, b'\n');
857 out_buf.extend_from_slice(footer_bytes);
858
859 if out_buf.len() >= flush_threshold {
860 output.write_all(&out_buf)?;
861 out_buf.clear();
862 }
863 }
864
865 if !out_buf.is_empty() {
866 output.write_all(&out_buf)?;
867 }
868 Ok(())
869}
870
871fn pr_data_numbered<W: Write>(
875 data: &[u8],
876 output: &mut W,
877 config: &PrConfig,
878 filename: &str,
879 file_date: Option<SystemTime>,
880) -> io::Result<()> {
881 let date = file_date.unwrap_or_else(SystemTime::now);
882 let header_str = config.header.as_deref().unwrap_or(filename);
883 let date_str = format_header_date(&date, &config.date_format);
884
885 let (sep_char, digits) = config.number_lines.unwrap_or(('\t', 5));
886 debug_assert!(sep_char.is_ascii(), "number separator must be ASCII");
887 let sep_byte = sep_char as u8;
888 let suppress_header = !config.omit_header
889 && !config.omit_pagination
890 && config.page_length <= HEADER_LINES + FOOTER_LINES;
891 let body_lines_per_page = if config.omit_header || config.omit_pagination {
892 if config.page_length > 0 {
893 config.page_length
894 } else {
895 DEFAULT_PAGE_LENGTH
896 }
897 } else if suppress_header {
898 config.page_length
899 } else {
900 config.page_length - HEADER_LINES - FOOTER_LINES
901 };
902 let show_header = !config.omit_header && !config.omit_pagination && !suppress_header;
903
904 let mut line_starts: Vec<usize> = Vec::with_capacity(data.len() / 40 + 64);
906 line_starts.push(0);
907 for pos in memchr::memchr_iter(b'\n', data) {
908 line_starts.push(pos + 1);
909 }
910 let total_lines = if !data.is_empty() && data[data.len() - 1] == b'\n' {
911 line_starts.len() - 1
912 } else {
913 line_starts.len()
914 };
915
916 let num_prefix_est = digits + 2; let out_cap =
920 (data.len() + total_lines * num_prefix_est + total_lines / 5 + 4096).min(64 * 1024 * 1024);
921 let mut out_buf: Vec<u8> = Vec::with_capacity(out_cap);
922
923 let mut line_number = config.first_line_number;
924 let mut page_num = 1usize;
925 let mut line_idx = 0;
926
927 while line_idx < total_lines {
928 let page_end = (line_idx + body_lines_per_page).min(total_lines);
929 let in_range = page_num >= config.first_page
930 && (config.last_page == 0 || page_num <= config.last_page);
931
932 if in_range {
933 if show_header {
934 write_header(&mut out_buf, &date_str, header_str, page_num, config)?;
935 }
936
937 let src = data.as_ptr();
942 for li in line_idx..page_end {
943 let line_start = line_starts[li];
944 let line_end = if li + 1 < line_starts.len() {
945 let end = line_starts[li + 1] - 1;
946 if end > line_start && data[end - 1] == b'\r' {
947 end - 1
948 } else {
949 end
950 }
951 } else {
952 data.len()
953 };
954 let line_len = line_end - line_start;
955
956 let wp = out_buf.len();
957
958 let mut n = line_number;
959 let mut num_pos = 19usize;
960 let mut num_tmp = [0u8; 20];
961 loop {
962 num_tmp[num_pos] = b'0' + (n % 10) as u8;
963 n /= 10;
964 if n == 0 || num_pos == 0 {
965 break;
966 }
967 num_pos -= 1;
968 }
969 let num_digits = 20 - num_pos;
970 let padding = digits.saturating_sub(num_digits);
971 let actual_prefix = padding + num_digits + 1;
972
973 let needed = actual_prefix + line_len + 1;
974 if out_buf.len() + needed > out_buf.capacity() {
975 out_buf.reserve(needed);
976 }
977 let base = out_buf.as_mut_ptr();
978
979 unsafe {
980 let dst = base.add(wp);
981 std::ptr::write_bytes(dst, b' ', padding);
982 std::ptr::copy_nonoverlapping(
983 num_tmp.as_ptr().add(num_pos),
984 dst.add(padding),
985 num_digits,
986 );
987 *dst.add(padding + num_digits) = sep_byte;
988 if line_len > 0 {
989 std::ptr::copy_nonoverlapping(
990 src.add(line_start),
991 dst.add(actual_prefix),
992 line_len,
993 );
994 }
995 *dst.add(actual_prefix + line_len) = b'\n';
996 out_buf.set_len(wp + actual_prefix + line_len + 1);
997 }
998
999 line_number += 1;
1000 }
1001
1002 if show_header {
1003 let body_lines_written = page_end - line_idx;
1004 let pad = body_lines_per_page.saturating_sub(body_lines_written);
1005 out_buf.resize(out_buf.len() + pad, b'\n');
1006 write_footer(&mut out_buf, config)?;
1007 }
1008
1009 if out_buf.len() >= 64 * 1024 * 1024 {
1011 output.write_all(&out_buf)?;
1012 out_buf.clear();
1013 }
1014 } else {
1015 line_number += page_end - line_idx;
1016 }
1017
1018 line_idx = page_end;
1019 page_num += 1;
1020 }
1021
1022 if !out_buf.is_empty() {
1023 output.write_all(&out_buf)?;
1024 }
1025 Ok(())
1026}
1027
1028pub fn pr_file<R: BufRead, W: Write>(
1030 input: R,
1031 output: &mut W,
1032 config: &PrConfig,
1033 filename: &str,
1034 file_date: Option<SystemTime>,
1035) -> io::Result<()> {
1036 let mut all_lines: Vec<String> = Vec::new();
1038 for line_result in input.lines() {
1039 let line = line_result?;
1040 let mut line = line;
1041
1042 if let Some((tab_char, tab_width)) = config.expand_tabs {
1044 line = expand_tabs_in_line(&line, tab_char, tab_width);
1045 }
1046
1047 if config.show_control_chars || config.show_nonprinting {
1049 line = process_control_chars(&line, config.show_control_chars, config.show_nonprinting);
1050 }
1051
1052 all_lines.push(line);
1053 }
1054
1055 let refs: Vec<&[u8]> = all_lines.iter().map(|s| s.as_bytes()).collect();
1057 pr_lines_generic(&refs, output, config, filename, file_date)
1058}
1059
1060fn pr_lines_generic<W: Write>(
1062 all_lines: &[&[u8]],
1063 output: &mut W,
1064 config: &PrConfig,
1065 filename: &str,
1066 file_date: Option<SystemTime>,
1067) -> io::Result<()> {
1068 let date = file_date.unwrap_or_else(SystemTime::now);
1069
1070 let header_str = config.header.as_deref().unwrap_or(filename);
1071 let date_str = format_header_date(&date, &config.date_format);
1072
1073 let suppress_header = !config.omit_header
1077 && !config.omit_pagination
1078 && config.page_length <= HEADER_LINES + FOOTER_LINES;
1079 let suppressed_config;
1082 let effective_config = if suppress_header {
1083 suppressed_config = PrConfig {
1084 omit_header: true,
1085 ..config.clone()
1086 };
1087 &suppressed_config
1088 } else {
1089 config
1090 };
1091 let body_lines_per_page = if config.omit_header || config.omit_pagination {
1092 if config.page_length > 0 {
1093 config.page_length
1094 } else {
1095 DEFAULT_PAGE_LENGTH
1096 }
1097 } else if suppress_header {
1098 config.page_length
1099 } else {
1100 config.page_length - HEADER_LINES - FOOTER_LINES
1101 };
1102
1103 let input_lines_per_page = if config.double_space {
1105 (body_lines_per_page + 1) / 2
1106 } else {
1107 body_lines_per_page
1108 };
1109
1110 let columns = config.columns.max(1);
1112
1113 let lines_consumed_per_page = if columns > 1 && !config.across {
1118 input_lines_per_page * columns
1119 } else {
1120 input_lines_per_page
1121 };
1122
1123 let total_lines = all_lines.len();
1125 let mut line_number = config.first_line_number;
1126 let mut page_num = 1usize;
1127 let mut line_idx = 0;
1128 let total_bytes: usize = all_lines.iter().map(|l| l.len() + 1).sum();
1129 let out_cap = (total_bytes + total_bytes / 5 + 4096).min(64 * 1024 * 1024);
1131 let mut out_buf: Vec<u8> = Vec::with_capacity(out_cap);
1132
1133 while line_idx < total_lines || (line_idx == 0 && total_lines == 0) {
1134 if total_lines == 0 && line_idx == 0 {
1135 if page_num >= config.first_page
1136 && (config.last_page == 0 || page_num <= config.last_page)
1137 {
1138 if !config.omit_header && !config.omit_pagination && !suppress_header {
1139 write_header(&mut out_buf, &date_str, header_str, page_num, config)?;
1140 write_footer(&mut out_buf, config)?;
1141 }
1142 }
1143 break;
1144 }
1145
1146 let page_end = (line_idx + lines_consumed_per_page).min(total_lines);
1147
1148 if page_num >= config.first_page && (config.last_page == 0 || page_num <= config.last_page)
1149 {
1150 if !config.omit_header && !config.omit_pagination && !suppress_header {
1151 write_header(&mut out_buf, &date_str, header_str, page_num, config)?;
1152 }
1153
1154 if columns > 1 {
1155 write_multicolumn_body(
1156 &mut out_buf,
1157 &all_lines[line_idx..page_end],
1158 effective_config,
1159 columns,
1160 &mut line_number,
1161 body_lines_per_page,
1162 )?;
1163 } else {
1164 write_single_column_body(
1165 &mut out_buf,
1166 &all_lines[line_idx..page_end],
1167 effective_config,
1168 &mut line_number,
1169 body_lines_per_page,
1170 )?;
1171 }
1172
1173 if !config.omit_header && !config.omit_pagination && !suppress_header {
1174 write_footer(&mut out_buf, config)?;
1175 }
1176
1177 if out_buf.len() >= 64 * 1024 * 1024 {
1179 output.write_all(&out_buf)?;
1180 out_buf.clear();
1181 }
1182 }
1183
1184 line_idx = page_end;
1185 page_num += 1;
1186
1187 if line_idx >= total_lines {
1188 break;
1189 }
1190 }
1191
1192 if !out_buf.is_empty() {
1193 output.write_all(&out_buf)?;
1194 }
1195 Ok(())
1196}
1197
1198pub fn pr_merge<W: Write>(
1200 inputs: &[Vec<String>],
1201 output: &mut W,
1202 config: &PrConfig,
1203 _filenames: &[&str],
1204 file_dates: &[SystemTime],
1205) -> io::Result<()> {
1206 let date = file_dates.first().copied().unwrap_or_else(SystemTime::now);
1207 let date_str = format_header_date(&date, &config.date_format);
1208 let header_str = config.header.as_deref().unwrap_or("");
1209
1210 let suppress_header = !config.omit_header
1211 && !config.omit_pagination
1212 && config.page_length <= HEADER_LINES + FOOTER_LINES;
1213 let body_lines_per_page = if config.omit_header || config.omit_pagination {
1214 if config.page_length > 0 {
1215 config.page_length
1216 } else {
1217 DEFAULT_PAGE_LENGTH
1218 }
1219 } else if suppress_header {
1220 config.page_length
1221 } else {
1222 config.page_length - HEADER_LINES - FOOTER_LINES
1223 };
1224
1225 let input_lines_per_page = if config.double_space {
1226 (body_lines_per_page + 1) / 2
1227 } else {
1228 body_lines_per_page
1229 };
1230
1231 let num_files = inputs.len();
1232 let explicit_sep = has_explicit_separator(config);
1233 let col_sep = get_column_separator(config);
1234 let col_width = if explicit_sep {
1235 if num_files > 1 {
1236 (config
1237 .page_width
1238 .saturating_sub(col_sep.len() * (num_files - 1)))
1239 / num_files
1240 } else {
1241 config.page_width
1242 }
1243 } else {
1244 config.page_width / num_files
1245 };
1246
1247 let max_lines = inputs.iter().map(|f| f.len()).max().unwrap_or(0);
1248 let mut page_num = 1usize;
1249 let mut line_idx = 0;
1250 let mut line_number = config.first_line_number;
1251
1252 let col_sep_bytes = col_sep.as_bytes();
1253 let mut page_buf: Vec<u8> = Vec::with_capacity(128 * 1024);
1254 let mut num_buf = [0u8; 32];
1255
1256 while line_idx < max_lines {
1257 let page_end = (line_idx + input_lines_per_page).min(max_lines);
1258
1259 if page_num >= config.first_page && (config.last_page == 0 || page_num <= config.last_page)
1260 {
1261 page_buf.clear();
1262
1263 if !config.omit_header && !config.omit_pagination && !suppress_header {
1264 write_header(&mut page_buf, &date_str, header_str, page_num, config)?;
1265 }
1266
1267 let indent_str = " ".repeat(config.indent);
1268 let mut body_lines_written = 0;
1269 for i in line_idx..page_end {
1270 if config.double_space && body_lines_written > 0 {
1271 page_buf.push(b'\n');
1272 body_lines_written += 1;
1273 }
1274
1275 page_buf.extend_from_slice(indent_str.as_bytes());
1276 let mut abs_pos = config.indent;
1277
1278 if let Some((sep, digits)) = config.number_lines {
1279 let num_str = format_line_number(line_number, sep, digits, &mut num_buf);
1280 page_buf.extend_from_slice(num_str);
1281 abs_pos += digits + 1;
1282 line_number += 1;
1283 }
1284
1285 for (fi, file_lines) in inputs.iter().enumerate() {
1286 let content = if i < file_lines.len() {
1287 file_lines[i].as_bytes()
1288 } else {
1289 b"" as &[u8]
1290 };
1291 let truncated = if !explicit_sep && content.len() > col_width.saturating_sub(1)
1292 {
1293 &content[..col_width.saturating_sub(1)]
1294 } else if explicit_sep && config.truncate_lines && content.len() > col_width {
1295 &content[..col_width]
1296 } else {
1297 content
1298 };
1299 if fi < num_files - 1 {
1300 if explicit_sep {
1301 if fi > 0 {
1302 page_buf.extend_from_slice(col_sep_bytes);
1303 }
1304 page_buf.extend_from_slice(truncated);
1305 abs_pos +=
1306 truncated.len() + if fi > 0 { col_sep_bytes.len() } else { 0 };
1307 } else {
1308 page_buf.extend_from_slice(truncated);
1309 abs_pos += truncated.len();
1310 let target = (fi + 1) * col_width + config.indent;
1311 write_column_padding(&mut page_buf, abs_pos, target)?;
1312 abs_pos = target;
1313 }
1314 } else {
1315 if explicit_sep && fi > 0 {
1316 page_buf.extend_from_slice(col_sep_bytes);
1317 }
1318 page_buf.extend_from_slice(truncated);
1319 }
1320 }
1321 page_buf.push(b'\n');
1322 body_lines_written += 1;
1323 }
1324
1325 while body_lines_written < body_lines_per_page {
1327 page_buf.push(b'\n');
1328 body_lines_written += 1;
1329 }
1330
1331 if !config.omit_header && !config.omit_pagination && !suppress_header {
1332 write_footer(&mut page_buf, config)?;
1333 }
1334
1335 output.write_all(&page_buf)?;
1336 }
1337
1338 line_idx = page_end;
1339 page_num += 1;
1340 }
1341
1342 Ok(())
1343}
1344
1345fn write_header<W: Write>(
1347 output: &mut W,
1348 date_str: &str,
1349 header: &str,
1350 page_num: usize,
1351 config: &PrConfig,
1352) -> io::Result<()> {
1353 output.write_all(b"\n\n")?;
1355
1356 let line_width = config.page_width;
1358
1359 let left = date_str;
1360 let center = header;
1361 let left_len = left.len();
1362 let center_len = center.len();
1363
1364 let mut page_buf = [0u8; 32];
1366 let page_str = format_page_number(page_num, &mut page_buf);
1367 let right_len = page_str.len();
1368
1369 if left_len + center_len + right_len + 2 >= line_width {
1371 output.write_all(left.as_bytes())?;
1372 output.write_all(b" ")?;
1373 output.write_all(center.as_bytes())?;
1374 output.write_all(b" ")?;
1375 output.write_all(page_str)?;
1376 output.write_all(b"\n")?;
1377 } else {
1378 let total_spaces = line_width - left_len - center_len - right_len;
1379 let left_spaces = total_spaces / 2;
1380 let right_spaces = total_spaces - left_spaces;
1381 output.write_all(left.as_bytes())?;
1382 write_spaces(output, left_spaces)?;
1383 output.write_all(center.as_bytes())?;
1384 write_spaces(output, right_spaces)?;
1385 output.write_all(page_str)?;
1386 output.write_all(b"\n")?;
1387 }
1388
1389 output.write_all(b"\n\n")?;
1391
1392 Ok(())
1393}
1394
1395#[inline]
1397fn format_page_number(page_num: usize, buf: &mut [u8; 32]) -> &[u8] {
1398 const PREFIX: &[u8] = b"Page ";
1399 let prefix_len = PREFIX.len();
1400 buf[..prefix_len].copy_from_slice(PREFIX);
1401 let mut num_buf = [0u8; 20];
1403 let mut n = page_num;
1404 let mut pos = 19;
1405 loop {
1406 num_buf[pos] = b'0' + (n % 10) as u8;
1407 n /= 10;
1408 if n == 0 {
1409 break;
1410 }
1411 pos -= 1;
1412 }
1413 let num_len = 20 - pos;
1414 buf[prefix_len..prefix_len + num_len].copy_from_slice(&num_buf[pos..20]);
1415 &buf[..prefix_len + num_len]
1416}
1417
1418fn write_footer<W: Write>(output: &mut W, config: &PrConfig) -> io::Result<()> {
1420 if config.form_feed {
1421 output.write_all(b"\x0c")?;
1422 } else {
1423 output.write_all(b"\n\n\n\n\n")?;
1424 }
1425 Ok(())
1426}
1427
1428fn write_single_column_body<W: Write>(
1430 output: &mut W,
1431 lines: &[&[u8]],
1432 config: &PrConfig,
1433 line_number: &mut usize,
1434 body_lines_per_page: usize,
1435) -> io::Result<()> {
1436 let indent_str = " ".repeat(config.indent);
1437 let content_width = if config.truncate_lines {
1438 compute_content_width(config)
1439 } else {
1440 0
1441 };
1442 let mut body_lines_written = 0;
1443 let mut num_buf = [0u8; 32];
1445
1446 for line in lines.iter() {
1447 output.write_all(indent_str.as_bytes())?;
1448
1449 if let Some((sep, digits)) = config.number_lines {
1450 let num_str = format_line_number(*line_number, sep, digits, &mut num_buf);
1452 output.write_all(num_str)?;
1453 *line_number += 1;
1454 }
1455
1456 let content: &[u8] = if config.truncate_lines {
1457 if line.len() > content_width {
1458 &line[..content_width]
1459 } else {
1460 line
1461 }
1462 } else {
1463 line
1464 };
1465
1466 output.write_all(content)?;
1468 output.write_all(b"\n")?;
1469 body_lines_written += 1;
1470 if body_lines_written >= body_lines_per_page {
1471 break;
1472 }
1473
1474 if config.double_space {
1476 output.write_all(b"\n")?;
1477 body_lines_written += 1;
1478 if body_lines_written >= body_lines_per_page {
1479 break;
1480 }
1481 }
1482 }
1483
1484 if !config.omit_header && !config.omit_pagination {
1486 while body_lines_written < body_lines_per_page {
1487 output.write_all(b"\n")?;
1488 body_lines_written += 1;
1489 }
1490 }
1491
1492 Ok(())
1493}
1494
1495#[inline]
1498fn format_line_number(num: usize, sep: char, digits: usize, buf: &mut [u8; 32]) -> &[u8] {
1499 let mut n = num;
1501 let mut pos = 31;
1502 loop {
1503 buf[pos] = b'0' + (n % 10) as u8;
1504 n /= 10;
1505 if n == 0 || pos == 0 {
1506 break;
1507 }
1508 pos -= 1;
1509 }
1510 let num_digits = 32 - pos;
1511 let padding = if digits > num_digits {
1513 digits - num_digits
1514 } else {
1515 0
1516 };
1517 let total_len = padding + num_digits + sep.len_utf8();
1518 let start = 32 - num_digits;
1521 let sep_byte = sep as u8; let out_start = 32usize.saturating_sub(total_len);
1525 for i in out_start..out_start + padding {
1527 buf[i] = b' ';
1528 }
1529 if out_start + padding != start {
1531 let src = start;
1532 let dst = out_start + padding;
1533 for i in 0..num_digits {
1534 buf[dst + i] = buf[src + i];
1535 }
1536 }
1537 buf[out_start + padding + num_digits] = sep_byte;
1539 &buf[out_start..out_start + total_len]
1540}
1541
1542fn compute_content_width(config: &PrConfig) -> usize {
1544 let mut w = config.page_width;
1545 w = w.saturating_sub(config.indent);
1546 if let Some((_, digits)) = config.number_lines {
1547 w = w.saturating_sub(digits + 1); }
1549 w
1550}
1551
1552fn write_multicolumn_body<W: Write>(
1554 output: &mut W,
1555 lines: &[&[u8]],
1556 config: &PrConfig,
1557 columns: usize,
1558 line_number: &mut usize,
1559 body_lines_per_page: usize,
1560) -> io::Result<()> {
1561 let explicit_sep = has_explicit_separator(config);
1562 let col_sep = get_column_separator(config);
1563 let col_width = if explicit_sep {
1566 if columns > 1 {
1567 (config
1568 .page_width
1569 .saturating_sub(col_sep.len() * (columns - 1)))
1570 / columns
1571 } else {
1572 config.page_width
1573 }
1574 } else {
1575 config.page_width / columns
1576 };
1577 let do_truncate = !config.join_lines;
1580 let content_width = if explicit_sep {
1581 col_width
1582 } else {
1583 col_width.saturating_sub(1)
1584 };
1585
1586 let indent_str = " ".repeat(config.indent);
1587 let col_sep_bytes = col_sep.as_bytes();
1588 let mut body_lines_written = 0;
1589 let mut num_buf = [0u8; 32];
1590
1591 if config.across {
1592 let mut i = 0;
1594 while i < lines.len() {
1595 if config.double_space && body_lines_written > 0 {
1596 output.write_all(b"\n")?;
1597 body_lines_written += 1;
1598 if body_lines_written >= body_lines_per_page {
1599 break;
1600 }
1601 }
1602
1603 output.write_all(indent_str.as_bytes())?;
1604 let mut abs_pos = config.indent;
1605
1606 let mut last_data_col = 0;
1608 for col in 0..columns {
1609 let li = i + col;
1610 if li < lines.len() {
1611 last_data_col = col;
1612 }
1613 }
1614
1615 for col in 0..columns {
1616 let li = i + col;
1617 if li < lines.len() {
1618 if explicit_sep && col > 0 {
1619 output.write_all(col_sep_bytes)?;
1620 abs_pos += col_sep_bytes.len();
1621 }
1622 if let Some((sep, digits)) = config.number_lines {
1623 let num_str = format_line_number(*line_number, sep, digits, &mut num_buf);
1624 output.write_all(num_str)?;
1625 abs_pos += digits + 1;
1626 *line_number += 1;
1627 }
1628 let content: &[u8] = lines[li];
1629 let mut truncated = if do_truncate && content.len() > content_width {
1630 &content[..content_width]
1631 } else {
1632 content
1633 };
1634 if col == last_data_col && !explicit_sep {
1636 while truncated.last() == Some(&b' ') {
1637 truncated = &truncated[..truncated.len() - 1];
1638 }
1639 }
1640 output.write_all(truncated)?;
1641 abs_pos += truncated.len();
1642 if col < last_data_col && !explicit_sep {
1643 let target = (col + 1) * col_width + config.indent;
1644 write_column_padding(output, abs_pos, target)?;
1645 abs_pos = target;
1646 }
1647 }
1648 }
1649 output.write_all(b"\n")?;
1650 body_lines_written += 1;
1651 i += columns;
1652 }
1653 } else {
1654 let n = lines.len();
1658 let base = n / columns;
1659 let extra = n % columns;
1660
1661 let mut col_starts = vec![0usize; columns + 1];
1663 for col in 0..columns {
1664 let col_lines = base + if col < extra { 1 } else { 0 };
1665 col_starts[col + 1] = col_starts[col] + col_lines;
1666 }
1667
1668 let num_rows = if extra > 0 { base + 1 } else { base };
1670
1671 for row in 0..num_rows {
1672 if config.double_space && row > 0 {
1673 output.write_all(b"\n")?;
1674 body_lines_written += 1;
1675 if body_lines_written >= body_lines_per_page {
1676 break;
1677 }
1678 }
1679
1680 output.write_all(indent_str.as_bytes())?;
1681 let mut abs_pos = config.indent;
1682
1683 let mut last_data_col = 0;
1685 for col in 0..columns {
1686 let col_lines = col_starts[col + 1] - col_starts[col];
1687 if row < col_lines {
1688 last_data_col = col;
1689 }
1690 }
1691
1692 for col in 0..columns {
1693 let col_lines = col_starts[col + 1] - col_starts[col];
1694 let li = col_starts[col] + row;
1695 if row < col_lines {
1696 if explicit_sep && col > 0 {
1697 output.write_all(col_sep_bytes)?;
1698 abs_pos += col_sep_bytes.len();
1699 }
1700 if let Some((sep, digits)) = config.number_lines {
1701 let num = config.first_line_number + li;
1702 let num_str = format_line_number(num, sep, digits, &mut num_buf);
1703 output.write_all(num_str)?;
1704 abs_pos += digits + 1;
1705 }
1706 let content: &[u8] = lines[li];
1707 let mut truncated = if do_truncate && content.len() > content_width {
1708 &content[..content_width]
1709 } else {
1710 content
1711 };
1712 if col == last_data_col && !explicit_sep {
1714 while truncated.last() == Some(&b' ') {
1715 truncated = &truncated[..truncated.len() - 1];
1716 }
1717 }
1718 output.write_all(truncated)?;
1719 abs_pos += truncated.len();
1720 if col < last_data_col && !explicit_sep {
1721 let target = (col + 1) * col_width + config.indent;
1723 write_column_padding(output, abs_pos, target)?;
1724 abs_pos = target;
1725 }
1726 } else if col <= last_data_col {
1727 if explicit_sep {
1729 if col > 0 {
1730 output.write_all(col_sep_bytes)?;
1731 abs_pos += col_sep_bytes.len();
1732 }
1733 } else {
1735 let target = (col + 1) * col_width + config.indent;
1736 write_column_padding(output, abs_pos, target)?;
1737 abs_pos = target;
1738 }
1739 }
1740 }
1742 output.write_all(b"\n")?;
1743 body_lines_written += 1;
1744 }
1745 if config.number_lines.is_some() {
1747 *line_number += lines.len();
1748 }
1749 }
1750
1751 if !config.omit_header && !config.omit_pagination {
1753 while body_lines_written < body_lines_per_page {
1754 output.write_all(b"\n")?;
1755 body_lines_written += 1;
1756 }
1757 }
1758
1759 Ok(())
1760}