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());
135 let mut col = 0;
136 for ch in line.chars() {
137 if ch == tab_char {
138 let spaces = tab_width - (col % tab_width);
139 for _ in 0..spaces {
140 result.push(' ');
141 }
142 col += spaces;
143 } else {
144 result.push(ch);
145 col += 1;
146 }
147 }
148 result
149}
150
151fn to_hat_notation(ch: char) -> String {
153 let b = ch as u32;
154 if b < 32 {
155 format!("^{}", (b as u8 + b'@') as char)
156 } else if b == 127 {
157 "^?".to_string()
158 } else {
159 ch.to_string()
160 }
161}
162
163fn to_nonprinting(ch: char) -> String {
165 let b = ch as u32;
166 if b < 32 && b != 9 && b != 10 {
167 format!("^{}", (b as u8 + b'@') as char)
169 } else if b == 127 {
170 "^?".to_string()
171 } else if b >= 128 && b < 160 {
172 format!("M-^{}", (b as u8 - 128 + b'@') as char)
173 } else if b >= 160 && b < 255 {
174 format!("M-{}", (b as u8 - 128) as char)
175 } else if b == 255 {
176 "M-^?".to_string()
177 } else {
178 ch.to_string()
179 }
180}
181
182fn process_control_chars(line: &str, show_control: bool, show_nonprinting: bool) -> String {
184 if !show_control && !show_nonprinting {
185 return line.to_string();
186 }
187 let mut result = String::with_capacity(line.len());
188 for ch in line.chars() {
189 if show_nonprinting {
190 result.push_str(&to_nonprinting(ch));
191 } else if show_control {
192 result.push_str(&to_hat_notation(ch));
193 } else {
194 result.push(ch);
195 }
196 }
197 result
198}
199
200fn get_column_separator(config: &PrConfig) -> String {
202 if let Some(ref s) = config.sep_string {
203 s.clone()
204 } else if let Some(c) = config.separator {
205 c.to_string()
206 } else {
207 " ".to_string()
208 }
209}
210
211fn has_explicit_separator(config: &PrConfig) -> bool {
213 config.sep_string.is_some() || config.separator.is_some()
214}
215
216const SPACES: [u8; 256] = [b' '; 256];
222
223#[inline]
225fn write_spaces<W: Write>(output: &mut W, n: usize) -> io::Result<()> {
226 let mut remaining = n;
227 while remaining > 0 {
228 let chunk = remaining.min(SPACES.len());
229 output.write_all(&SPACES[..chunk])?;
230 remaining -= chunk;
231 }
232 Ok(())
233}
234
235fn write_column_padding<W: Write>(
236 output: &mut W,
237 abs_pos: usize,
238 target_abs_pos: usize,
239) -> io::Result<()> {
240 let tab_size = 8;
241 let mut pos = abs_pos;
242 while pos < target_abs_pos {
243 let next_tab = ((pos / tab_size) + 1) * tab_size;
244 if next_tab <= target_abs_pos {
245 output.write_all(b"\t")?;
246 pos = next_tab;
247 } else {
248 let n = target_abs_pos - pos;
249 if n <= SPACES.len() {
250 output.write_all(&SPACES[..n])?;
251 } else {
252 for _ in 0..n {
253 output.write_all(b" ")?;
254 }
255 }
256 pos = target_abs_pos;
257 }
258 }
259 Ok(())
260}
261
262pub fn pr_data<W: Write>(
266 data: &[u8],
267 output: &mut W,
268 config: &PrConfig,
269 filename: &str,
270 file_date: Option<SystemTime>,
271) -> io::Result<()> {
272 let needs_transform =
273 config.expand_tabs.is_some() || config.show_control_chars || config.show_nonprinting;
274
275 if needs_transform {
276 let reader = io::Cursor::new(data);
278 return pr_file(reader, output, config, filename, file_date);
279 }
280
281 let text = String::from_utf8_lossy(data);
283 let all_lines: Vec<&str> = text
285 .split('\n')
286 .map(|l| l.strip_suffix('\r').unwrap_or(l))
287 .collect();
288 let all_lines = if all_lines.last() == Some(&"") {
290 &all_lines[..all_lines.len() - 1]
291 } else {
292 &all_lines[..]
293 };
294
295 pr_lines_generic(all_lines, output, config, filename, file_date)
296}
297
298pub fn pr_file<R: BufRead, W: Write>(
300 input: R,
301 output: &mut W,
302 config: &PrConfig,
303 filename: &str,
304 file_date: Option<SystemTime>,
305) -> io::Result<()> {
306 let mut all_lines: Vec<String> = Vec::new();
308 for line_result in input.lines() {
309 let line = line_result?;
310 let mut line = line;
311
312 if let Some((tab_char, tab_width)) = config.expand_tabs {
314 line = expand_tabs_in_line(&line, tab_char, tab_width);
315 }
316
317 if config.show_control_chars || config.show_nonprinting {
319 line = process_control_chars(&line, config.show_control_chars, config.show_nonprinting);
320 }
321
322 all_lines.push(line);
323 }
324
325 let refs: Vec<&str> = all_lines.iter().map(|s| s.as_str()).collect();
327 pr_lines_generic(&refs, output, config, filename, file_date)
328}
329
330fn pr_lines_generic<W: Write>(
332 all_lines: &[&str],
333 output: &mut W,
334 config: &PrConfig,
335 filename: &str,
336 file_date: Option<SystemTime>,
337) -> io::Result<()> {
338 let date = file_date.unwrap_or_else(SystemTime::now);
339
340 let header_str = config.header.as_deref().unwrap_or(filename);
341 let date_str = format_header_date(&date, &config.date_format);
342
343 let suppress_header = !config.omit_header
347 && !config.omit_pagination
348 && config.page_length <= HEADER_LINES + FOOTER_LINES;
349 let suppressed_config;
352 let effective_config = if suppress_header {
353 suppressed_config = PrConfig {
354 omit_header: true,
355 ..config.clone()
356 };
357 &suppressed_config
358 } else {
359 config
360 };
361 let body_lines_per_page = if config.omit_header || config.omit_pagination {
362 if config.page_length > 0 {
363 config.page_length
364 } else {
365 DEFAULT_PAGE_LENGTH
366 }
367 } else if suppress_header {
368 config.page_length
369 } else {
370 config.page_length - HEADER_LINES - FOOTER_LINES
371 };
372
373 let input_lines_per_page = if config.double_space {
375 (body_lines_per_page + 1) / 2
376 } else {
377 body_lines_per_page
378 };
379
380 let columns = config.columns.max(1);
382
383 let lines_consumed_per_page = if columns > 1 && !config.across {
388 input_lines_per_page * columns
389 } else {
390 input_lines_per_page
391 };
392
393 let total_lines = all_lines.len();
395 let mut line_number = config.first_line_number;
396 let mut page_num = 1usize;
397 let mut line_idx = 0;
398
399 while line_idx < total_lines || (line_idx == 0 && total_lines == 0) {
400 if total_lines == 0 && line_idx == 0 {
402 if page_num >= config.first_page
403 && (config.last_page == 0 || page_num <= config.last_page)
404 {
405 if !config.omit_header && !config.omit_pagination && !suppress_header {
406 write_header(output, &date_str, header_str, page_num, config)?;
407 }
408 if !config.omit_header && !config.omit_pagination && !suppress_header {
409 write_footer(output, config)?;
410 }
411 }
412 break;
413 }
414
415 let page_end = (line_idx + lines_consumed_per_page).min(total_lines);
416
417 if page_num >= config.first_page && (config.last_page == 0 || page_num <= config.last_page)
418 {
419 if !config.omit_header && !config.omit_pagination && !suppress_header {
421 write_header(output, &date_str, header_str, page_num, config)?;
422 }
423
424 if columns > 1 {
426 write_multicolumn_body(
427 output,
428 &all_lines[line_idx..page_end],
429 effective_config,
430 columns,
431 &mut line_number,
432 body_lines_per_page,
433 )?;
434 } else {
435 write_single_column_body(
436 output,
437 &all_lines[line_idx..page_end],
438 effective_config,
439 &mut line_number,
440 body_lines_per_page,
441 )?;
442 }
443
444 if !config.omit_header && !config.omit_pagination && !suppress_header {
446 write_footer(output, config)?;
447 }
448 }
449
450 line_idx = page_end;
451 page_num += 1;
452
453 if line_idx >= total_lines {
455 break;
456 }
457 }
458
459 Ok(())
460}
461
462pub fn pr_merge<W: Write>(
464 inputs: &[Vec<String>],
465 output: &mut W,
466 config: &PrConfig,
467 _filenames: &[&str],
468 file_dates: &[SystemTime],
469) -> io::Result<()> {
470 let date = file_dates.first().copied().unwrap_or_else(SystemTime::now);
471 let date_str = format_header_date(&date, &config.date_format);
472 let header_str = config.header.as_deref().unwrap_or("");
473
474 let suppress_header = !config.omit_header
475 && !config.omit_pagination
476 && config.page_length <= HEADER_LINES + FOOTER_LINES;
477 let body_lines_per_page = if config.omit_header || config.omit_pagination {
478 if config.page_length > 0 {
479 config.page_length
480 } else {
481 DEFAULT_PAGE_LENGTH
482 }
483 } else if suppress_header {
484 config.page_length
485 } else {
486 config.page_length - HEADER_LINES - FOOTER_LINES
487 };
488
489 let input_lines_per_page = if config.double_space {
490 (body_lines_per_page + 1) / 2
491 } else {
492 body_lines_per_page
493 };
494
495 let num_files = inputs.len();
496 let explicit_sep = has_explicit_separator(config);
497 let col_sep = get_column_separator(config);
498 let col_width = if explicit_sep {
499 if num_files > 1 {
500 (config
501 .page_width
502 .saturating_sub(col_sep.len() * (num_files - 1)))
503 / num_files
504 } else {
505 config.page_width
506 }
507 } else {
508 config.page_width / num_files
509 };
510
511 let max_lines = inputs.iter().map(|f| f.len()).max().unwrap_or(0);
512 let mut page_num = 1usize;
513 let mut line_idx = 0;
514 let mut line_number = config.first_line_number;
515
516 while line_idx < max_lines {
517 let page_end = (line_idx + input_lines_per_page).min(max_lines);
518
519 if page_num >= config.first_page && (config.last_page == 0 || page_num <= config.last_page)
520 {
521 if !config.omit_header && !config.omit_pagination && !suppress_header {
522 write_header(output, &date_str, header_str, page_num, config)?;
523 }
524
525 let indent_str = " ".repeat(config.indent);
526 let mut body_lines_written = 0;
527 for i in line_idx..page_end {
528 if config.double_space && body_lines_written > 0 {
529 writeln!(output)?;
530 body_lines_written += 1;
531 }
532
533 output.write_all(indent_str.as_bytes())?;
534 let mut abs_pos = config.indent;
535
536 if let Some((sep, digits)) = config.number_lines {
537 write!(output, "{:>width$}{}", line_number, sep, width = digits)?;
538 abs_pos += digits + 1;
539 line_number += 1;
540 }
541
542 for (fi, file_lines) in inputs.iter().enumerate() {
543 let content = if i < file_lines.len() {
544 &file_lines[i]
545 } else {
546 ""
547 };
548 let truncated = if !explicit_sep && content.len() > col_width.saturating_sub(1)
549 {
550 &content[..col_width.saturating_sub(1)]
552 } else if explicit_sep && config.truncate_lines && content.len() > col_width {
553 &content[..col_width]
555 } else {
556 content
557 };
558 if fi < num_files - 1 {
559 if explicit_sep {
561 if fi > 0 {
563 write!(output, "{}", col_sep)?;
564 }
565 write!(output, "{}", truncated)?;
566 abs_pos += truncated.len() + if fi > 0 { col_sep.len() } else { 0 };
567 } else {
568 write!(output, "{}", truncated)?;
569 abs_pos += truncated.len();
570 let target = (fi + 1) * col_width + config.indent;
571 write_column_padding(output, abs_pos, target)?;
572 abs_pos = target;
573 }
574 } else {
575 if explicit_sep && fi > 0 {
577 write!(output, "{}", col_sep)?;
578 }
579 write!(output, "{}", truncated)?;
580 }
581 }
582 writeln!(output)?;
583 body_lines_written += 1;
584 }
585
586 while body_lines_written < body_lines_per_page {
588 writeln!(output)?;
589 body_lines_written += 1;
590 }
591
592 if !config.omit_header && !config.omit_pagination && !suppress_header {
593 write_footer(output, config)?;
594 }
595 }
596
597 line_idx = page_end;
598 page_num += 1;
599 }
600
601 Ok(())
602}
603
604fn write_header<W: Write>(
606 output: &mut W,
607 date_str: &str,
608 header: &str,
609 page_num: usize,
610 config: &PrConfig,
611) -> io::Result<()> {
612 output.write_all(b"\n\n")?;
614
615 let line_width = config.page_width;
617
618 let left = date_str;
619 let center = header;
620 let left_len = left.len();
621 let center_len = center.len();
622
623 let mut page_buf = [0u8; 32];
625 let page_str = format_page_number(page_num, &mut page_buf);
626 let right_len = page_str.len();
627
628 if left_len + center_len + right_len + 2 >= line_width {
630 output.write_all(left.as_bytes())?;
631 output.write_all(b" ")?;
632 output.write_all(center.as_bytes())?;
633 output.write_all(b" ")?;
634 output.write_all(page_str)?;
635 output.write_all(b"\n")?;
636 } else {
637 let total_spaces = line_width - left_len - center_len - right_len;
638 let left_spaces = total_spaces / 2;
639 let right_spaces = total_spaces - left_spaces;
640 output.write_all(left.as_bytes())?;
641 write_spaces(output, left_spaces)?;
642 output.write_all(center.as_bytes())?;
643 write_spaces(output, right_spaces)?;
644 output.write_all(page_str)?;
645 output.write_all(b"\n")?;
646 }
647
648 output.write_all(b"\n\n")?;
650
651 Ok(())
652}
653
654#[inline]
656fn format_page_number(page_num: usize, buf: &mut [u8; 32]) -> &[u8] {
657 const PREFIX: &[u8] = b"Page ";
658 let prefix_len = PREFIX.len();
659 buf[..prefix_len].copy_from_slice(PREFIX);
660 let mut num_buf = [0u8; 20];
662 let mut n = page_num;
663 let mut pos = 19;
664 loop {
665 num_buf[pos] = b'0' + (n % 10) as u8;
666 n /= 10;
667 if n == 0 {
668 break;
669 }
670 pos -= 1;
671 }
672 let num_len = 20 - pos;
673 buf[prefix_len..prefix_len + num_len].copy_from_slice(&num_buf[pos..20]);
674 &buf[..prefix_len + num_len]
675}
676
677fn write_footer<W: Write>(output: &mut W, config: &PrConfig) -> io::Result<()> {
679 if config.form_feed {
680 output.write_all(b"\x0c")?;
681 } else {
682 output.write_all(b"\n\n\n\n\n")?;
683 }
684 Ok(())
685}
686
687fn write_single_column_body<W: Write>(
689 output: &mut W,
690 lines: &[&str],
691 config: &PrConfig,
692 line_number: &mut usize,
693 body_lines_per_page: usize,
694) -> io::Result<()> {
695 let indent_str = " ".repeat(config.indent);
696 let content_width = if config.truncate_lines {
697 compute_content_width(config)
698 } else {
699 0
700 };
701 let mut body_lines_written = 0;
702 let mut num_buf = [0u8; 32];
704
705 for line in lines.iter() {
706 output.write_all(indent_str.as_bytes())?;
707
708 if let Some((sep, digits)) = config.number_lines {
709 let num_str = format_line_number(*line_number, sep, digits, &mut num_buf);
711 output.write_all(num_str)?;
712 *line_number += 1;
713 }
714
715 let content = if config.truncate_lines {
716 if line.len() > content_width {
717 &line[..content_width]
718 } else {
719 line
720 }
721 } else {
722 line
723 };
724
725 output.write_all(content.as_bytes())?;
727 output.write_all(b"\n")?;
728 body_lines_written += 1;
729 if body_lines_written >= body_lines_per_page {
730 break;
731 }
732
733 if config.double_space {
735 output.write_all(b"\n")?;
736 body_lines_written += 1;
737 if body_lines_written >= body_lines_per_page {
738 break;
739 }
740 }
741 }
742
743 if !config.omit_header && !config.omit_pagination {
745 while body_lines_written < body_lines_per_page {
746 output.write_all(b"\n")?;
747 body_lines_written += 1;
748 }
749 }
750
751 Ok(())
752}
753
754#[inline]
757fn format_line_number(num: usize, sep: char, digits: usize, buf: &mut [u8; 32]) -> &[u8] {
758 let mut n = num;
760 let mut pos = 31;
761 loop {
762 buf[pos] = b'0' + (n % 10) as u8;
763 n /= 10;
764 if n == 0 || pos == 0 {
765 break;
766 }
767 pos -= 1;
768 }
769 let num_digits = 32 - pos;
770 let padding = if digits > num_digits {
772 digits - num_digits
773 } else {
774 0
775 };
776 let total_len = padding + num_digits + sep.len_utf8();
777 let start = 32 - num_digits;
780 let sep_byte = sep as u8; let out_start = 32usize.saturating_sub(total_len);
784 for i in out_start..out_start + padding {
786 buf[i] = b' ';
787 }
788 if out_start + padding != start {
790 let src = start;
791 let dst = out_start + padding;
792 for i in 0..num_digits {
793 buf[dst + i] = buf[src + i];
794 }
795 }
796 buf[out_start + padding + num_digits] = sep_byte;
798 &buf[out_start..out_start + total_len]
799}
800
801fn compute_content_width(config: &PrConfig) -> usize {
803 let mut w = config.page_width;
804 w = w.saturating_sub(config.indent);
805 if let Some((_, digits)) = config.number_lines {
806 w = w.saturating_sub(digits + 1); }
808 w
809}
810
811fn write_multicolumn_body<W: Write>(
813 output: &mut W,
814 lines: &[&str],
815 config: &PrConfig,
816 columns: usize,
817 line_number: &mut usize,
818 body_lines_per_page: usize,
819) -> io::Result<()> {
820 let explicit_sep = has_explicit_separator(config);
821 let col_sep = get_column_separator(config);
822 let col_width = if explicit_sep {
825 if columns > 1 {
826 (config
827 .page_width
828 .saturating_sub(col_sep.len() * (columns - 1)))
829 / columns
830 } else {
831 config.page_width
832 }
833 } else {
834 config.page_width / columns
835 };
836
837 let indent_str = " ".repeat(config.indent);
838 let mut body_lines_written = 0;
839
840 if config.across {
841 let mut i = 0;
843 while i < lines.len() {
844 if config.double_space && body_lines_written > 0 {
845 writeln!(output)?;
846 body_lines_written += 1;
847 if body_lines_written >= body_lines_per_page {
848 break;
849 }
850 }
851
852 output.write_all(indent_str.as_bytes())?;
853 let mut abs_pos = config.indent;
854
855 let mut last_data_col = 0;
857 for col in 0..columns {
858 let li = i + col;
859 if li < lines.len() {
860 last_data_col = col;
861 }
862 }
863
864 for col in 0..columns {
865 let li = i + col;
866 if li < lines.len() {
867 if explicit_sep && col > 0 {
868 write!(output, "{}", col_sep)?;
869 abs_pos += col_sep.len();
870 }
871 if let Some((sep, digits)) = config.number_lines {
872 write!(output, "{:>width$}{}", line_number, sep, width = digits)?;
873 abs_pos += digits + 1;
874 *line_number += 1;
875 }
876 let content = lines[li];
877 let truncated = if config.truncate_lines && content.len() > col_width {
878 &content[..col_width]
879 } else {
880 content
881 };
882 output.write_all(truncated.as_bytes())?;
883 abs_pos += truncated.len();
884 if col < last_data_col && !explicit_sep {
885 let target = (col + 1) * col_width + config.indent;
886 write_column_padding(output, abs_pos, target)?;
887 abs_pos = target;
888 }
889 }
890 }
891 output.write_all(b"\n")?;
892 body_lines_written += 1;
893 i += columns;
894 }
895 } else {
896 let n = lines.len();
900 let base = n / columns;
901 let extra = n % columns;
902
903 let mut col_starts = vec![0usize; columns + 1];
905 for col in 0..columns {
906 let col_lines = base + if col < extra { 1 } else { 0 };
907 col_starts[col + 1] = col_starts[col] + col_lines;
908 }
909
910 let num_rows = if extra > 0 { base + 1 } else { base };
912
913 for row in 0..num_rows {
914 if config.double_space && row > 0 {
915 writeln!(output)?;
916 body_lines_written += 1;
917 if body_lines_written >= body_lines_per_page {
918 break;
919 }
920 }
921
922 output.write_all(indent_str.as_bytes())?;
923 let mut abs_pos = config.indent;
924
925 let mut last_data_col = 0;
927 for col in 0..columns {
928 let col_lines = col_starts[col + 1] - col_starts[col];
929 if row < col_lines {
930 last_data_col = col;
931 }
932 }
933
934 for col in 0..columns {
935 let col_lines = col_starts[col + 1] - col_starts[col];
936 let li = col_starts[col] + row;
937 if row < col_lines {
938 if explicit_sep && col > 0 {
939 write!(output, "{}", col_sep)?;
940 abs_pos += col_sep.len();
941 }
942 if let Some((sep, digits)) = config.number_lines {
943 let num = config.first_line_number + li;
944 write!(output, "{:>width$}{}", num, sep, width = digits)?;
945 abs_pos += digits + 1;
946 }
947 let content = lines[li];
948 let truncated = if config.truncate_lines && content.len() > col_width {
949 &content[..col_width]
950 } else {
951 content
952 };
953 output.write_all(truncated.as_bytes())?;
954 abs_pos += truncated.len();
955 if col < last_data_col && !explicit_sep {
956 let target = (col + 1) * col_width + config.indent;
958 write_column_padding(output, abs_pos, target)?;
959 abs_pos = target;
960 }
961 } else if col <= last_data_col {
962 if explicit_sep {
964 if col > 0 {
965 write!(output, "{}", col_sep)?;
966 abs_pos += col_sep.len();
967 }
968 } else {
970 let target = (col + 1) * col_width + config.indent;
971 write_column_padding(output, abs_pos, target)?;
972 abs_pos = target;
973 }
974 }
975 }
977 output.write_all(b"\n")?;
978 body_lines_written += 1;
979 }
980 if config.number_lines.is_some() {
982 *line_number += lines.len();
983 }
984 }
985
986 if !config.omit_header && !config.omit_pagination {
988 while body_lines_written < body_lines_per_page {
989 output.write_all(b"\n")?;
990 body_lines_written += 1;
991 }
992 }
993
994 Ok(())
995}