diff --git a/src/pr/pr.rs b/src/pr/pr.rs index 0fe3be3a7..06c991c29 100644 --- a/src/pr/pr.rs +++ b/src/pr/pr.rs @@ -15,12 +15,12 @@ extern crate chrono; extern crate getopts; extern crate uucore; -use std::io::{BufRead, BufReader, stdin, stdout, stderr, Error, Read, Write, Stdout, Lines}; +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::{Matches, Options}; -use std::fs::{metadata, File}; +use std::fs::{metadata, File, Metadata}; #[cfg(unix)] use std::os::unix::fs::FileTypeExt; use quick_error::ResultExt; @@ -29,6 +29,7 @@ use getopts::{HasArg, Occur}; use std::num::ParseIntError; use itertools::{Itertools, GroupBy}; use std::iter::{Enumerate, Map, TakeWhile, SkipWhile}; +use itertools::structs::KMergeBy; static NAME: &str = "pr"; static VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -51,6 +52,8 @@ static COLUMN_WIDTH_OPTION: &str = "w"; static ACROSS_OPTION: &str = "a"; static COLUMN_OPTION: &str = "column"; static COLUMN_SEPARATOR_OPTION: &str = "s"; +static MERGE_FILES_PRINT: &str = "m"; +static OFFSET_SPACES_OPTION: &str = "o"; static FILE_STDIN: &str = "-"; static READ_BUFFER_SIZE: usize = 1024 * 64; static DEFAULT_COLUMN_WIDTH: usize = 72; @@ -71,6 +74,22 @@ struct OutputOptions { content_lines_per_page: usize, page_separator_char: String, column_mode_options: Option, + merge_files_print: Option, + offset_spaces: usize +} + +struct FileLine { + file_id: usize, + line_number: usize, + page_number: usize, + key: usize, + line_content: Result, +} + +impl AsRef for FileLine { + fn as_ref(&self) -> &FileLine { + self + } } struct ColumnModeOptions { @@ -283,6 +302,27 @@ pub fn uumain(args: Vec) -> i32 { Occur::Optional, ); + opts.opt( + MERGE_FILES_PRINT, + "merge", + "Merge files. Standard output shall be formatted so the pr utility writes one line from each file specified by a + file operand, side by side into text columns of equal fixed widths, in terms of the number of column positions. + Implementations shall support merging of at least nine file operands.", + "", + HasArg::No, + Occur::Optional, + ); + + opts.opt( + OFFSET_SPACES_OPTION, + "indent", + "Each line of output shall be preceded by offset s. If the -o option is not specified, the default offset + shall be zero. The space taken is in addition to the output line width (see the -w option below).", + "offset", + HasArg::Yes, + Occur::Optional, + ); + opts.optflag("", "help", "display this help and exit"); opts.optflag("V", "version", "output version information and exit"); @@ -297,21 +337,20 @@ pub fn uumain(args: Vec) -> i32 { } let mut files: Vec = matches.free.clone(); - if files.is_empty() { - // -n value is optional if -n is given the opts gets confused - if matches.opt_present(NUMBERING_MODE_OPTION) { - let maybe_file = matches.opt_str(NUMBERING_MODE_OPTION).unwrap(); - let is_afile = is_a_file(&maybe_file); - if !is_afile { - print_error(&matches, PrError::NotExists(maybe_file)); - return 1; - } else { - files.push(maybe_file); - } - } else { - //For stdin - files.push(FILE_STDIN.to_owned()); + // -n value is optional if -n is given the opts gets confused + // if -n is used just before file path it might be captured as value of -n + if matches.opt_str(NUMBERING_MODE_OPTION).is_some() { + let maybe_a_file_path: String = matches.opt_str(NUMBERING_MODE_OPTION).unwrap(); + let is_file: bool = is_a_file(&maybe_a_file_path); + if !is_file && files.is_empty() { + print_error(&matches, PrError::NotExists(maybe_a_file_path)); + return 1; + } else if is_file { + files.insert(0, maybe_a_file_path); } + } else if files.is_empty() { + //For stdin + files.insert(0, FILE_STDIN.to_owned()); } @@ -319,14 +358,25 @@ pub fn uumain(args: Vec) -> i32 { return print_usage(&mut opts, &matches); } - for f in files { - let result_options = build_options(&matches, &f); + let file_groups: Vec> = if matches.opt_present(MERGE_FILES_PRINT) { + vec![files] + } else { + files.into_iter().map(|i| vec![i]).collect() + }; + + for file_group in file_groups { + let result_options: Result = build_options(&matches, &file_group); if result_options.is_err() { print_error(&matches, result_options.err().unwrap()); return 1; } - let options = &result_options.unwrap(); - let status: i32 = match pr(&f, options) { + let options: &OutputOptions = &result_options.unwrap(); + let cmd_result: Result = if file_group.len() == 1 { + pr(&file_group.get(0).unwrap(), options) + } else { + mpr(&file_group, options) + }; + let status: i32 = match cmd_result { Err(error) => { print_error(&matches, error); 1 @@ -385,22 +435,44 @@ fn print_usage(opts: &mut Options, matches: &Matches) -> i32 { return 0; } -fn build_options(matches: &Matches, path: &String) -> Result { - let header: String = matches.opt_str(STRING_HEADER_OPTION).unwrap_or(if path == FILE_STDIN { +fn build_options(matches: &Matches, paths: &Vec) -> Result { + 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(); + 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(); + return Err(PrError::EncounteredErrors(err_msg)); + } + + let merge_files_print: Option = if matches.opt_present(MERGE_FILES_PRINT) { + Some(paths.len()) + } else { + None + }; + + let header: String = matches.opt_str(STRING_HEADER_OPTION).unwrap_or(if is_merge_mode { "".to_string() } else { - path.to_string() + if paths[0].to_string() == FILE_STDIN { + "".to_string() + } else { + paths[0].to_string() + } }); - let default_first_number = NumberingMode::default().first_number; - let first_number = matches.opt_str(FIRST_LINE_NUMBER_OPTION).map(|n| { + let default_first_number: usize = NumberingMode::default().first_number; + let first_number: usize = matches.opt_str(FIRST_LINE_NUMBER_OPTION).map(|n| { n.parse::().unwrap_or(default_first_number) }).unwrap_or(default_first_number); let numbering_options: Option = matches.opt_str(NUMBERING_MODE_OPTION).map(|i| { - let parse_result = i.parse::(); + let parse_result: Result = i.parse::(); - let separator = if parse_result.is_err() { + let separator: String = if parse_result.is_err() { if is_a_file(&i) { NumberingMode::default().separator } else { @@ -410,7 +482,7 @@ fn build_options(matches: &Matches, path: &String) -> Result Result Result| { - let unparsed_value = matches.opt_str(PAGE_RANGE_OPTION).unwrap(); + 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 = match matches.opt_str(PAGE_RANGE_OPTION).map(|i| { + 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) @@ -465,9 +537,9 @@ fn build_options(matches: &Matches, path: &String) -> Result None }; - let end_page = match matches.opt_str(PAGE_RANGE_OPTION) - .filter(|i| i.contains(":")) - .map(|i| { + 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::() }) @@ -481,38 +553,38 @@ fn build_options(matches: &Matches, path: &String) -> Result() }) { Some(res) => res?, _ => LINES_PER_PAGE }; - let page_length_le_ht = page_length < (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE); + let page_length_le_ht: bool = page_length < (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE); - let display_header_and_trailer = !(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 = if page_length_le_ht { + let content_lines_per_page: usize = if page_length_le_ht { page_length } else { page_length - (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE) }; - let page_separator_char = matches.opt_str(FORM_FEED_OPTION).map(|_i| { + let page_separator_char: String = matches.opt_str(FORM_FEED_OPTION).map(|_i| { '\u{000A}'.to_string() }).unwrap_or("\n".to_string()); - let column_width = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::()) { + let column_width: Option = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::()) { Some(res) => Some(res?), _ => None }; - let across_mode = matches.opt_present(ACROSS_OPTION); + let across_mode: bool = matches.opt_present(ACROSS_OPTION); - let column_separator = 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 = match matches.opt_str(COLUMN_OPTION).map(|i| { + let column_mode_options: Option = match matches.opt_str(COLUMN_OPTION).map(|i| { i.parse::() }) { Some(res) => { @@ -528,6 +600,14 @@ fn build_options(matches: &Matches, path: &String) -> Result 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))?; Ok(OutputOptions { number: numbering_options, @@ -543,16 +623,18 @@ fn build_options(matches: &Matches, path: &String) -> Result Result, PrError> { if path == FILE_STDIN { - let stdin = stdin(); + let stdin: Stdin = stdin(); return Ok(Box::new(stdin) as Box); } - metadata(path).map(|i| { + metadata(path).map(|i: Metadata| { let path_string = path.to_string(); match i.file_type() { #[cfg(unix)] @@ -582,50 +664,217 @@ fn open(path: &str) -> Result, PrError> { }).unwrap_or(Err(PrError::NotExists(path.to_string()))) } -fn pr(path: &str, options: &OutputOptions) -> Result { +fn pr(path: &String, options: &OutputOptions) -> Result { let start_page: &usize = options.start_page.as_ref().unwrap_or(&1); + 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 start_line_number: usize = get_start_line_number(options); - - let pages: GroupBy>>>, _>, _>, _>, _> = - BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?) + let file_line_groups: GroupBy>>>, _>, _>, _>, _>, _> = + BufReader::with_capacity(READ_BUFFER_SIZE, open(path).unwrap()) .lines() .enumerate() - .skip_while(|line_index: &(usize, Result)| { - // Skip the initial lines if not in page range - let start_line_index_of_start_page = (*start_page - 1) * lines_needed_per_page; - line_index.0 < (start_line_index_of_start_page) + .map(move |i: (usize, Result)| { + FileLine { + file_id: 0, + line_number: i.0, + line_content: i.1, + page_number: 0, + key: 0, + } }) - .take_while(|i: &(usize, Result)| { + .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| i.0 < ((*lp) * lines_needed_per_page)) + .map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page)) .unwrap_or(true) }) - .map(|i: (usize, Result)| (i.0 + start_line_number, i.1)) // get display line number with line content - .group_by(|i: &(usize, Result)| { - ((i.0 - start_line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize - }); // group them by page number + .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 + }); - for (page_number, content_with_line_number) in pages.into_iter() { - let mut lines: Vec<(usize, String)> = Vec::new(); - for line_number_and_line in content_with_line_number { - let line_number: usize = line_number_and_line.0; - let line: Result = line_number_and_line.1; - let x = line?; - lines.push((line_number, x)); + 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())); + } + 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); } -fn print_page(lines: &Vec<(usize, String)>, options: &OutputOptions, page: &usize) -> Result { +fn mpr(paths: &Vec, options: &OutputOptions) -> Result { + let nfiles = paths.len(); + + 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 last_page: Option<&usize> = options.end_page.as_ref(); + + let file_line_groups: GroupBy>>>, _>, _>, _>, _>, _>, _> = paths + .into_iter() + .enumerate() + .map(|indexed_path: (usize, &String)| { + let start_line_number: usize = get_start_line_number(options); + 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, + } + }) + .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 + 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, + ..file_line + } + }) // get display line number with line content + }) + .kmerge_by(|a: &FileLine, b: &FileLine| { + if a.key == b.key { + a.line_number < b.line_number + } else { + a.key < b.key + } + }) + .group_by(|file_line: &FileLine| { + file_line.key + }); + + let mut lines: Vec = Vec::new(); + let start_page: &usize = options.start_page.as_ref().unwrap_or(&1); + 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())); + } + 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())); + } + lines = Vec::new(); + } + lines.push(file_line); + 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())); + } + + + return Ok(0); +} + +fn fill_missing_files(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; + 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 line_number = lines.get(i).map(|i: &FileLine| i.line_number).unwrap_or(1); + if lines_processed == 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; + } + + 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, + }); + line_number_counter += 1; + } else { + // Insert missing lines for a file_id + if line_number < line_number_counter { + 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; + } + } + } + + lines_processed += 1; + } +} + +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); @@ -653,7 +902,7 @@ fn print_page(lines: &Vec<(usize, String)>, options: &OutputOptions, page: &usiz Ok(lines_written) } -fn write_columns(lines: &Vec<(usize, String)>, 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 @@ -671,26 +920,35 @@ fn write_columns(lines: &Vec<(usize, String)>, options: &OutputOptions, out: &mu .unwrap_or(NumberingMode::default().separator); let blank_line = "".to_string(); - let columns = get_columns(options); - + 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() .map(|i| &i.column_separator) - .unwrap_or(&blank_line); + .unwrap_or(options + .merge_files_print + .map(|_k| &def_sep) + .unwrap_or(&blank_line) + ); let col_width: Option = options .column_mode_options.as_ref() .map(|i| i.width) - .unwrap_or(None); + .unwrap_or(options + .merge_files_print + .map(|_k| Some(DEFAULT_COLUMN_WIDTH)) + .unwrap_or(None) + ); let across_mode = options .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| @@ -707,19 +965,20 @@ fn write_columns(lines: &Vec<(usize, String)>, options: &OutputOptions, out: &mu ).collect() }; + let spaces = " ".repeat(*offset_spaces); + for fetch_index in fetch_indexes { let indexes = fetch_index.len(); for i in 0..indexes { - let index = fetch_index[i]; + let index: usize = fetch_index[i]; if lines.get(index).is_none() { break; } - let read_line: &String = &lines.get(index).unwrap().1; - let next_line_number: usize = lines.get(index).unwrap().0; - let trimmed_line = get_line_for_printing( - next_line_number, &width, - &number_separator, columns, col_width, - read_line, is_number_mode); + 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, + )); out.write(trimmed_line.as_bytes())?; if (i + 1) != indexes { out.write(col_sep.as_bytes())?; @@ -731,16 +990,20 @@ fn write_columns(lines: &Vec<(usize, String)>, options: &OutputOptions, out: &mu Ok(lines_printed) } -fn get_line_for_printing(line_number: usize, width: &usize, +fn get_line_for_printing(file_line: &FileLine, width: &usize, separator: &String, columns: usize, col_width: Option, - 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) + 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 = 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, read_line); + let mut complete_line = format!("{}{}", fmtd_line_number, file_line.line_content.as_ref().unwrap()); let tab_count: usize = complete_line .chars() diff --git a/tests/fixtures/pr/column_spaces_across.log.expected b/tests/fixtures/pr/column_spaces_across.log.expected new file mode 100644 index 000000000..037dd814b --- /dev/null +++ b/tests/fixtures/pr/column_spaces_across.log.expected @@ -0,0 +1,198 @@ + + +{last_modified_time} column.log Page 3 + + + 337 337 338 338 339 339 + 340 340 341 341 342 342 + 343 343 344 344 345 345 + 346 346 347 347 348 348 + 349 349 350 350 351 351 + 352 352 353 353 354 354 + 355 355 356 356 357 357 + 358 358 359 359 360 360 + 361 361 362 362 363 363 + 364 364 365 365 366 366 + 367 367 368 368 369 369 + 370 370 371 371 372 372 + 373 373 374 374 375 375 + 376 376 377 377 378 378 + 379 379 380 380 381 381 + 382 382 383 383 384 384 + 385 385 386 386 387 387 + 388 388 389 389 390 390 + 391 391 392 392 393 393 + 394 394 395 395 396 396 + 397 397 398 398 399 399 + 400 400 401 401 402 402 + 403 403 404 404 405 405 + 406 406 407 407 408 408 + 409 409 410 410 411 411 + 412 412 413 413 414 414 + 415 415 416 416 417 417 + 418 418 419 419 420 420 + 421 421 422 422 423 423 + 424 424 425 425 426 426 + 427 427 428 428 429 429 + 430 430 431 431 432 432 + 433 433 434 434 435 435 + 436 436 437 437 438 438 + 439 439 440 440 441 441 + 442 442 443 443 444 444 + 445 445 446 446 447 447 + 448 448 449 449 450 450 + 451 451 452 452 453 453 + 454 454 455 455 456 456 + 457 457 458 458 459 459 + 460 460 461 461 462 462 + 463 463 464 464 465 465 + 466 466 467 467 468 468 + 469 469 470 470 471 471 + 472 472 473 473 474 474 + 475 475 476 476 477 477 + 478 478 479 479 480 480 + 481 481 482 482 483 483 + 484 484 485 485 486 486 + 487 487 488 488 489 489 + 490 490 491 491 492 492 + 493 493 494 494 495 495 + 496 496 497 497 498 498 + 499 499 500 500 501 501 + 502 502 503 503 504 504 + + + + + + + +{last_modified_time} column.log Page 4 + + + 505 505 506 506 507 507 + 508 508 509 509 510 510 + 511 511 512 512 513 513 + 514 514 515 515 516 516 + 517 517 518 518 519 519 + 520 520 521 521 522 522 + 523 523 524 524 525 525 + 526 526 527 527 528 528 + 529 529 530 530 531 531 + 532 532 533 533 534 534 + 535 535 536 536 537 537 + 538 538 539 539 540 540 + 541 541 542 542 543 543 + 544 544 545 545 546 546 + 547 547 548 548 549 549 + 550 550 551 551 552 552 + 553 553 554 554 555 555 + 556 556 557 557 558 558 + 559 559 560 560 561 561 + 562 562 563 563 564 564 + 565 565 566 566 567 567 + 568 568 569 569 570 570 + 571 571 572 572 573 573 + 574 574 575 575 576 576 + 577 577 578 578 579 579 + 580 580 581 581 582 582 + 583 583 584 584 585 585 + 586 586 587 587 588 588 + 589 589 590 590 591 591 + 592 592 593 593 594 594 + 595 595 596 596 597 597 + 598 598 599 599 600 600 + 601 601 602 602 603 603 + 604 604 605 605 606 606 + 607 607 608 608 609 609 + 610 610 611 611 612 612 + 613 613 614 614 615 615 + 616 616 617 617 618 618 + 619 619 620 620 621 621 + 622 622 623 623 624 624 + 625 625 626 626 627 627 + 628 628 629 629 630 630 + 631 631 632 632 633 633 + 634 634 635 635 636 636 + 637 637 638 638 639 639 + 640 640 641 641 642 642 + 643 643 644 644 645 645 + 646 646 647 647 648 648 + 649 649 650 650 651 651 + 652 652 653 653 654 654 + 655 655 656 656 657 657 + 658 658 659 659 660 660 + 661 661 662 662 663 663 + 664 664 665 665 666 666 + 667 667 668 668 669 669 + 670 670 671 671 672 672 + + + + + + + +{last_modified_time} column.log Page 5 + + + 673 673 674 674 675 675 + 676 676 677 677 678 678 + 679 679 680 680 681 681 + 682 682 683 683 684 684 + 685 685 686 686 687 687 + 688 688 689 689 690 690 + 691 691 692 692 693 693 + 694 694 695 695 696 696 + 697 697 698 698 699 699 + 700 700 701 701 702 702 + 703 703 704 704 705 705 + 706 706 707 707 708 708 + 709 709 710 710 711 711 + 712 712 713 713 714 714 + 715 715 716 716 717 717 + 718 718 719 719 720 720 + 721 721 722 722 723 723 + 724 724 725 725 726 726 + 727 727 728 728 729 729 + 730 730 731 731 732 732 + 733 733 734 734 735 735 + 736 736 737 737 738 738 + 739 739 740 740 741 741 + 742 742 743 743 744 744 + 745 745 746 746 747 747 + 748 748 749 749 750 750 + 751 751 752 752 753 753 + 754 754 755 755 756 756 + 757 757 758 758 759 759 + 760 760 761 761 762 762 + 763 763 764 764 765 765 + 766 766 767 767 768 768 + 769 769 770 770 771 771 + 772 772 773 773 774 774 + 775 775 776 776 777 777 + 778 778 779 779 780 780 + 781 781 782 782 783 783 + 784 784 785 785 786 786 + 787 787 788 788 789 789 + 790 790 791 791 792 792 + 793 793 794 794 795 795 + 796 796 797 797 798 798 + 799 799 800 800 801 801 + 802 802 803 803 804 804 + 805 805 806 806 807 807 + 808 808 809 809 810 810 + 811 811 812 812 813 813 + 814 814 815 815 816 816 + 817 817 818 818 819 819 + 820 820 821 821 822 822 + 823 823 824 824 825 825 + 826 826 827 827 828 828 + 829 829 830 830 831 831 + 832 832 833 833 834 834 + 835 835 836 836 837 837 + 838 838 839 839 840 840 + + + + + diff --git a/tests/fixtures/pr/hosts.log b/tests/fixtures/pr/hosts.log new file mode 100644 index 000000000..8d725920c --- /dev/null +++ b/tests/fixtures/pr/hosts.log @@ -0,0 +1,11 @@ +## +# Host Database +# +# localhost is used to configure the loopback interface +# when the system is booting. Do not change this entry. +## +127.0.0.1 localhost +127.0.0.1 Techopss-MacBook-Pro.local +127.0.0.1 tilakpr +255.255.255.255 broadcasthost +::1 localhost \ No newline at end of file diff --git a/tests/fixtures/pr/mpr.log.expected b/tests/fixtures/pr/mpr.log.expected new file mode 100644 index 000000000..f6fffd191 --- /dev/null +++ b/tests/fixtures/pr/mpr.log.expected @@ -0,0 +1,132 @@ + + +{last_modified_time} Page 1 + + + 1 1 ## + 2 2 # Host Database + 3 3 # + 4 4 # localhost is used to configure th + 5 5 # when the system is booting. Do n + 6 6 ## + 7 7 127.0.0.1 localhost + 8 8 127.0.0.1 Techopss-MacBook-Pro.loca + 9 9 127.0.0.1 tilakpr + 10 10 255.255.255.255 broadcasthost + 11 11 ::1 localhost + 12 12 + 13 13 + 14 14 + 15 15 + 16 16 + 17 17 + 18 18 + 19 19 + 20 20 + 21 21 + 22 22 + 23 23 + 24 24 + 25 25 + 26 26 + 27 27 + 28 28 + 29 29 + 30 30 + 31 31 + 32 32 + 33 33 + 34 34 + 35 35 + 36 36 + 37 37 + 38 38 + 39 39 + 40 40 + 41 41 + 42 42 + 43 43 + 44 44 + 45 45 + 46 46 + 47 47 + 48 48 + 49 49 + 50 50 + 51 51 + 52 52 + 53 53 + 54 54 + 55 55 + 56 56 + + + + + + + +{last_modified_time} Page 2 + + + 57 57 + 58 58 + 59 59 + 60 60 + 61 61 + 62 62 + 63 63 + 64 64 + 65 65 + 66 66 + 67 67 + 68 68 + 69 69 + 70 70 + 71 71 + 72 72 + 73 73 + 74 74 + 75 75 + 76 76 + 77 77 + 78 78 + 79 79 + 80 80 + 81 81 + 82 82 + 83 83 + 84 84 + 85 85 + 86 86 + 87 87 + 88 88 + 89 89 + 90 90 + 91 91 + 92 92 + 93 93 + 94 94 + 95 95 + 96 96 + 97 97 + 98 98 + 99 99 + 100 100 + 101 101 + 102 102 + 103 103 + 104 104 + 105 105 + 106 106 + 107 107 + 108 108 + 109 109 + 110 110 + 111 111 + 112 112 + + + + + diff --git a/tests/fixtures/pr/mpr1.log.expected b/tests/fixtures/pr/mpr1.log.expected new file mode 100644 index 000000000..64d786d90 --- /dev/null +++ b/tests/fixtures/pr/mpr1.log.expected @@ -0,0 +1,198 @@ + + +{last_modified_time} Page 2 + + + 57 57 + 58 58 + 59 59 + 60 60 + 61 61 + 62 62 + 63 63 + 64 64 + 65 65 + 66 66 + 67 67 + 68 68 + 69 69 + 70 70 + 71 71 + 72 72 + 73 73 + 74 74 + 75 75 + 76 76 + 77 77 + 78 78 + 79 79 + 80 80 + 81 81 + 82 82 + 83 83 + 84 84 + 85 85 + 86 86 + 87 87 + 88 88 + 89 89 + 90 90 + 91 91 + 92 92 + 93 93 + 94 94 + 95 95 + 96 96 + 97 97 + 98 98 + 99 99 + 100 100 + 101 101 + 102 102 + 103 103 + 104 104 + 105 105 + 106 106 + 107 107 + 108 108 + 109 109 + 110 110 + 111 111 + 112 112 + + + + + + + +{last_modified_time} Page 3 + + + 113 113 + 114 114 + 115 115 + 116 116 + 117 117 + 118 118 + 119 119 + 120 120 + 121 121 + 122 122 + 123 123 + 124 124 + 125 125 + 126 126 + 127 127 + 128 128 + 129 129 + 130 130 + 131 131 + 132 132 + 133 133 + 134 134 + 135 135 + 136 136 + 137 137 + 138 138 + 139 139 + 140 140 + 141 141 + 142 142 + 143 143 + 144 144 + 145 145 + 146 146 + 147 147 + 148 148 + 149 149 + 150 150 + 151 151 + 152 152 + 153 153 + 154 154 + 155 155 + 156 156 + 157 157 + 158 158 + 159 159 + 160 160 + 161 161 + 162 162 + 163 163 + 164 164 + 165 165 + 166 166 + 167 167 + 168 168 + + + + + + + +{last_modified_time} Page 4 + + + 169 169 + 170 170 + 171 171 + 172 172 + 173 173 + 174 174 + 175 175 + 176 176 + 177 177 + 178 178 + 179 179 + 180 180 + 181 181 + 182 182 + 183 183 + 184 184 + 185 185 + 186 186 + 187 187 + 188 188 + 189 189 + 190 190 + 191 191 + 192 192 + 193 193 + 194 194 + 195 195 + 196 196 + 197 197 + 198 198 + 199 199 + 200 200 + 201 201 + 202 202 + 203 203 + 204 204 + 205 205 + 206 206 + 207 207 + 208 208 + 209 209 + 210 210 + 211 211 + 212 212 + 213 213 + 214 214 + 215 215 + 216 216 + 217 217 + 218 218 + 219 219 + 220 220 + 221 221 + 222 222 + 223 223 + 224 224 + + + + + diff --git a/tests/fixtures/pr/mpr2.log.expected b/tests/fixtures/pr/mpr2.log.expected new file mode 100644 index 000000000..091f0f228 --- /dev/null +++ b/tests/fixtures/pr/mpr2.log.expected @@ -0,0 +1,200 @@ + + +{last_modified_time} Page 1 + + + 1 1 ## 1 + 2 2 # Host Database 2 + 3 3 # 3 + 4 4 # localhost is used to 4 + 5 5 # when the system is bo 5 + 6 6 ## 6 + 7 7 127.0.0.1 localhost 7 + 8 8 127.0.0.1 Techopss-MacB 8 + 9 9 127.0.0.1 tilakpr 9 + 10 10 255.255.255.255 broadca 10 + 11 11 ::1 localho 11 + 12 12 12 + 13 13 13 + 14 14 14 + 15 15 15 + 16 16 16 + 17 17 17 + 18 18 18 + 19 19 19 + 20 20 20 + 21 21 21 + 22 22 22 + 23 23 23 + 24 24 24 + 25 25 25 + 26 26 26 + 27 27 27 + 28 28 28 + 29 29 29 + 30 30 30 + 31 31 31 + 32 32 32 + 33 33 33 + 34 34 34 + 35 35 35 + 36 36 36 + 37 37 37 + 38 38 38 + 39 39 39 + 40 40 40 + 41 41 41 + 42 42 42 + 43 43 43 + 44 44 44 + 45 45 45 + 46 46 46 + 47 47 47 + 48 48 48 + 49 49 49 + 50 50 50 + 51 51 51 + 52 52 52 + 53 53 53 + 54 54 54 + 55 55 55 + 56 56 56 + 57 57 57 + 58 58 58 + 59 59 59 + 60 60 60 + 61 61 61 + 62 62 62 + 63 63 63 + 64 64 64 + 65 65 65 + 66 66 66 + 67 67 67 + 68 68 68 + 69 69 69 + 70 70 70 + 71 71 71 + 72 72 72 + 73 73 73 + 74 74 74 + 75 75 75 + 76 76 76 + 77 77 77 + 78 78 78 + 79 79 79 + 80 80 80 + 81 81 81 + 82 82 82 + 83 83 83 + 84 84 84 + 85 85 85 + 86 86 86 + 87 87 87 + 88 88 88 + 89 89 89 + 90 90 90 + + + + + + + +{last_modified_time} Page 2 + + + 91 91 91 + 92 92 92 + 93 93 93 + 94 94 94 + 95 95 95 + 96 96 96 + 97 97 97 + 98 98 98 + 99 99 99 + 100 100 100 + 101 101 101 + 102 102 102 + 103 103 103 + 104 104 104 + 105 105 105 + 106 106 106 + 107 107 107 + 108 108 108 + 109 109 109 + 110 110 110 + 111 111 111 + 112 112 112 + 113 113 113 + 114 114 114 + 115 115 115 + 116 116 116 + 117 117 117 + 118 118 118 + 119 119 119 + 120 120 120 + 121 121 121 + 122 122 122 + 123 123 123 + 124 124 124 + 125 125 125 + 126 126 126 + 127 127 127 + 128 128 128 + 129 129 129 + 130 130 130 + 131 131 131 + 132 132 132 + 133 133 133 + 134 134 134 + 135 135 135 + 136 136 136 + 137 137 137 + 138 138 138 + 139 139 139 + 140 140 140 + 141 141 141 + 142 142 142 + 143 143 143 + 144 144 144 + 145 145 145 + 146 146 146 + 147 147 147 + 148 148 148 + 149 149 149 + 150 150 150 + 151 151 151 + 152 152 152 + 153 153 153 + 154 154 154 + 155 155 155 + 156 156 156 + 157 157 157 + 158 158 158 + 159 159 159 + 160 160 160 + 161 161 161 + 162 162 162 + 163 163 163 + 164 164 164 + 165 165 165 + 166 166 166 + 167 167 167 + 168 168 168 + 169 169 169 + 170 170 170 + 171 171 171 + 172 172 172 + 173 173 173 + 174 174 174 + 175 175 175 + 176 176 176 + 177 177 177 + 178 178 178 + 179 179 179 + 180 180 180 + + + + + diff --git a/tests/test_pr.rs b/tests/test_pr.rs index 6faa48348..7b8c77d34 100644 --- a/tests/test_pr.rs +++ b/tests/test_pr.rs @@ -282,7 +282,6 @@ fn test_with_suppress_error_option() { #[test] fn test_with_stdin() { - let test_file_path = "stdin.log"; let expected_file_path = "stdin.log.expected"; let mut scenario = new_ucmd!(); scenario @@ -306,7 +305,6 @@ fn test_with_column() { .stdout_is_templated_fixture(expected_test_file_path, vec![ (&"{last_modified_time}".to_string(), &value), ]); - } #[test] @@ -321,7 +319,6 @@ fn test_with_column_across_option() { .stdout_is_templated_fixture(expected_test_file_path, vec![ (&"{last_modified_time}".to_string(), &value), ]); - } #[test] @@ -336,6 +333,64 @@ fn test_with_column_across_option_and_column_separator() { .stdout_is_templated_fixture(expected_test_file_path, vec![ (&"{last_modified_time}".to_string(), &value), ]); - } +#[test] +fn test_with_mpr() { + let test_file_path = "column.log"; + let test_file_path1 = "hosts.log"; + let expected_test_file_path = "mpr.log.expected"; + let expected_test_file_path1 = "mpr1.log.expected"; + let expected_test_file_path2 = "mpr2.log.expected"; + 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()), + ]); + + 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()), + ]); + + new_ucmd!() + .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()), + ]); +} + +#[test] +fn test_with_mpr_and_column_options() { + let test_file_path = "column.log"; + new_ucmd!() + .args(&["--column=2", "-m", "-n", test_file_path]) + .fails() + .stderr_is("pr: cannot specify number of columns when printing in parallel") + .stdout_is(""); + + new_ucmd!() + .args(&["-a", "-m", "-n", test_file_path]) + .fails() + .stderr_is("pr: cannot specify both printing across and printing in parallel") + .stdout_is(""); +} + + +#[test] +fn test_with_offset_space_option() { + let test_file_path = "column.log"; + let expected_test_file_path = "column_spaces_across.log.expected"; + 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]) + .succeeds() + .stdout_is_templated_fixture(expected_test_file_path, vec![ + (&"{last_modified_time}".to_string(), &value), + ]); +}