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 tab_size = 8;
263 let mut pos = abs_pos;
264 while pos < target_abs_pos {
265 let next_tab = ((pos / tab_size) + 1) * tab_size;
266 if next_tab <= target_abs_pos {
267 output.write_all(b"\t")?;
268 pos = next_tab;
269 } else {
270 let n = target_abs_pos - pos;
271 write_spaces(output, n)?;
272 pos = target_abs_pos;
273 }
274 }
275 Ok(())
276}
277
278pub fn pr_data<W: Write>(
282 data: &[u8],
283 output: &mut W,
284 config: &PrConfig,
285 filename: &str,
286 file_date: Option<SystemTime>,
287) -> io::Result<()> {
288 let needs_transform =
289 config.expand_tabs.is_some() || config.show_control_chars || config.show_nonprinting;
290
291 if needs_transform {
292 let reader = io::Cursor::new(data);
294 return pr_file(reader, output, config, filename, file_date);
295 }
296
297 let text = String::from_utf8_lossy(data);
299 let all_lines: Vec<&str> = text
301 .split('\n')
302 .map(|l| l.strip_suffix('\r').unwrap_or(l))
303 .collect();
304 let all_lines = if all_lines.last() == Some(&"") {
306 &all_lines[..all_lines.len() - 1]
307 } else {
308 &all_lines[..]
309 };
310
311 pr_lines_generic(all_lines, output, config, filename, file_date)
312}
313
314pub fn pr_file<R: BufRead, W: Write>(
316 input: R,
317 output: &mut W,
318 config: &PrConfig,
319 filename: &str,
320 file_date: Option<SystemTime>,
321) -> io::Result<()> {
322 let mut all_lines: Vec<String> = Vec::new();
324 for line_result in input.lines() {
325 let line = line_result?;
326 let mut line = line;
327
328 if let Some((tab_char, tab_width)) = config.expand_tabs {
330 line = expand_tabs_in_line(&line, tab_char, tab_width);
331 }
332
333 if config.show_control_chars || config.show_nonprinting {
335 line = process_control_chars(&line, config.show_control_chars, config.show_nonprinting);
336 }
337
338 all_lines.push(line);
339 }
340
341 let refs: Vec<&str> = all_lines.iter().map(|s| s.as_str()).collect();
343 pr_lines_generic(&refs, output, config, filename, file_date)
344}
345
346fn pr_lines_generic<W: Write>(
348 all_lines: &[&str],
349 output: &mut W,
350 config: &PrConfig,
351 filename: &str,
352 file_date: Option<SystemTime>,
353) -> io::Result<()> {
354 let date = file_date.unwrap_or_else(SystemTime::now);
355
356 let header_str = config.header.as_deref().unwrap_or(filename);
357 let date_str = format_header_date(&date, &config.date_format);
358
359 let suppress_header = !config.omit_header
363 && !config.omit_pagination
364 && config.page_length <= HEADER_LINES + FOOTER_LINES;
365 let suppressed_config;
368 let effective_config = if suppress_header {
369 suppressed_config = PrConfig {
370 omit_header: true,
371 ..config.clone()
372 };
373 &suppressed_config
374 } else {
375 config
376 };
377 let body_lines_per_page = if config.omit_header || config.omit_pagination {
378 if config.page_length > 0 {
379 config.page_length
380 } else {
381 DEFAULT_PAGE_LENGTH
382 }
383 } else if suppress_header {
384 config.page_length
385 } else {
386 config.page_length - HEADER_LINES - FOOTER_LINES
387 };
388
389 let input_lines_per_page = if config.double_space {
391 (body_lines_per_page + 1) / 2
392 } else {
393 body_lines_per_page
394 };
395
396 let columns = config.columns.max(1);
398
399 let lines_consumed_per_page = if columns > 1 && !config.across {
404 input_lines_per_page * columns
405 } else {
406 input_lines_per_page
407 };
408
409 let total_lines = all_lines.len();
411 let mut line_number = config.first_line_number;
412 let mut page_num = 1usize;
413 let mut line_idx = 0;
414
415 while line_idx < total_lines || (line_idx == 0 && total_lines == 0) {
416 if total_lines == 0 && line_idx == 0 {
418 if page_num >= config.first_page
419 && (config.last_page == 0 || page_num <= config.last_page)
420 {
421 if !config.omit_header && !config.omit_pagination && !suppress_header {
422 write_header(output, &date_str, header_str, page_num, config)?;
423 }
424 if !config.omit_header && !config.omit_pagination && !suppress_header {
425 write_footer(output, config)?;
426 }
427 }
428 break;
429 }
430
431 let page_end = (line_idx + lines_consumed_per_page).min(total_lines);
432
433 if page_num >= config.first_page && (config.last_page == 0 || page_num <= config.last_page)
434 {
435 if !config.omit_header && !config.omit_pagination && !suppress_header {
437 write_header(output, &date_str, header_str, page_num, config)?;
438 }
439
440 if columns > 1 {
442 write_multicolumn_body(
443 output,
444 &all_lines[line_idx..page_end],
445 effective_config,
446 columns,
447 &mut line_number,
448 body_lines_per_page,
449 )?;
450 } else {
451 write_single_column_body(
452 output,
453 &all_lines[line_idx..page_end],
454 effective_config,
455 &mut line_number,
456 body_lines_per_page,
457 )?;
458 }
459
460 if !config.omit_header && !config.omit_pagination && !suppress_header {
462 write_footer(output, config)?;
463 }
464 }
465
466 line_idx = page_end;
467 page_num += 1;
468
469 if line_idx >= total_lines {
471 break;
472 }
473 }
474
475 Ok(())
476}
477
478pub fn pr_merge<W: Write>(
480 inputs: &[Vec<String>],
481 output: &mut W,
482 config: &PrConfig,
483 _filenames: &[&str],
484 file_dates: &[SystemTime],
485) -> io::Result<()> {
486 let date = file_dates.first().copied().unwrap_or_else(SystemTime::now);
487 let date_str = format_header_date(&date, &config.date_format);
488 let header_str = config.header.as_deref().unwrap_or("");
489
490 let suppress_header = !config.omit_header
491 && !config.omit_pagination
492 && config.page_length <= HEADER_LINES + FOOTER_LINES;
493 let body_lines_per_page = if config.omit_header || config.omit_pagination {
494 if config.page_length > 0 {
495 config.page_length
496 } else {
497 DEFAULT_PAGE_LENGTH
498 }
499 } else if suppress_header {
500 config.page_length
501 } else {
502 config.page_length - HEADER_LINES - FOOTER_LINES
503 };
504
505 let input_lines_per_page = if config.double_space {
506 (body_lines_per_page + 1) / 2
507 } else {
508 body_lines_per_page
509 };
510
511 let num_files = inputs.len();
512 let explicit_sep = has_explicit_separator(config);
513 let col_sep = get_column_separator(config);
514 let col_width = if explicit_sep {
515 if num_files > 1 {
516 (config
517 .page_width
518 .saturating_sub(col_sep.len() * (num_files - 1)))
519 / num_files
520 } else {
521 config.page_width
522 }
523 } else {
524 config.page_width / num_files
525 };
526
527 let max_lines = inputs.iter().map(|f| f.len()).max().unwrap_or(0);
528 let mut page_num = 1usize;
529 let mut line_idx = 0;
530 let mut line_number = config.first_line_number;
531
532 while line_idx < max_lines {
533 let page_end = (line_idx + input_lines_per_page).min(max_lines);
534
535 if page_num >= config.first_page && (config.last_page == 0 || page_num <= config.last_page)
536 {
537 if !config.omit_header && !config.omit_pagination && !suppress_header {
538 write_header(output, &date_str, header_str, page_num, config)?;
539 }
540
541 let indent_str = " ".repeat(config.indent);
542 let mut body_lines_written = 0;
543 for i in line_idx..page_end {
544 if config.double_space && body_lines_written > 0 {
545 writeln!(output)?;
546 body_lines_written += 1;
547 }
548
549 output.write_all(indent_str.as_bytes())?;
550 let mut abs_pos = config.indent;
551
552 if let Some((sep, digits)) = config.number_lines {
553 write!(output, "{:>width$}{}", line_number, sep, width = digits)?;
554 abs_pos += digits + 1;
555 line_number += 1;
556 }
557
558 for (fi, file_lines) in inputs.iter().enumerate() {
559 let content = if i < file_lines.len() {
560 &file_lines[i]
561 } else {
562 ""
563 };
564 let truncated = if !explicit_sep && content.len() > col_width.saturating_sub(1)
565 {
566 &content[..col_width.saturating_sub(1)]
568 } else if explicit_sep && config.truncate_lines && content.len() > col_width {
569 &content[..col_width]
571 } else {
572 content
573 };
574 if fi < num_files - 1 {
575 if explicit_sep {
577 if fi > 0 {
579 write!(output, "{}", col_sep)?;
580 }
581 write!(output, "{}", truncated)?;
582 abs_pos += truncated.len() + if fi > 0 { col_sep.len() } else { 0 };
583 } else {
584 write!(output, "{}", truncated)?;
585 abs_pos += truncated.len();
586 let target = (fi + 1) * col_width + config.indent;
587 write_column_padding(output, abs_pos, target)?;
588 abs_pos = target;
589 }
590 } else {
591 if explicit_sep && fi > 0 {
593 write!(output, "{}", col_sep)?;
594 }
595 write!(output, "{}", truncated)?;
596 }
597 }
598 writeln!(output)?;
599 body_lines_written += 1;
600 }
601
602 while body_lines_written < body_lines_per_page {
604 writeln!(output)?;
605 body_lines_written += 1;
606 }
607
608 if !config.omit_header && !config.omit_pagination && !suppress_header {
609 write_footer(output, config)?;
610 }
611 }
612
613 line_idx = page_end;
614 page_num += 1;
615 }
616
617 Ok(())
618}
619
620fn write_header<W: Write>(
622 output: &mut W,
623 date_str: &str,
624 header: &str,
625 page_num: usize,
626 config: &PrConfig,
627) -> io::Result<()> {
628 output.write_all(b"\n\n")?;
630
631 let line_width = config.page_width;
633
634 let left = date_str;
635 let center = header;
636 let left_len = left.len();
637 let center_len = center.len();
638
639 let mut page_buf = [0u8; 32];
641 let page_str = format_page_number(page_num, &mut page_buf);
642 let right_len = page_str.len();
643
644 if left_len + center_len + right_len + 2 >= line_width {
646 output.write_all(left.as_bytes())?;
647 output.write_all(b" ")?;
648 output.write_all(center.as_bytes())?;
649 output.write_all(b" ")?;
650 output.write_all(page_str)?;
651 output.write_all(b"\n")?;
652 } else {
653 let total_spaces = line_width - left_len - center_len - right_len;
654 let left_spaces = total_spaces / 2;
655 let right_spaces = total_spaces - left_spaces;
656 output.write_all(left.as_bytes())?;
657 write_spaces(output, left_spaces)?;
658 output.write_all(center.as_bytes())?;
659 write_spaces(output, right_spaces)?;
660 output.write_all(page_str)?;
661 output.write_all(b"\n")?;
662 }
663
664 output.write_all(b"\n\n")?;
666
667 Ok(())
668}
669
670#[inline]
672fn format_page_number(page_num: usize, buf: &mut [u8; 32]) -> &[u8] {
673 const PREFIX: &[u8] = b"Page ";
674 let prefix_len = PREFIX.len();
675 buf[..prefix_len].copy_from_slice(PREFIX);
676 let mut num_buf = [0u8; 20];
678 let mut n = page_num;
679 let mut pos = 19;
680 loop {
681 num_buf[pos] = b'0' + (n % 10) as u8;
682 n /= 10;
683 if n == 0 {
684 break;
685 }
686 pos -= 1;
687 }
688 let num_len = 20 - pos;
689 buf[prefix_len..prefix_len + num_len].copy_from_slice(&num_buf[pos..20]);
690 &buf[..prefix_len + num_len]
691}
692
693fn write_footer<W: Write>(output: &mut W, config: &PrConfig) -> io::Result<()> {
695 if config.form_feed {
696 output.write_all(b"\x0c")?;
697 } else {
698 output.write_all(b"\n\n\n\n\n")?;
699 }
700 Ok(())
701}
702
703fn write_single_column_body<W: Write>(
705 output: &mut W,
706 lines: &[&str],
707 config: &PrConfig,
708 line_number: &mut usize,
709 body_lines_per_page: usize,
710) -> io::Result<()> {
711 let indent_str = " ".repeat(config.indent);
712 let content_width = if config.truncate_lines {
713 compute_content_width(config)
714 } else {
715 0
716 };
717 let mut body_lines_written = 0;
718 let mut num_buf = [0u8; 32];
720
721 for line in lines.iter() {
722 output.write_all(indent_str.as_bytes())?;
723
724 if let Some((sep, digits)) = config.number_lines {
725 let num_str = format_line_number(*line_number, sep, digits, &mut num_buf);
727 output.write_all(num_str)?;
728 *line_number += 1;
729 }
730
731 let content = if config.truncate_lines {
732 if line.len() > content_width {
733 &line[..content_width]
734 } else {
735 line
736 }
737 } else {
738 line
739 };
740
741 output.write_all(content.as_bytes())?;
743 output.write_all(b"\n")?;
744 body_lines_written += 1;
745 if body_lines_written >= body_lines_per_page {
746 break;
747 }
748
749 if config.double_space {
751 output.write_all(b"\n")?;
752 body_lines_written += 1;
753 if body_lines_written >= body_lines_per_page {
754 break;
755 }
756 }
757 }
758
759 if !config.omit_header && !config.omit_pagination {
761 while body_lines_written < body_lines_per_page {
762 output.write_all(b"\n")?;
763 body_lines_written += 1;
764 }
765 }
766
767 Ok(())
768}
769
770#[inline]
773fn format_line_number(num: usize, sep: char, digits: usize, buf: &mut [u8; 32]) -> &[u8] {
774 let mut n = num;
776 let mut pos = 31;
777 loop {
778 buf[pos] = b'0' + (n % 10) as u8;
779 n /= 10;
780 if n == 0 || pos == 0 {
781 break;
782 }
783 pos -= 1;
784 }
785 let num_digits = 32 - pos;
786 let padding = if digits > num_digits {
788 digits - num_digits
789 } else {
790 0
791 };
792 let total_len = padding + num_digits + sep.len_utf8();
793 let start = 32 - num_digits;
796 let sep_byte = sep as u8; let out_start = 32usize.saturating_sub(total_len);
800 for i in out_start..out_start + padding {
802 buf[i] = b' ';
803 }
804 if out_start + padding != start {
806 let src = start;
807 let dst = out_start + padding;
808 for i in 0..num_digits {
809 buf[dst + i] = buf[src + i];
810 }
811 }
812 buf[out_start + padding + num_digits] = sep_byte;
814 &buf[out_start..out_start + total_len]
815}
816
817fn compute_content_width(config: &PrConfig) -> usize {
819 let mut w = config.page_width;
820 w = w.saturating_sub(config.indent);
821 if let Some((_, digits)) = config.number_lines {
822 w = w.saturating_sub(digits + 1); }
824 w
825}
826
827fn write_multicolumn_body<W: Write>(
829 output: &mut W,
830 lines: &[&str],
831 config: &PrConfig,
832 columns: usize,
833 line_number: &mut usize,
834 body_lines_per_page: usize,
835) -> io::Result<()> {
836 let explicit_sep = has_explicit_separator(config);
837 let col_sep = get_column_separator(config);
838 let col_width = if explicit_sep {
841 if columns > 1 {
842 (config
843 .page_width
844 .saturating_sub(col_sep.len() * (columns - 1)))
845 / columns
846 } else {
847 config.page_width
848 }
849 } else {
850 config.page_width / columns
851 };
852
853 let indent_str = " ".repeat(config.indent);
854 let mut body_lines_written = 0;
855
856 if config.across {
857 let mut i = 0;
859 while i < lines.len() {
860 if config.double_space && body_lines_written > 0 {
861 writeln!(output)?;
862 body_lines_written += 1;
863 if body_lines_written >= body_lines_per_page {
864 break;
865 }
866 }
867
868 output.write_all(indent_str.as_bytes())?;
869 let mut abs_pos = config.indent;
870
871 let mut last_data_col = 0;
873 for col in 0..columns {
874 let li = i + col;
875 if li < lines.len() {
876 last_data_col = col;
877 }
878 }
879
880 for col in 0..columns {
881 let li = i + col;
882 if li < lines.len() {
883 if explicit_sep && col > 0 {
884 write!(output, "{}", col_sep)?;
885 abs_pos += col_sep.len();
886 }
887 if let Some((sep, digits)) = config.number_lines {
888 write!(output, "{:>width$}{}", line_number, sep, width = digits)?;
889 abs_pos += digits + 1;
890 *line_number += 1;
891 }
892 let content = lines[li];
893 let truncated = if config.truncate_lines && content.len() > col_width {
894 &content[..col_width]
895 } else {
896 content
897 };
898 output.write_all(truncated.as_bytes())?;
899 abs_pos += truncated.len();
900 if col < last_data_col && !explicit_sep {
901 let target = (col + 1) * col_width + config.indent;
902 write_column_padding(output, abs_pos, target)?;
903 abs_pos = target;
904 }
905 }
906 }
907 output.write_all(b"\n")?;
908 body_lines_written += 1;
909 i += columns;
910 }
911 } else {
912 let n = lines.len();
916 let base = n / columns;
917 let extra = n % columns;
918
919 let mut col_starts = vec![0usize; columns + 1];
921 for col in 0..columns {
922 let col_lines = base + if col < extra { 1 } else { 0 };
923 col_starts[col + 1] = col_starts[col] + col_lines;
924 }
925
926 let num_rows = if extra > 0 { base + 1 } else { base };
928
929 for row in 0..num_rows {
930 if config.double_space && row > 0 {
931 writeln!(output)?;
932 body_lines_written += 1;
933 if body_lines_written >= body_lines_per_page {
934 break;
935 }
936 }
937
938 output.write_all(indent_str.as_bytes())?;
939 let mut abs_pos = config.indent;
940
941 let mut last_data_col = 0;
943 for col in 0..columns {
944 let col_lines = col_starts[col + 1] - col_starts[col];
945 if row < col_lines {
946 last_data_col = col;
947 }
948 }
949
950 for col in 0..columns {
951 let col_lines = col_starts[col + 1] - col_starts[col];
952 let li = col_starts[col] + row;
953 if row < col_lines {
954 if explicit_sep && col > 0 {
955 write!(output, "{}", col_sep)?;
956 abs_pos += col_sep.len();
957 }
958 if let Some((sep, digits)) = config.number_lines {
959 let num = config.first_line_number + li;
960 write!(output, "{:>width$}{}", num, sep, width = digits)?;
961 abs_pos += digits + 1;
962 }
963 let content = lines[li];
964 let truncated = if config.truncate_lines && content.len() > col_width {
965 &content[..col_width]
966 } else {
967 content
968 };
969 output.write_all(truncated.as_bytes())?;
970 abs_pos += truncated.len();
971 if col < last_data_col && !explicit_sep {
972 let target = (col + 1) * col_width + config.indent;
974 write_column_padding(output, abs_pos, target)?;
975 abs_pos = target;
976 }
977 } else if col <= last_data_col {
978 if explicit_sep {
980 if col > 0 {
981 write!(output, "{}", col_sep)?;
982 abs_pos += col_sep.len();
983 }
984 } else {
986 let target = (col + 1) * col_width + config.indent;
987 write_column_padding(output, abs_pos, target)?;
988 abs_pos = target;
989 }
990 }
991 }
993 output.write_all(b"\n")?;
994 body_lines_written += 1;
995 }
996 if config.number_lines.is_some() {
998 *line_number += lines.len();
999 }
1000 }
1001
1002 if !config.omit_header && !config.omit_pagination {
1004 while body_lines_written < body_lines_per_page {
1005 output.write_all(b"\n")?;
1006 body_lines_written += 1;
1007 }
1008 }
1009
1010 Ok(())
1011}