1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

pr: add multi column printing

This commit is contained in:
tilakpatidar 2018-12-15 09:18:41 +05:30 committed by Max Semenik
parent 2897039000
commit f799d22b7d

View file

@ -67,7 +67,7 @@ struct OutputOptions {
} }
struct ColumnModeOptions { struct ColumnModeOptions {
width: usize, width: Option<usize>,
columns: usize, columns: usize,
column_separator: String, column_separator: String,
} }
@ -78,6 +78,25 @@ impl AsRef<OutputOptions> for OutputOptions {
} }
} }
impl OutputOptions {
fn get_columns(&self) -> usize {
self.as_ref()
.column_mode_options.as_ref()
.map(|i| i.columns)
.unwrap_or(1)
}
fn lines_to_read_for_page(&self) -> usize {
let content_lines_per_page = &self.as_ref().content_lines_per_page;
let columns = self.get_columns();
if self.as_ref().double_space {
(content_lines_per_page / 2) * columns
} else {
content_lines_per_page * columns
}
}
}
struct NumberingMode { struct NumberingMode {
/// Line numbering mode /// Line numbering mode
width: usize, width: usize,
@ -393,13 +412,13 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result<Ou
let suppress_errors = matches.opt_present(SUPPRESS_PRINTING_ERROR); let suppress_errors = matches.opt_present(SUPPRESS_PRINTING_ERROR);
let page_separator_char = matches.opt_str(FORM_FEED_OPTION).map(|i| { let page_separator_char = matches.opt_str(FORM_FEED_OPTION).map(|_i| {
'\u{000A}'.to_string() '\u{000A}'.to_string()
}).unwrap_or("\n".to_string()); }).unwrap_or("\n".to_string());
let column_width = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::<usize>()) { let column_width = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::<usize>()) {
Some(res) => res?, Some(res) => Some(res?),
_ => DEFAULT_COLUMN_WIDTH _ => None
}; };
let column_mode_options = match matches.opt_str(COLUMN_OPTION).map(|i| { let column_mode_options = match matches.opt_str(COLUMN_OPTION).map(|i| {
@ -408,7 +427,10 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result<Ou
Some(res) => { Some(res) => {
Some(ColumnModeOptions { Some(ColumnModeOptions {
columns: res?, columns: res?,
width: column_width, width: match column_width {
Some(x) => Some(x),
None => Some(DEFAULT_COLUMN_WIDTH)
},
column_separator: DEFAULT_COLUMN_SEPARATOR.to_string(), column_separator: DEFAULT_COLUMN_SEPARATOR.to_string(),
}) })
} }
@ -452,21 +474,16 @@ fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
let mut i = 0; let mut i = 0;
let mut page: usize = 0; let mut page: usize = 0;
let mut buffered_content: Vec<String> = Vec::new(); let mut buffered_content: Vec<String> = Vec::new();
let content_lines_per_page = options.as_ref().content_lines_per_page; let read_lines_per_page = options.lines_to_read_for_page();
let columns = _get_columns(options); let mut line_number = 0;
let lines_per_page = if options.as_ref().double_space {
(content_lines_per_page / 2) * columns
} else {
content_lines_per_page * columns
};
for line in BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines() { for line in BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines() {
if i == lines_per_page { if i == read_lines_per_page {
page = page + 1; page = page + 1;
i = 0; i = 0;
if !_is_within_page_range(options, &page) { if !_is_within_page_range(options, &page) {
return Ok(0); return Ok(0);
} }
print_page(&buffered_content, options, &page)?; line_number += print_page(&buffered_content, options, &page, &line_number)?;
buffered_content = Vec::new(); buffered_content = Vec::new();
} }
buffered_content.push(line?); buffered_content.push(line?);
@ -478,39 +495,33 @@ fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
if !_is_within_page_range(options, &page) { if !_is_within_page_range(options, &page) {
return Ok(0); return Ok(0);
} }
print_page(&buffered_content, options, &page)?; print_page(&buffered_content, options, &page, &line_number)?;
} }
return Ok(0); return Ok(0);
} }
fn _get_columns(options: &OutputOptions) -> usize {
options.as_ref().column_mode_options.as_ref().map(|i| i.columns).unwrap_or(1)
}
fn _is_within_page_range(options: &OutputOptions, page: &usize) -> bool { fn _is_within_page_range(options: &OutputOptions, page: &usize) -> bool {
let start_page = options.as_ref().start_page.as_ref(); let start_page = options.as_ref().start_page.as_ref();
let last_page = options.as_ref().end_page.as_ref(); let last_page = options.as_ref().end_page.as_ref();
(start_page.is_none() || page >= start_page.unwrap()) && (last_page.is_none() || page <= last_page.unwrap()) (start_page.is_none() || page >= start_page.unwrap()) && (last_page.is_none() || page <= last_page.unwrap())
} }
fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize) -> Result<usize, Error> { fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize, line_number: &usize) -> Result<usize, Error> {
let page_separator = options.as_ref().page_separator_char.as_bytes(); let page_separator = options.as_ref().page_separator_char.as_bytes();
let header: Vec<String> = header_content(options, page); let header: Vec<String> = header_content(options, page);
let trailer_content: Vec<String> = trailer_content(options); let trailer_content: Vec<String> = trailer_content(options);
let out: &mut Stdout = &mut stdout(); let out: &mut Stdout = &mut stdout();
let line_separator = options.as_ref().line_separator.as_bytes(); let line_separator = options.as_ref().line_separator.as_bytes();
let mut lines_written = 0;
out.lock(); out.lock();
for x in header { for x in header {
out.write(x.as_bytes())?; out.write(x.as_bytes())?;
out.write(line_separator)?; out.write(line_separator)?;
lines_written += 1;
} }
lines_written += write_columns(lines, options, page_separator, out, line_separator, page)?; let lines_written = write_columns(lines, options, out, line_number)?;
for index in 0..trailer_content.len() { for index in 0..trailer_content.len() {
let x: &String = trailer_content.get(index).unwrap(); let x: &String = trailer_content.get(index).unwrap();
@ -520,43 +531,88 @@ fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize) -> Res
} else { } else {
out.write(line_separator)?; out.write(line_separator)?;
} }
lines_written += 1;
} }
out.flush()?; out.flush()?;
Ok(lines_written) Ok(lines_written)
} }
fn write_columns(lines: &Vec<String>, options: &OutputOptions, page_separator: &[u8], out: &mut Stdout, line_separator: &[u8], page: &usize) -> Result<usize, Error> { fn write_columns(lines: &Vec<String>, options: &OutputOptions, out: &mut Stdout, line_number: &usize) -> Result<usize, Error> {
let line_separator = options.as_ref().line_separator.as_bytes();
let page_separator = options.as_ref().page_separator_char.as_bytes();
let content_lines_per_page = options.as_ref().content_lines_per_page; let content_lines_per_page = options.as_ref().content_lines_per_page;
let prev_lines = content_lines_per_page * (page - 1);
let width: usize = options.as_ref() let width: usize = options.as_ref()
.number.as_ref() .number.as_ref()
.map(|i| i.width) .map(|i| i.width)
.unwrap_or(0); .unwrap_or(0);
let separator: String = options.as_ref() let number_separator: String = options.as_ref()
.number.as_ref() .number.as_ref()
.map(|i| i.separator.to_string()) .map(|i| i.separator.to_string())
.unwrap_or(NumberingMode::default().separator); .unwrap_or(NumberingMode::default().separator);
let mut i = 0; let blank_line = "".to_string();
for x in lines { let columns = options.get_columns();
if options.number.is_none() {
out.write(x.as_bytes())?;
} else {
let fmtd_line_number: String = get_fmtd_line_number(&width, prev_lines + i, &separator);
out.write(format!("{}{}", fmtd_line_number, x).as_bytes())?;
}
let col_sep: &String = options.as_ref()
.column_mode_options.as_ref()
.map(|i| &i.column_separator)
.unwrap_or(&blank_line);
let col_width: Option<usize> = options.as_ref()
.column_mode_options.as_ref()
.map(|i| i.width)
.unwrap_or(None);
let mut i = 0;
let is_number_mode = options.number.is_some();
for start in 0..content_lines_per_page {
let indexes: Vec<usize> = get_indexes(start, content_lines_per_page, columns);
let mut line = String::new();
for index in indexes {
let read_line: &String = lines.get(index).unwrap_or(&blank_line);
let next_line_number = line_number + index + 1;
let trimmed_line = get_line_for_printing(
next_line_number, &width,
&number_separator, columns,
col_sep, col_width,
read_line, is_number_mode);
line.push_str(&trimmed_line);
i += 1;
}
out.write(line.as_bytes())?;
if i == lines.len() { if i == lines.len() {
out.write(page_separator)?; out.write(page_separator)?;
} else { } else {
out.write(line_separator)?; out.write(line_separator)?;
} }
i += 1;
} }
Ok(i) Ok(i)
} }
fn get_line_for_printing(line_number: usize, width: &usize,
separator: &String, columns: usize, col_sep: &String, col_width: Option<usize>,
read_line: &String, is_number_mode: bool) -> String {
let fmtd_line_number: String = if is_number_mode {
get_fmtd_line_number(&width, line_number, &separator)
} else {
"".to_string()
};
let complete_line = format!("{}{}{}", fmtd_line_number, read_line, col_sep);
// TODO Adjust the width according to -n option
// TODO Line has less content than the column width
col_width.map(|i| complete_line.chars().take(i / columns).collect()).unwrap_or(complete_line)
}
fn get_indexes(start: usize, content_lines_per_page: usize, columns: usize) -> Vec<usize> {
let mut indexes: Vec<usize> = Vec::new();
let mut offset = start;
indexes.push(offset);
for _col in 1..columns {
offset += content_lines_per_page;
indexes.push(offset);
}
indexes
}
fn get_fmtd_line_number(width: &usize, line_number: usize, separator: &String) -> String { fn get_fmtd_line_number(width: &usize, line_number: usize, separator: &String) -> String {
let line_str = line_number.to_string(); let line_str = line_number.to_string();
if line_str.len() >= *width { if line_str.len() >= *width {