diff --git a/src/pr/pr.rs b/src/pr/pr.rs index 06c991c29..87aadf80c 100644 --- a/src/pr/pr.rs +++ b/src/pr/pr.rs @@ -10,35 +10,36 @@ extern crate unix_socket; #[macro_use] extern crate quick_error; -extern crate itertools; extern crate chrono; extern crate getopts; +extern crate itertools; extern crate uucore; -use std::io::{BufRead, BufReader, stdin, stdout, stderr, Error, Read, Write, Stdout, Lines, Stdin}; -use std::vec::Vec; use chrono::offset::Local; use chrono::DateTime; +use getopts::{HasArg, Occur}; use getopts::{Matches, Options}; -use std::fs::{metadata, File, Metadata}; -#[cfg(unix)] -use std::os::unix::fs::FileTypeExt; +use itertools::structs::KMergeBy; +use itertools::{GroupBy, Itertools}; use quick_error::ResultExt; use std::convert::From; -use getopts::{HasArg, Occur}; +use std::fs::{metadata, File, Metadata}; +use std::io::{stderr, stdin, stdout, BufRead, BufReader, Lines, Read, Stdin, Stdout, Write}; +use std::iter::{Enumerate, Map, SkipWhile, TakeWhile}; use std::num::ParseIntError; -use itertools::{Itertools, GroupBy}; -use std::iter::{Enumerate, Map, TakeWhile, SkipWhile}; -use itertools::structs::KMergeBy; +#[cfg(unix)] +use std::os::unix::fs::FileTypeExt; +use std::vec::Vec; + +type IOError = std::io::Error; static NAME: &str = "pr"; static VERSION: &str = env!("CARGO_PKG_VERSION"); static TAB: char = '\t'; +static NEW_LINE: &str = "\n"; static LINES_PER_PAGE: usize = 66; static HEADER_LINES_PER_PAGE: usize = 5; static TRAILER_LINES_PER_PAGE: usize = 5; -static NUMBERING_MODE_DEFAULT_SEPARATOR: &str = "\t"; -static NUMBERING_MODE_DEFAULT_WIDTH: usize = 5; static STRING_HEADER_OPTION: &str = "h"; static DOUBLE_SPACE_OPTION: &str = "d"; static NUMBERING_MODE_OPTION: &str = "n"; @@ -57,7 +58,8 @@ static OFFSET_SPACES_OPTION: &str = "o"; static FILE_STDIN: &str = "-"; static READ_BUFFER_SIZE: usize = 1024 * 64; static DEFAULT_COLUMN_WIDTH: usize = 72; -static DEFAULT_COLUMN_SEPARATOR: &str = "\t"; +static DEFAULT_COLUMN_SEPARATOR: &char = &TAB; +static BLANK_STRING: &str = ""; struct OutputOptions { /// Line numbering mode @@ -67,7 +69,7 @@ struct OutputOptions { line_separator: String, content_line_separator: String, last_modified_time: String, - start_page: Option, + start_page: usize, end_page: Option, display_header: bool, display_trailer: bool, @@ -75,15 +77,15 @@ struct OutputOptions { page_separator_char: String, column_mode_options: Option, merge_files_print: Option, - offset_spaces: usize + offset_spaces: usize, } struct FileLine { file_id: usize, line_number: usize, page_number: usize, - key: usize, - line_content: Result, + group_key: usize, + line_content: Result, } impl AsRef for FileLine { @@ -93,7 +95,7 @@ impl AsRef for FileLine { } struct ColumnModeOptions { - width: Option, + width: usize, columns: usize, column_separator: String, across_mode: bool, @@ -115,21 +117,15 @@ struct NumberingMode { impl Default for NumberingMode { fn default() -> NumberingMode { NumberingMode { - width: NUMBERING_MODE_DEFAULT_WIDTH, - separator: NUMBERING_MODE_DEFAULT_SEPARATOR.to_string(), + width: 5, + separator: TAB.to_string(), first_number: 1, } } } -impl From for PrError { - fn from(err: Error) -> Self { - PrError::EncounteredErrors(err.to_string()) - } -} - -impl From for PrError { - fn from(err: std::num::ParseIntError) -> Self { +impl From for PrError { + fn from(err: IOError) -> Self { PrError::EncounteredErrors(err.to_string()) } } @@ -137,8 +133,8 @@ impl From for PrError { quick_error! { #[derive(Debug)] enum PrError { - Input(err: Error, path: String) { - context(path: &'a str, err: Error) -> (err, path.to_owned()) + Input(err: IOError, path: String) { + context(path: &'a str, err: IOError) -> (err, path.to_owned()) display("pr: Reading from input {0} gave error", path) cause(err) } @@ -181,7 +177,7 @@ pub fn uumain(args: Vec) -> i32 { STRING_HEADER_OPTION, "header", "Use the string header to replace the file name \ - in the header line.", + in the header line.", "STRING", HasArg::Yes, Occur::Optional, @@ -353,7 +349,6 @@ pub fn uumain(args: Vec) -> i32 { files.insert(0, FILE_STDIN.to_owned()); } - if matches.opt_present("help") { return print_usage(&mut opts, &matches); } @@ -381,7 +376,7 @@ pub fn uumain(args: Vec) -> i32 { print_error(&matches, error); 1 } - _ => 0 + _ => 0, }; if status != 0 { return status; @@ -403,10 +398,13 @@ fn print_error(matches: &Matches, err: PrError) { fn print_usage(opts: &mut Options, matches: &Matches) -> i32 { println!("{} {} -- print files", NAME, VERSION); println!(); - println!("Usage: {} [+page] [-column] [-adFfmprt] [[-e] [char] [gap]] + println!( + "Usage: {} [+page] [-column] [-adFfmprt] [[-e] [char] [gap]] [-L locale] [-h header] [[-i] [char] [gap]] [-l lines] [-o offset] [[-s] [char]] [[-n] [char] - [width]] [-w width] [-] [file ...].", NAME); + [width]] [-w width] [-] [file ...].", + NAME + ); println!(); let usage: &str = "The pr utility is a printing and pagination filter for text files. When multiple input files are spec- @@ -435,16 +433,39 @@ fn print_usage(opts: &mut Options, matches: &Matches) -> i32 { return 0; } +fn parse_usize(matches: &Matches, opt: &str) -> Option> { + let from_parse_error_to_pr_error = |value_to_parse: (String, String)| { + let i = value_to_parse.0; + let option = value_to_parse.1; + i.parse::().map_err(|_e| { + PrError::EncounteredErrors(format!("invalid {} argument '{}'", option, i)) + }) + }; + matches + .opt_str(opt) + .map(|i| (i, format!("-{}", opt))) + .map(from_parse_error_to_pr_error) +} + fn build_options(matches: &Matches, paths: &Vec) -> Result { + let invalid_pages_map = |i: String| { + let unparsed_value: String = matches.opt_str(PAGE_RANGE_OPTION).unwrap(); + i.parse::().map_err(|_e| { + PrError::EncounteredErrors(format!("invalid --pages argument '{}'", unparsed_value)) + }) + }; + let is_merge_mode: bool = matches.opt_present(MERGE_FILES_PRINT); if is_merge_mode && matches.opt_present(COLUMN_OPTION) { - let err_msg: String = "cannot specify number of columns when printing in parallel".to_string(); + let err_msg: String = + "cannot specify number of columns when printing in parallel".to_string(); return Err(PrError::EncounteredErrors(err_msg)); } if is_merge_mode && matches.opt_present(ACROSS_OPTION) { - let err_msg: String = "cannot specify both printing across and printing in parallel".to_string(); + let err_msg: String = + "cannot specify both printing across and printing in parallel".to_string(); return Err(PrError::EncounteredErrors(err_msg)); } @@ -454,65 +475,71 @@ fn build_options(matches: &Matches, paths: &Vec) -> Result().unwrap_or(default_first_number) - }).unwrap_or(default_first_number); + let first_number: usize = + parse_usize(matches, FIRST_LINE_NUMBER_OPTION).unwrap_or(Ok(default_first_number))?; - let numbering_options: Option = matches.opt_str(NUMBERING_MODE_OPTION).map(|i| { - let parse_result: Result = i.parse::(); + let numbering_options: Option = matches + .opt_str(NUMBERING_MODE_OPTION) + .map(|i| { + let parse_result: Result = i.parse::(); - let separator: String = if parse_result.is_err() { - if is_a_file(&i) { + let separator: String = if parse_result.is_err() { + if is_a_file(&i) { + NumberingMode::default().separator + } else { + i[0..1].to_string() + } + } else { NumberingMode::default().separator - } else { - i[0..1].to_string() - } - } else { - NumberingMode::default().separator - }; + }; - let width: usize = if parse_result.is_err() { - if is_a_file(&i) { - NumberingMode::default().width + let width: usize = if parse_result.is_err() { + if is_a_file(&i) { + NumberingMode::default().width + } else { + i[1..] + .parse::() + .unwrap_or(NumberingMode::default().width) + } } else { - i[1..].parse::().unwrap_or(NumberingMode::default().width) - } - } else { - parse_result.unwrap() - }; + parse_result.unwrap() + }; - NumberingMode { - width, - separator, - first_number, - } - }).or_else(|| { - if matches.opt_present(NUMBERING_MODE_OPTION) { - return Some(NumberingMode::default()); - } - return None; - }); + NumberingMode { + width, + separator, + first_number, + } + }) + .or_else(|| { + if matches.opt_present(NUMBERING_MODE_OPTION) { + return Some(NumberingMode::default()); + } + return None; + }); let double_space: bool = matches.opt_present(DOUBLE_SPACE_OPTION); let content_line_separator: String = if double_space { - "\n\n".to_string() + NEW_LINE.repeat(2) } else { - "\n".to_string() + NEW_LINE.to_string() }; - let line_separator: String = "\n".to_string(); + let line_separator: String = NEW_LINE.to_string(); let last_modified_time: String = if is_merge_mode || paths[0].eq(FILE_STDIN) { current_time() @@ -520,48 +547,46 @@ fn build_options(matches: &Matches, paths: &Vec) -> Result| { - let unparsed_value: String = matches.opt_str(PAGE_RANGE_OPTION).unwrap(); - match i { - Ok(val) => Ok(val), - Err(_e) => Err(PrError::EncounteredErrors(format!("invalid --pages argument '{}'", unparsed_value))) - } + let start_page: usize = match matches + .opt_str(PAGE_RANGE_OPTION) + .map(|i| { + let x: Vec<&str> = i.split(":").collect(); + x[0].to_string() + }) + .map(invalid_pages_map) + { + Some(res) => res?, + _ => 1, }; - let start_page: Option = match matches.opt_str(PAGE_RANGE_OPTION).map(|i| { - let x: Vec<&str> = i.split(":").collect(); - x[0].parse::() - }).map(invalid_pages_map) - { - Some(res) => Some(res?), - _ => None - }; - - let end_page: Option = match matches.opt_str(PAGE_RANGE_OPTION) + let end_page: Option = match matches + .opt_str(PAGE_RANGE_OPTION) .filter(|i: &String| i.contains(":")) .map(|i: String| { let x: Vec<&str> = i.split(":").collect(); - x[1].parse::() + x[1].to_string() }) .map(invalid_pages_map) - { - Some(res) => Some(res?), - _ => None - }; + { + Some(res) => Some(res?), + _ => None, + }; - if start_page.is_some() && end_page.is_some() && start_page.unwrap() > end_page.unwrap() { - return Err(PrError::EncounteredErrors(format!("invalid --pages argument '{}:{}'", start_page.unwrap(), end_page.unwrap()))); + if end_page.is_some() && start_page > end_page.unwrap() { + return Err(PrError::EncounteredErrors(format!( + "invalid --pages argument '{}:{}'", + start_page, + end_page.unwrap() + ))); } - let page_length: usize = match matches.opt_str(PAGE_LENGTH_OPTION).map(|i| { - i.parse::() - }) { - Some(res) => res?, - _ => LINES_PER_PAGE - }; + let page_length: usize = + parse_usize(matches, PAGE_LENGTH_OPTION).unwrap_or(Ok(LINES_PER_PAGE))?; + let page_length_le_ht: bool = page_length < (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE); - let display_header_and_trailer: bool = !(page_length_le_ht) && !matches.opt_present(NO_HEADER_TRAILER_OPTION); + let display_header_and_trailer: bool = + !(page_length_le_ht) && !matches.opt_present(NO_HEADER_TRAILER_OPTION); let content_lines_per_page: usize = if page_length_le_ht { page_length @@ -569,45 +594,31 @@ fn build_options(matches: &Matches, paths: &Vec) -> Result = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::()) { - Some(res) => Some(res?), - _ => None - }; + let column_width: usize = + parse_usize(matches, COLUMN_WIDTH_OPTION).unwrap_or(Ok(DEFAULT_COLUMN_WIDTH))?; let across_mode: bool = matches.opt_present(ACROSS_OPTION); - let column_separator: String = matches.opt_str(COLUMN_SEPARATOR_OPTION) + let column_separator: String = matches + .opt_str(COLUMN_SEPARATOR_OPTION) .unwrap_or(DEFAULT_COLUMN_SEPARATOR.to_string()); - - let column_mode_options: Option = match matches.opt_str(COLUMN_OPTION).map(|i| { - i.parse::() - }) { - Some(res) => { - Some(ColumnModeOptions { - columns: res?, - width: match column_width { - Some(x) => Some(x), - None => Some(DEFAULT_COLUMN_WIDTH) - }, - column_separator, - across_mode, - }) - } - _ => None + let column_mode_options: Option = match parse_usize(matches, COLUMN_OPTION) { + Some(res) => Some(ColumnModeOptions { + columns: res?, + width: column_width, + column_separator, + across_mode, + }), + _ => None, }; - - let offset_spaces: usize = matches.opt_str(OFFSET_SPACES_OPTION) - .map(|i| { - match i.parse::() { - Ok(val)=> Ok(val), - Err(e)=> Err(PrError::EncounteredErrors("".to_string())) - } - }).unwrap_or(Ok(0))?; + + let offset_spaces: usize = parse_usize(matches, OFFSET_SPACES_OPTION).unwrap_or(Ok(0))?; Ok(OutputOptions { number: numbering_options, @@ -634,94 +645,81 @@ fn open(path: &str) -> Result, PrError> { return Ok(Box::new(stdin) as Box); } - metadata(path).map(|i: Metadata| { - let path_string = path.to_string(); - match i.file_type() { - #[cfg(unix)] - ft if ft.is_block_device() => - { - Err(PrError::UnknownFiletype(path_string)) + metadata(path) + .map(|i: Metadata| { + let path_string = path.to_string(); + match i.file_type() { + #[cfg(unix)] + ft if ft.is_block_device() => Err(PrError::UnknownFiletype(path_string)), + #[cfg(unix)] + ft if ft.is_char_device() => Err(PrError::UnknownFiletype(path_string)), + #[cfg(unix)] + ft if ft.is_fifo() => Err(PrError::UnknownFiletype(path_string)), + #[cfg(unix)] + ft if ft.is_socket() => Err(PrError::IsSocket(path_string)), + ft if ft.is_dir() => Err(PrError::IsDirectory(path_string)), + ft if ft.is_file() || ft.is_symlink() => { + Ok(Box::new(File::open(path).context(path)?) as Box) } - #[cfg(unix)] - ft if ft.is_char_device() => - { - Err(PrError::UnknownFiletype(path_string)) - } - #[cfg(unix)] - ft if ft.is_fifo() => - { - Err(PrError::UnknownFiletype(path_string)) - } - #[cfg(unix)] - ft if ft.is_socket() => - { - Err(PrError::IsSocket(path_string)) - } - ft if ft.is_dir() => Err(PrError::IsDirectory(path_string)), - ft if ft.is_file() || ft.is_symlink() => Ok(Box::new(File::open(path).context(path)?) as Box), - _ => Err(PrError::UnknownFiletype(path_string)) - } - }).unwrap_or(Err(PrError::NotExists(path.to_string()))) + _ => Err(PrError::UnknownFiletype(path_string)), + } + }) + .unwrap_or(Err(PrError::NotExists(path.to_string()))) } fn pr(path: &String, options: &OutputOptions) -> Result { - let start_page: &usize = options.start_page.as_ref().unwrap_or(&1); + let start_page: &usize = &options.start_page; let start_line_number: usize = get_start_line_number(options); let last_page: Option<&usize> = options.end_page.as_ref(); let lines_needed_per_page: usize = lines_to_read_for_page(options); - let file_line_groups: GroupBy>>>, _>, _>, _>, _>, _> = - BufReader::with_capacity(READ_BUFFER_SIZE, open(path).unwrap()) - .lines() - .enumerate() - .map(move |i: (usize, Result)| { - FileLine { - file_id: 0, - line_number: i.0, - line_content: i.1, - page_number: 0, - key: 0, - } - }) - .skip_while(move |file_line: &FileLine| { - // Skip the initial lines if not in page range - let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page; - file_line.line_number < (start_line_index_of_start_page) - }) - .take_while(move |file_line: &FileLine| { - // Only read the file until provided last page reached - last_page - .map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page)) - .unwrap_or(true) - }) - .map(move |file_line: FileLine| { - let page_number = ((file_line.line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize; - FileLine { - line_number: file_line.line_number + start_line_number, - page_number, - key: page_number, - ..file_line - } - }) // get display line number with line content - .group_by(|file_line: &FileLine| { - file_line.page_number - }); - + let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page; + let file_line_groups: GroupBy< + usize, + Map>>>, _>, _>, _>, _>, + _, + > = BufReader::with_capacity(READ_BUFFER_SIZE, open(path).unwrap()) + .lines() + .enumerate() + .map(|i: (usize, Result)| FileLine { + file_id: 0, + line_number: i.0, + line_content: i.1, + page_number: 0, + group_key: 0, + }) + .skip_while(|file_line: &FileLine| { + // Skip the initial lines if not in page range + file_line.line_number < (start_line_index_of_start_page) + }) + .take_while(|file_line: &FileLine| { + // Only read the file until provided last page reached + last_page + .map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page)) + .unwrap_or(true) + }) + .map(|file_line: FileLine| { + let page_number = + ((file_line.line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize; + FileLine { + line_number: file_line.line_number + start_line_number, + page_number, + group_key: page_number, + ..file_line + } + }) // get display line number with line content + .group_by(|file_line: &FileLine| file_line.group_key); for (page_number, file_line_group) in file_line_groups.into_iter() { let mut lines: Vec = Vec::new(); for file_line in file_line_group { if file_line.line_content.is_err() { - return Err(PrError::from(file_line.line_content.unwrap_err())); + return Err(file_line.line_content.unwrap_err().into()); } lines.push(file_line); } - let print_status: Result = print_page(&lines, options, &page_number); - if print_status.is_err() { - return Err(PrError::from(print_status.unwrap_err())); - } + print_page(&lines, options, &page_number)?; } - return Ok(0); } @@ -730,10 +728,18 @@ fn mpr(paths: &Vec, options: &OutputOptions) -> Result { let lines_needed_per_page: usize = lines_to_read_for_page(options); let lines_needed_per_page_f64: f64 = lines_needed_per_page as f64; - let start_page: &usize = options.start_page.as_ref().unwrap_or(&1); + let start_page: &usize = &options.start_page; let last_page: Option<&usize> = options.end_page.as_ref(); + let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page; - let file_line_groups: GroupBy>>>, _>, _>, _>, _>, _>, _> = paths + let file_line_groups: GroupBy< + usize, + KMergeBy< + Map>>>, _>, _>, _>, _>, + _, + >, + _, + > = paths .into_iter() .enumerate() .map(|indexed_path: (usize, &String)| { @@ -741,63 +747,56 @@ fn mpr(paths: &Vec, options: &OutputOptions) -> Result { BufReader::with_capacity(READ_BUFFER_SIZE, open(indexed_path.1).unwrap()) .lines() .enumerate() - .map(move |i: (usize, Result)| { - FileLine { - file_id: indexed_path.0, - line_number: i.0, - line_content: i.1, - page_number: 0, - key: 0, - } + .map(move |i: (usize, Result)| FileLine { + file_id: indexed_path.0, + line_number: i.0, + line_content: i.1, + page_number: 0, + group_key: 0, }) .skip_while(move |file_line: &FileLine| { // Skip the initial lines if not in page range - let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page; file_line.line_number < (start_line_index_of_start_page) }) .take_while(move |file_line: &FileLine| { - // Only read the file until provided last page reached - + // Only read the file until provided last page reached last_page .map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page)) .unwrap_or(true) }) .map(move |file_line: FileLine| { - let page_number = ((file_line.line_number + 2 - start_line_number) as f64 / (lines_needed_per_page_f64)).ceil() as usize; + let page_number = ((file_line.line_number + 2 - start_line_number) as f64 + / (lines_needed_per_page_f64)) + .ceil() as usize; FileLine { line_number: file_line.line_number + start_line_number, page_number, - key: page_number * nfiles + file_line.file_id, + group_key: page_number * nfiles + file_line.file_id, ..file_line } }) // get display line number with line content }) .kmerge_by(|a: &FileLine, b: &FileLine| { - if a.key == b.key { + if a.group_key == b.group_key { a.line_number < b.line_number } else { - a.key < b.key + a.group_key < b.group_key } }) - .group_by(|file_line: &FileLine| { - file_line.key - }); + .group_by(|file_line: &FileLine| file_line.group_key); let mut lines: Vec = Vec::new(); - let start_page: &usize = options.start_page.as_ref().unwrap_or(&1); + let start_page: &usize = &options.start_page; let mut page_counter: usize = *start_page; for (_key, file_line_group) in file_line_groups.into_iter() { for file_line in file_line_group { if file_line.line_content.is_err() { - return Err(PrError::from(file_line.line_content.unwrap_err())); + return Err(file_line.line_content.unwrap_err().into()); } let new_page_number = file_line.page_number; if page_counter != new_page_number { - fill_missing_files(&mut lines, lines_needed_per_page, &nfiles, page_counter); - let print_status: Result = print_page(&lines, options, &page_counter); - if print_status.is_err() { - return Err(PrError::from(print_status.unwrap_err())); - } + fill_missing_lines(&mut lines, lines_needed_per_page, &nfiles, page_counter); + print_page(&lines, options, &page_counter)?; lines = Vec::new(); } lines.push(file_line); @@ -805,76 +804,73 @@ fn mpr(paths: &Vec, options: &OutputOptions) -> Result { } } - fill_missing_files(&mut lines, lines_needed_per_page, &nfiles, page_counter); - let print_status: Result = print_page(&lines, options, &page_counter); - if print_status.is_err() { - return Err(PrError::from(print_status.unwrap_err())); - } - + fill_missing_lines(&mut lines, lines_needed_per_page, &nfiles, page_counter); + print_page(&lines, options, &page_counter)?; return Ok(0); } -fn fill_missing_files(lines: &mut Vec, lines_per_file: usize, nfiles: &usize, page_number: usize) { +fn fill_missing_lines( + lines: &mut Vec, + lines_per_file: usize, + nfiles: &usize, + page_number: usize, +) { let init_line_number = (page_number - 1) * lines_per_file + 1; - let final_line_number = page_number * lines_per_file; let mut file_id_counter: usize = 0; let mut line_number_counter: usize = init_line_number; - let mut lines_processed: usize = 0; - let mut file_id_missing: bool = false; + let mut lines_processed_per_file: usize = 0; for mut i in 0..lines_per_file * nfiles { - let file_id = lines.get(i).map(|i: &FileLine| i.file_id).unwrap_or(file_id_counter); + let file_id = lines + .get(i) + .map(|i: &FileLine| i.file_id) + .unwrap_or(file_id_counter); let line_number = lines.get(i).map(|i: &FileLine| i.line_number).unwrap_or(1); - if lines_processed == lines_per_file { + if lines_processed_per_file == lines_per_file { line_number_counter = init_line_number; file_id_counter += 1; - lines_processed = 0; - file_id_missing = false; - } - - if file_id_counter >= *nfiles { - file_id_counter = 0; - file_id_missing = false; + lines_processed_per_file = 0; } if file_id != file_id_counter { - file_id_missing = true; - } - - if file_id_missing { // Insert missing file_ids - lines.insert(i, FileLine { - file_id: file_id_counter, - line_number: line_number_counter, - line_content: Ok("".to_string()), - page_number, - key: 0, - }); + lines.insert( + i, + FileLine { + file_id: file_id_counter, + line_number: line_number_counter, + line_content: Ok("".to_string()), + page_number, + group_key: 0, + }, + ); line_number_counter += 1; - } else { + } else if line_number < line_number_counter { // Insert missing lines for a file_id - if line_number < line_number_counter { - line_number_counter += 1; - lines.insert(i, FileLine { + line_number_counter += 1; + lines.insert( + i, + FileLine { file_id, line_number: line_number_counter, line_content: Ok("".to_string()), page_number, - key: 0, - }); - } else { - line_number_counter = line_number; - if line_number_counter == final_line_number { - line_number_counter = init_line_number; - } - } + group_key: 0, + }, + ); + } else { + line_number_counter = line_number; } - lines_processed += 1; + lines_processed_per_file += 1; } } -fn print_page(lines: &Vec, options: &OutputOptions, page: &usize) -> Result { +fn print_page( + lines: &Vec, + options: &OutputOptions, + page: &usize, +) -> Result { let page_separator = options.page_separator_char.as_bytes(); let header: Vec = header_content(options, page); let trailer_content: Vec = trailer_content(options); @@ -902,7 +898,11 @@ fn print_page(lines: &Vec, options: &OutputOptions, page: &usize) -> R Ok(lines_written) } -fn write_columns(lines: &Vec, options: &OutputOptions, out: &mut Stdout) -> Result { +fn write_columns( + lines: &Vec, + options: &OutputOptions, + out: &mut Stdout, +) -> Result { let line_separator = options.content_line_separator.as_bytes(); let content_lines_per_page = if options.double_space { options.content_lines_per_page / 2 @@ -910,12 +910,10 @@ fn write_columns(lines: &Vec, options: &OutputOptions, out: &mut Stdou options.content_lines_per_page }; - let width: usize = options - .number.as_ref() - .map(|i| i.width) - .unwrap_or(0); + let width: usize = options.number.as_ref().map(|i| i.width).unwrap_or(0); let number_separator: String = options - .number.as_ref() + .number + .as_ref() .map(|i| i.separator.to_string()) .unwrap_or(NumberingMode::default().separator); @@ -923,46 +921,50 @@ fn write_columns(lines: &Vec, options: &OutputOptions, out: &mut Stdou let columns = options.merge_files_print.unwrap_or(get_columns(options)); let def_sep = DEFAULT_COLUMN_SEPARATOR.to_string(); let col_sep: &String = options - .column_mode_options.as_ref() + .column_mode_options + .as_ref() .map(|i| &i.column_separator) - .unwrap_or(options - .merge_files_print - .map(|_k| &def_sep) - .unwrap_or(&blank_line) + .unwrap_or( + options + .merge_files_print + .map(|_k| &def_sep) + .unwrap_or(&blank_line), ); + // TODO simplify let col_width: Option = options - .column_mode_options.as_ref() - .map(|i| i.width) - .unwrap_or(options - .merge_files_print - .map(|_k| Some(DEFAULT_COLUMN_WIDTH)) - .unwrap_or(None) + .column_mode_options + .as_ref() + .map(|i| Some(i.width)) + .unwrap_or( + options + .merge_files_print + .map(|_k| Some(DEFAULT_COLUMN_WIDTH)) + .unwrap_or(None), ); let across_mode = options - .column_mode_options.as_ref() + .column_mode_options + .as_ref() .map(|i| i.across_mode) .unwrap_or(false); - + let offset_spaces: &usize = &options.offset_spaces; let mut lines_printed = 0; let is_number_mode = options.number.is_some(); let fetch_indexes: Vec> = if across_mode { (0..content_lines_per_page) - .map(|a| - (0..columns) - .map(|i| a * columns + i) - .collect() - ).collect() + .map(|a| (0..columns).map(|i| a * columns + i).collect()) + .collect() } else { (0..content_lines_per_page) - .map(|start| + .map(|start| { (0..columns) .map(|i| start + content_lines_per_page * i) .collect() - ).collect() + }) + .collect() }; let spaces = " ".repeat(*offset_spaces); @@ -975,10 +977,20 @@ fn write_columns(lines: &Vec, options: &OutputOptions, out: &mut Stdou break; } let file_line: &FileLine = lines.get(index).unwrap(); - let trimmed_line: String = format!("{}{}", spaces, get_line_for_printing( - file_line, &width, &number_separator, columns, col_width, - is_number_mode, &options.merge_files_print, &i, - )); + let trimmed_line: String = format!( + "{}{}", + spaces, + get_line_for_printing( + file_line, + &width, + &number_separator, + columns, + col_width, + is_number_mode, + &options.merge_files_print, + &i, + ) + ); out.write(trimmed_line.as_bytes())?; if (i + 1) != indexes { out.write(col_sep.as_bytes())?; @@ -990,58 +1002,76 @@ fn write_columns(lines: &Vec, options: &OutputOptions, out: &mut Stdou Ok(lines_printed) } -fn get_line_for_printing(file_line: &FileLine, width: &usize, - separator: &String, columns: usize, - col_width: Option, - is_number_mode: bool, merge_files_print: &Option, - index: &usize, +fn get_line_for_printing( + file_line: &FileLine, + width: &usize, + separator: &String, + columns: usize, + col_width: Option, + is_number_mode: bool, + merge_files_print: &Option, + index: &usize, ) -> String { - let should_show_line_number_merge_file = merge_files_print.is_none() || index == &usize::min_value(); + let should_show_line_number_merge_file = + merge_files_print.is_none() || index == &usize::min_value(); let should_show_line_number = is_number_mode && should_show_line_number_merge_file; let fmtd_line_number: String = if should_show_line_number { get_fmtd_line_number(&width, file_line.line_number, &separator) } else { "".to_string() }; - let mut complete_line = format!("{}{}", fmtd_line_number, file_line.line_content.as_ref().unwrap()); + let mut complete_line = format!( + "{}{}", + fmtd_line_number, + file_line.line_content.as_ref().unwrap() + ); - let tab_count: usize = complete_line - .chars() - .filter(|i| i == &TAB) - .count(); + let tab_count: usize = complete_line.chars().filter(|i| i == &TAB).count(); let display_length = complete_line.len() + (tab_count * 7); -// TODO Adjust the width according to -n option -// TODO actual len of the string vs display len of string because of tabs - col_width.map(|i| { - let min_width = (i - (columns - 1)) / columns; - if display_length < min_width { - for _i in 0..(min_width - display_length) { - complete_line.push(' '); + // TODO Adjust the width according to -n option + // TODO actual len of the string vs display len of string because of tabs + col_width + .map(|i| { + let min_width = (i - (columns - 1)) / columns; + if display_length < min_width { + for _i in 0..(min_width - display_length) { + complete_line.push(' '); + } } - } - complete_line - .chars() - .take(min_width) - .collect() - }).unwrap_or(complete_line) + complete_line.chars().take(min_width).collect() + }) + .unwrap_or(complete_line) } fn get_fmtd_line_number(width: &usize, line_number: usize, separator: &String) -> String { let line_str = line_number.to_string(); if line_str.len() >= *width { - format!("{:>width$}{}", &line_str[line_str.len() - *width..], separator, width = width) + format!( + "{:>width$}{}", + &line_str[line_str.len() - *width..], + separator, + width = width + ) } else { format!("{:>width$}{}", line_str, separator, width = width) } } - fn header_content(options: &OutputOptions, page: &usize) -> Vec { if options.display_header { - let first_line: String = format!("{} {} Page {}", options.last_modified_time, options.header, page); - vec!["".to_string(), "".to_string(), first_line, "".to_string(), "".to_string()] + let first_line: String = format!( + "{} {} Page {}", + options.last_modified_time, options.header, page + ); + vec![ + BLANK_STRING.to_string(), + BLANK_STRING.to_string(), + first_line, + BLANK_STRING.to_string(), + BLANK_STRING.to_string(), + ] } else { Vec::new() } @@ -1049,12 +1079,17 @@ fn header_content(options: &OutputOptions, page: &usize) -> Vec { fn file_last_modified_time(path: &str) -> String { let file_metadata = metadata(path); - return file_metadata.map(|i| { - return i.modified().map(|x| { - let datetime: DateTime = x.into(); - datetime.format("%b %d %H:%M %Y").to_string() - }).unwrap_or(String::new()); - }).unwrap_or(String::new()); + return file_metadata + .map(|i| { + return i + .modified() + .map(|x| { + let datetime: DateTime = x.into(); + datetime.format("%b %d %H:%M %Y").to_string() + }) + .unwrap_or(String::new()); + }) + .unwrap_or(String::new()); } fn current_time() -> String { @@ -1064,7 +1099,13 @@ fn current_time() -> String { fn trailer_content(options: &OutputOptions) -> Vec { if options.as_ref().display_trailer { - vec!["".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string()] + vec![ + BLANK_STRING.to_string(), + BLANK_STRING.to_string(), + BLANK_STRING.to_string(), + BLANK_STRING.to_string(), + BLANK_STRING.to_string(), + ] } else { Vec::new() } @@ -1076,10 +1117,7 @@ fn trailer_content(options: &OutputOptions) -> Vec { /// # Arguments /// * `opts` - A reference to OutputOptions fn get_start_line_number(opts: &OutputOptions) -> usize { - opts.number - .as_ref() - .map(|i| i.first_number) - .unwrap_or(1) + opts.number.as_ref().map(|i| i.first_number).unwrap_or(1) } /// Returns number of lines to read from input for constructing one page of pr output. diff --git a/tests/test_pr.rs b/tests/test_pr.rs index 7b8c77d34..00d602b55 100644 --- a/tests/test_pr.rs +++ b/tests/test_pr.rs @@ -2,25 +2,29 @@ extern crate chrono; use common::util::*; use std::fs::metadata; -use test_pr::chrono::DateTime; use test_pr::chrono::offset::Local; +use test_pr::chrono::DateTime; fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String { let tmp_dir_path = ucmd.get_full_fixture_path(path); let file_metadata = metadata(tmp_dir_path); - return file_metadata.map(|i| { - return i.modified().map(|x| { - let datetime: DateTime = x.into(); - datetime.format("%b %d %H:%M %Y").to_string() - }).unwrap_or(String::new()); - }).unwrap_or(String::new()); + return file_metadata + .map(|i| { + return i + .modified() + .map(|x| { + let datetime: DateTime = x.into(); + datetime.format("%b %d %H:%M %Y").to_string() + }) + .unwrap_or(String::new()); + }) + .unwrap_or(String::new()); } fn now_time() -> String { Local::now().format("%b %d %H:%M %Y").to_string() } - #[test] fn test_without_any_options() { let test_file_path = "test_one_page.log"; @@ -30,7 +34,10 @@ fn test_without_any_options() { scenario .args(&[test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![(&"{last_modified_time}".to_string(), &value)]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -42,7 +49,10 @@ fn test_with_numbering_option() { scenario .args(&["-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![(&"{last_modified_time}".to_string(), &value)]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -54,7 +64,10 @@ fn test_with_numbering_option_when_content_is_less_than_page() { scenario .args(&["-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![(&"{last_modified_time}".to_string(), &value)]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -66,7 +79,10 @@ fn test_with_numbering_option_with_number_width() { scenario .args(&["-n", "2", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![(&"{last_modified_time}".to_string(), &value)]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -79,10 +95,13 @@ fn test_with_header_option() { scenario .args(&["-h", header, test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - (&"{header}".to_string(), &header.to_string()) - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![ + (&"{last_modified_time}".to_string(), &value), + (&"{header}".to_string(), &header.to_string()), + ], + ); } #[test] @@ -95,10 +114,13 @@ fn test_with_long_header_option() { scenario .args(&["--header=new file", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - (&"{header}".to_string(), &header.to_string()) - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![ + (&"{last_modified_time}".to_string(), &value), + (&"{header}".to_string(), &header.to_string()), + ], + ); } #[test] @@ -110,9 +132,10 @@ fn test_with_double_space_option() { scenario .args(&["-d", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -124,9 +147,10 @@ fn test_with_long_double_space_option() { scenario .args(&["--double-space", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -138,9 +162,10 @@ fn test_with_first_line_number_option() { scenario .args(&["-N", "5", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -152,9 +177,10 @@ fn test_with_first_line_number_long_option() { scenario .args(&["--first-line-number=5", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -166,9 +192,10 @@ fn test_with_number_option_with_custom_separator_char() { scenario .args(&["-nc", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -180,9 +207,10 @@ fn test_with_number_option_with_custom_separator_char_and_width() { scenario .args(&["-nc1", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -197,9 +225,7 @@ fn test_with_valid_page_ranges() { new_ucmd!() .args(&["--pages=1:5", test_file_path]) .succeeds(); - new_ucmd!() - .args(&["--pages=1", test_file_path]) - .succeeds(); + new_ucmd!().args(&["--pages=1", test_file_path]).succeeds(); new_ucmd!() .args(&["--pages=-1:5", test_file_path]) .fails() @@ -227,15 +253,17 @@ fn test_with_page_range() { scenario .args(&["--pages=15", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); new_ucmd!() .args(&["--pages=15:17", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path1, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path1, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -259,9 +287,10 @@ fn test_with_page_length_option() { scenario .args(&["--pages=2:3", "-l", "100", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); new_ucmd!() .args(&["--pages=2:3", "-l", "5", "-n", test_file_path]) @@ -288,9 +317,10 @@ fn test_with_stdin() { .pipe_in_fixture("stdin.log") .args(&["--pages=1:2", "-n", "-"]) .run() - .stdout_is_templated_fixture(expected_file_path, vec![ - (&"{last_modified_time}".to_string(), &now_time()), - ]); + .stdout_is_templated_fixture( + expected_file_path, + vec![(&"{last_modified_time}".to_string(), &now_time())], + ); } #[test] @@ -302,9 +332,10 @@ fn test_with_column() { scenario .args(&["--pages=3:5", "--column=3", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -316,9 +347,10 @@ fn test_with_column_across_option() { scenario .args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -328,11 +360,19 @@ fn test_with_column_across_option_and_column_separator() { let mut scenario = new_ucmd!(); let value = file_last_modified_time(&scenario, test_file_path); scenario - .args(&["--pages=3:5", "--column=3", "-s|", "-a", "-n", test_file_path]) + .args(&[ + "--pages=3:5", + "--column=3", + "-s|", + "-a", + "-n", + test_file_path, + ]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); } #[test] @@ -345,23 +385,35 @@ fn test_with_mpr() { new_ucmd!() .args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &now_time()), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &now_time())], + ); new_ucmd!() .args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path1, vec![ - (&"{last_modified_time}".to_string(), &now_time()), - ]); + .stdout_is_templated_fixture( + expected_test_file_path1, + vec![(&"{last_modified_time}".to_string(), &now_time())], + ); new_ucmd!() - .args(&["--pages=1:2", "-l", "100", "-n", "-m", test_file_path, test_file_path1, test_file_path]) + .args(&[ + "--pages=1:2", + "-l", + "100", + "-n", + "-m", + test_file_path, + test_file_path1, + test_file_path, + ]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path2, vec![ - (&"{last_modified_time}".to_string(), &now_time()), - ]); + .stdout_is_templated_fixture( + expected_test_file_path2, + vec![(&"{last_modified_time}".to_string(), &now_time())], + ); } #[test] @@ -380,7 +432,6 @@ fn test_with_mpr_and_column_options() { .stdout_is(""); } - #[test] fn test_with_offset_space_option() { let test_file_path = "column.log"; @@ -388,9 +439,18 @@ fn test_with_offset_space_option() { let mut scenario = new_ucmd!(); let value = file_last_modified_time(&scenario, test_file_path); scenario - .args(&["-o", "5", "--pages=3:5", "--column=3", "-a", "-n", test_file_path]) + .args(&[ + "-o", + "5", + "--pages=3:5", + "--column=3", + "-a", + "-n", + test_file_path, + ]) .succeeds() - .stdout_is_templated_fixture(expected_test_file_path, vec![ - (&"{last_modified_time}".to_string(), &value), - ]); + .stdout_is_templated_fixture( + expected_test_file_path, + vec![(&"{last_modified_time}".to_string(), &value)], + ); }