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 {
562 write!(output, "{}", col_sep)?;
563 }
564 write!(output, "{:<width$}", truncated, width = col_width)?;
565 abs_pos = (fi + 1) * col_width + config.indent + fi * col_sep.len();
566 } else {
567 write!(output, "{}", truncated)?;
568 abs_pos += truncated.len();
569 let target = (fi + 1) * col_width + config.indent;
570 write_column_padding(output, abs_pos, target)?;
571 abs_pos = target;
572 }
573 } else {
574 if explicit_sep && fi > 0 {
576 write!(output, "{}", col_sep)?;
577 }
578 write!(output, "{}", truncated)?;
579 }
580 }
581 writeln!(output)?;
582 body_lines_written += 1;
583 }
584
585 while body_lines_written < body_lines_per_page {
587 writeln!(output)?;
588 body_lines_written += 1;
589 }
590
591 if !config.omit_header && !config.omit_pagination && !suppress_header {
592 write_footer(output, config)?;
593 }
594 }
595
596 line_idx = page_end;
597 page_num += 1;
598 }
599
600 Ok(())
601}
602
603fn write_header<W: Write>(
605 output: &mut W,
606 date_str: &str,
607 header: &str,
608 page_num: usize,
609 config: &PrConfig,
610) -> io::Result<()> {
611 output.write_all(b"\n\n")?;
613
614 let line_width = config.page_width;
616
617 let left = date_str;
618 let center = header;
619 let left_len = left.len();
620 let center_len = center.len();
621
622 let mut page_buf = [0u8; 32];
624 let page_str = format_page_number(page_num, &mut page_buf);
625 let right_len = page_str.len();
626
627 if left_len + center_len + right_len + 2 >= line_width {
629 output.write_all(left.as_bytes())?;
630 output.write_all(b" ")?;
631 output.write_all(center.as_bytes())?;
632 output.write_all(b" ")?;
633 output.write_all(page_str)?;
634 output.write_all(b"\n")?;
635 } else {
636 let total_spaces = line_width - left_len - center_len - right_len;
637 let left_spaces = total_spaces / 2;
638 let right_spaces = total_spaces - left_spaces;
639 output.write_all(left.as_bytes())?;
640 write_spaces(output, left_spaces)?;
641 output.write_all(center.as_bytes())?;
642 write_spaces(output, right_spaces)?;
643 output.write_all(page_str)?;
644 output.write_all(b"\n")?;
645 }
646
647 output.write_all(b"\n\n")?;
649
650 Ok(())
651}
652
653#[inline]
655fn format_page_number(page_num: usize, buf: &mut [u8; 32]) -> &[u8] {
656 const PREFIX: &[u8] = b"Page ";
657 let prefix_len = PREFIX.len();
658 buf[..prefix_len].copy_from_slice(PREFIX);
659 let mut num_buf = [0u8; 20];
661 let mut n = page_num;
662 let mut pos = 19;
663 loop {
664 num_buf[pos] = b'0' + (n % 10) as u8;
665 n /= 10;
666 if n == 0 {
667 break;
668 }
669 pos -= 1;
670 }
671 let num_len = 20 - pos;
672 buf[prefix_len..prefix_len + num_len].copy_from_slice(&num_buf[pos..20]);
673 &buf[..prefix_len + num_len]
674}
675
676fn write_footer<W: Write>(output: &mut W, config: &PrConfig) -> io::Result<()> {
678 if config.form_feed {
679 output.write_all(b"\x0c")?;
680 } else {
681 output.write_all(b"\n\n\n\n\n")?;
682 }
683 Ok(())
684}
685
686fn write_single_column_body<W: Write>(
688 output: &mut W,
689 lines: &[&str],
690 config: &PrConfig,
691 line_number: &mut usize,
692 body_lines_per_page: usize,
693) -> io::Result<()> {
694 let indent_str = " ".repeat(config.indent);
695 let content_width = if config.truncate_lines {
696 compute_content_width(config)
697 } else {
698 0
699 };
700 let mut body_lines_written = 0;
701 let mut num_buf = [0u8; 32];
703
704 for line in lines.iter() {
705 output.write_all(indent_str.as_bytes())?;
706
707 if let Some((sep, digits)) = config.number_lines {
708 let num_str = format_line_number(*line_number, sep, digits, &mut num_buf);
710 output.write_all(num_str)?;
711 *line_number += 1;
712 }
713
714 let content = if config.truncate_lines {
715 if line.len() > content_width {
716 &line[..content_width]
717 } else {
718 line
719 }
720 } else {
721 line
722 };
723
724 output.write_all(content.as_bytes())?;
726 output.write_all(b"\n")?;
727 body_lines_written += 1;
728 if body_lines_written >= body_lines_per_page {
729 break;
730 }
731
732 if config.double_space {
734 output.write_all(b"\n")?;
735 body_lines_written += 1;
736 if body_lines_written >= body_lines_per_page {
737 break;
738 }
739 }
740 }
741
742 if !config.omit_header && !config.omit_pagination {
744 while body_lines_written < body_lines_per_page {
745 output.write_all(b"\n")?;
746 body_lines_written += 1;
747 }
748 }
749
750 Ok(())
751}
752
753#[inline]
756fn format_line_number(num: usize, sep: char, digits: usize, buf: &mut [u8; 32]) -> &[u8] {
757 let mut n = num;
759 let mut pos = 31;
760 loop {
761 buf[pos] = b'0' + (n % 10) as u8;
762 n /= 10;
763 if n == 0 || pos == 0 {
764 break;
765 }
766 pos -= 1;
767 }
768 let num_digits = 32 - pos;
769 let padding = if digits > num_digits {
771 digits - num_digits
772 } else {
773 0
774 };
775 let total_len = padding + num_digits + sep.len_utf8();
776 let start = 32 - num_digits;
779 let sep_byte = sep as u8; let out_start = 32usize.saturating_sub(total_len);
783 for i in out_start..out_start + padding {
785 buf[i] = b' ';
786 }
787 if out_start + padding != start {
789 let src = start;
790 let dst = out_start + padding;
791 for i in 0..num_digits {
792 buf[dst + i] = buf[src + i];
793 }
794 }
795 buf[out_start + padding + num_digits] = sep_byte;
797 &buf[out_start..out_start + total_len]
798}
799
800fn compute_content_width(config: &PrConfig) -> usize {
802 let mut w = config.page_width;
803 w = w.saturating_sub(config.indent);
804 if let Some((_, digits)) = config.number_lines {
805 w = w.saturating_sub(digits + 1); }
807 w
808}
809
810fn write_multicolumn_body<W: Write>(
812 output: &mut W,
813 lines: &[&str],
814 config: &PrConfig,
815 columns: usize,
816 line_number: &mut usize,
817 body_lines_per_page: usize,
818) -> io::Result<()> {
819 let explicit_sep = has_explicit_separator(config);
820 let col_sep = get_column_separator(config);
821 let col_width = if explicit_sep {
824 if columns > 1 {
825 (config
826 .page_width
827 .saturating_sub(col_sep.len() * (columns - 1)))
828 / columns
829 } else {
830 config.page_width
831 }
832 } else {
833 config.page_width / columns
834 };
835
836 let indent_str = " ".repeat(config.indent);
837 let mut body_lines_written = 0;
838
839 if config.across {
840 let mut i = 0;
842 while i < lines.len() {
843 if config.double_space && body_lines_written > 0 {
844 writeln!(output)?;
845 body_lines_written += 1;
846 if body_lines_written >= body_lines_per_page {
847 break;
848 }
849 }
850
851 output.write_all(indent_str.as_bytes())?;
852 let mut abs_pos = config.indent;
853
854 let mut last_data_col = 0;
856 for col in 0..columns {
857 let li = i + col;
858 if li < lines.len() {
859 last_data_col = col;
860 }
861 }
862
863 for col in 0..columns {
864 let li = i + col;
865 if li < lines.len() {
866 if explicit_sep && col > 0 {
867 write!(output, "{}", col_sep)?;
868 abs_pos += col_sep.len();
869 }
870 if let Some((sep, digits)) = config.number_lines {
871 write!(output, "{:>width$}{}", line_number, sep, width = digits)?;
872 abs_pos += digits + 1;
873 *line_number += 1;
874 }
875 let content = lines[li];
876 let truncated = if config.truncate_lines && content.len() > col_width {
877 &content[..col_width]
878 } else {
879 content
880 };
881 output.write_all(truncated.as_bytes())?;
882 abs_pos += truncated.len();
883 if col < last_data_col && !explicit_sep {
884 let target = (col + 1) * col_width + config.indent;
885 write_column_padding(output, abs_pos, target)?;
886 abs_pos = target;
887 }
888 }
889 }
890 output.write_all(b"\n")?;
891 body_lines_written += 1;
892 i += columns;
893 }
894 } else {
895 let n = lines.len();
899 let base = n / columns;
900 let extra = n % columns;
901
902 let mut col_starts = vec![0usize; columns + 1];
904 for col in 0..columns {
905 let col_lines = base + if col < extra { 1 } else { 0 };
906 col_starts[col + 1] = col_starts[col] + col_lines;
907 }
908
909 let num_rows = if extra > 0 { base + 1 } else { base };
911
912 for row in 0..num_rows {
913 if config.double_space && row > 0 {
914 writeln!(output)?;
915 body_lines_written += 1;
916 if body_lines_written >= body_lines_per_page {
917 break;
918 }
919 }
920
921 output.write_all(indent_str.as_bytes())?;
922 let mut abs_pos = config.indent;
923
924 let mut last_data_col = 0;
926 for col in 0..columns {
927 let col_lines = col_starts[col + 1] - col_starts[col];
928 if row < col_lines {
929 last_data_col = col;
930 }
931 }
932
933 for col in 0..columns {
934 let col_lines = col_starts[col + 1] - col_starts[col];
935 let li = col_starts[col] + row;
936 if row < col_lines {
937 if explicit_sep && col > 0 {
938 write!(output, "{}", col_sep)?;
939 abs_pos += col_sep.len();
940 }
941 if let Some((sep, digits)) = config.number_lines {
942 let num = config.first_line_number + li;
943 write!(output, "{:>width$}{}", num, sep, width = digits)?;
944 abs_pos += digits + 1;
945 }
946 let content = lines[li];
947 let truncated = if config.truncate_lines && content.len() > col_width {
948 &content[..col_width]
949 } else {
950 content
951 };
952 output.write_all(truncated.as_bytes())?;
953 abs_pos += truncated.len();
954 if col < last_data_col && !explicit_sep {
955 let target = (col + 1) * col_width + config.indent;
957 write_column_padding(output, abs_pos, target)?;
958 abs_pos = target;
959 }
960 } else if col <= last_data_col {
961 if explicit_sep {
963 if col > 0 {
964 write!(output, "{}", col_sep)?;
965 abs_pos += col_sep.len();
966 }
967 } else {
969 let target = (col + 1) * col_width + config.indent;
970 write_column_padding(output, abs_pos, target)?;
971 abs_pos = target;
972 }
973 }
974 }
976 output.write_all(b"\n")?;
977 body_lines_written += 1;
978 }
979 if config.number_lines.is_some() {
981 *line_number += lines.len();
982 }
983 }
984
985 if !config.omit_header && !config.omit_pagination {
987 while body_lines_written < body_lines_per_page {
988 output.write_all(b"\n")?;
989 body_lines_written += 1;
990 }
991 }
992
993 Ok(())
994}