1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 20:17:45 +00:00

pr: refactor and fmt fill_missing_lines and error checks

pr: Remove unwanted brancing in fill_missing_lines

pr: Remove unnecessary error check

pr: Rename key to group_key in FileLine

pr: Reformat test_pr with rustfmt
This commit is contained in:
Tilak Patidar 2018-12-29 14:10:43 +05:30 committed by Max Semenik
parent 5956894d00
commit 5c6c524334
2 changed files with 528 additions and 430 deletions

View file

@ -10,35 +10,36 @@
extern crate unix_socket; extern crate unix_socket;
#[macro_use] #[macro_use]
extern crate quick_error; extern crate quick_error;
extern crate itertools;
extern crate chrono; extern crate chrono;
extern crate getopts; extern crate getopts;
extern crate itertools;
extern crate uucore; 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::offset::Local;
use chrono::DateTime; use chrono::DateTime;
use getopts::{HasArg, Occur};
use getopts::{Matches, Options}; use getopts::{Matches, Options};
use std::fs::{metadata, File, Metadata}; use itertools::structs::KMergeBy;
#[cfg(unix)] use itertools::{GroupBy, Itertools};
use std::os::unix::fs::FileTypeExt;
use quick_error::ResultExt; use quick_error::ResultExt;
use std::convert::From; 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 std::num::ParseIntError;
use itertools::{Itertools, GroupBy}; #[cfg(unix)]
use std::iter::{Enumerate, Map, TakeWhile, SkipWhile}; use std::os::unix::fs::FileTypeExt;
use itertools::structs::KMergeBy; use std::vec::Vec;
type IOError = std::io::Error;
static NAME: &str = "pr"; static NAME: &str = "pr";
static VERSION: &str = env!("CARGO_PKG_VERSION"); static VERSION: &str = env!("CARGO_PKG_VERSION");
static TAB: char = '\t'; static TAB: char = '\t';
static NEW_LINE: &str = "\n";
static LINES_PER_PAGE: usize = 66; static LINES_PER_PAGE: usize = 66;
static HEADER_LINES_PER_PAGE: usize = 5; static HEADER_LINES_PER_PAGE: usize = 5;
static TRAILER_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 STRING_HEADER_OPTION: &str = "h";
static DOUBLE_SPACE_OPTION: &str = "d"; static DOUBLE_SPACE_OPTION: &str = "d";
static NUMBERING_MODE_OPTION: &str = "n"; static NUMBERING_MODE_OPTION: &str = "n";
@ -57,7 +58,8 @@ static OFFSET_SPACES_OPTION: &str = "o";
static FILE_STDIN: &str = "-"; static FILE_STDIN: &str = "-";
static READ_BUFFER_SIZE: usize = 1024 * 64; static READ_BUFFER_SIZE: usize = 1024 * 64;
static DEFAULT_COLUMN_WIDTH: usize = 72; static DEFAULT_COLUMN_WIDTH: usize = 72;
static DEFAULT_COLUMN_SEPARATOR: &str = "\t"; static DEFAULT_COLUMN_SEPARATOR: &char = &TAB;
static BLANK_STRING: &str = "";
struct OutputOptions { struct OutputOptions {
/// Line numbering mode /// Line numbering mode
@ -67,7 +69,7 @@ struct OutputOptions {
line_separator: String, line_separator: String,
content_line_separator: String, content_line_separator: String,
last_modified_time: String, last_modified_time: String,
start_page: Option<usize>, start_page: usize,
end_page: Option<usize>, end_page: Option<usize>,
display_header: bool, display_header: bool,
display_trailer: bool, display_trailer: bool,
@ -75,15 +77,15 @@ struct OutputOptions {
page_separator_char: String, page_separator_char: String,
column_mode_options: Option<ColumnModeOptions>, column_mode_options: Option<ColumnModeOptions>,
merge_files_print: Option<usize>, merge_files_print: Option<usize>,
offset_spaces: usize offset_spaces: usize,
} }
struct FileLine { struct FileLine {
file_id: usize, file_id: usize,
line_number: usize, line_number: usize,
page_number: usize, page_number: usize,
key: usize, group_key: usize,
line_content: Result<String, Error>, line_content: Result<String, IOError>,
} }
impl AsRef<FileLine> for FileLine { impl AsRef<FileLine> for FileLine {
@ -93,7 +95,7 @@ impl AsRef<FileLine> for FileLine {
} }
struct ColumnModeOptions { struct ColumnModeOptions {
width: Option<usize>, width: usize,
columns: usize, columns: usize,
column_separator: String, column_separator: String,
across_mode: bool, across_mode: bool,
@ -115,21 +117,15 @@ struct NumberingMode {
impl Default for NumberingMode { impl Default for NumberingMode {
fn default() -> NumberingMode { fn default() -> NumberingMode {
NumberingMode { NumberingMode {
width: NUMBERING_MODE_DEFAULT_WIDTH, width: 5,
separator: NUMBERING_MODE_DEFAULT_SEPARATOR.to_string(), separator: TAB.to_string(),
first_number: 1, first_number: 1,
} }
} }
} }
impl From<Error> for PrError { impl From<IOError> for PrError {
fn from(err: Error) -> Self { fn from(err: IOError) -> Self {
PrError::EncounteredErrors(err.to_string())
}
}
impl From<std::num::ParseIntError> for PrError {
fn from(err: std::num::ParseIntError) -> Self {
PrError::EncounteredErrors(err.to_string()) PrError::EncounteredErrors(err.to_string())
} }
} }
@ -137,8 +133,8 @@ impl From<std::num::ParseIntError> for PrError {
quick_error! { quick_error! {
#[derive(Debug)] #[derive(Debug)]
enum PrError { enum PrError {
Input(err: Error, path: String) { Input(err: IOError, path: String) {
context(path: &'a str, err: Error) -> (err, path.to_owned()) context(path: &'a str, err: IOError) -> (err, path.to_owned())
display("pr: Reading from input {0} gave error", path) display("pr: Reading from input {0} gave error", path)
cause(err) cause(err)
} }
@ -353,7 +349,6 @@ pub fn uumain(args: Vec<String>) -> i32 {
files.insert(0, FILE_STDIN.to_owned()); files.insert(0, FILE_STDIN.to_owned());
} }
if matches.opt_present("help") { if matches.opt_present("help") {
return print_usage(&mut opts, &matches); return print_usage(&mut opts, &matches);
} }
@ -381,7 +376,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
print_error(&matches, error); print_error(&matches, error);
1 1
} }
_ => 0 _ => 0,
}; };
if status != 0 { if status != 0 {
return status; return status;
@ -403,10 +398,13 @@ fn print_error(matches: &Matches, err: PrError) {
fn print_usage(opts: &mut Options, matches: &Matches) -> i32 { fn print_usage(opts: &mut Options, matches: &Matches) -> i32 {
println!("{} {} -- print files", NAME, VERSION); println!("{} {} -- print files", NAME, VERSION);
println!(); 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 locale] [-h header] [[-i] [char] [gap]]
[-l lines] [-o offset] [[-s] [char]] [[-n] [char] [-l lines] [-o offset] [[-s] [char]] [[-n] [char]
[width]] [-w width] [-] [file ...].", NAME); [width]] [-w width] [-] [file ...].",
NAME
);
println!(); println!();
let usage: &str = "The pr utility is a printing and pagination filter let usage: &str = "The pr utility is a printing and pagination filter
for text files. When multiple input files are spec- for text files. When multiple input files are spec-
@ -435,16 +433,39 @@ fn print_usage(opts: &mut Options, matches: &Matches) -> i32 {
return 0; return 0;
} }
fn parse_usize(matches: &Matches, opt: &str) -> Option<Result<usize, PrError>> {
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::<usize>().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<String>) -> Result<OutputOptions, PrError> { fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions, PrError> {
let invalid_pages_map = |i: String| {
let unparsed_value: String = matches.opt_str(PAGE_RANGE_OPTION).unwrap();
i.parse::<usize>().map_err(|_e| {
PrError::EncounteredErrors(format!("invalid --pages argument '{}'", unparsed_value))
})
};
let is_merge_mode: bool = matches.opt_present(MERGE_FILES_PRINT); let is_merge_mode: bool = matches.opt_present(MERGE_FILES_PRINT);
if is_merge_mode && matches.opt_present(COLUMN_OPTION) { 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)); return Err(PrError::EncounteredErrors(err_msg));
} }
if is_merge_mode && matches.opt_present(ACROSS_OPTION) { 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)); return Err(PrError::EncounteredErrors(err_msg));
} }
@ -454,7 +475,9 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
None None
}; };
let header: String = matches.opt_str(STRING_HEADER_OPTION).unwrap_or(if is_merge_mode { let header: String = matches
.opt_str(STRING_HEADER_OPTION)
.unwrap_or(if is_merge_mode {
"".to_string() "".to_string()
} else { } else {
if paths[0].to_string() == FILE_STDIN { if paths[0].to_string() == FILE_STDIN {
@ -465,11 +488,12 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
}); });
let default_first_number: usize = NumberingMode::default().first_number; let default_first_number: usize = NumberingMode::default().first_number;
let first_number: usize = matches.opt_str(FIRST_LINE_NUMBER_OPTION).map(|n| { let first_number: usize =
n.parse::<usize>().unwrap_or(default_first_number) parse_usize(matches, FIRST_LINE_NUMBER_OPTION).unwrap_or(Ok(default_first_number))?;
}).unwrap_or(default_first_number);
let numbering_options: Option<NumberingMode> = matches.opt_str(NUMBERING_MODE_OPTION).map(|i| { let numbering_options: Option<NumberingMode> = matches
.opt_str(NUMBERING_MODE_OPTION)
.map(|i| {
let parse_result: Result<usize, ParseIntError> = i.parse::<usize>(); let parse_result: Result<usize, ParseIntError> = i.parse::<usize>();
let separator: String = if parse_result.is_err() { let separator: String = if parse_result.is_err() {
@ -486,7 +510,9 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
if is_a_file(&i) { if is_a_file(&i) {
NumberingMode::default().width NumberingMode::default().width
} else { } else {
i[1..].parse::<usize>().unwrap_or(NumberingMode::default().width) i[1..]
.parse::<usize>()
.unwrap_or(NumberingMode::default().width)
} }
} else { } else {
parse_result.unwrap() parse_result.unwrap()
@ -497,7 +523,8 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
separator, separator,
first_number, first_number,
} }
}).or_else(|| { })
.or_else(|| {
if matches.opt_present(NUMBERING_MODE_OPTION) { if matches.opt_present(NUMBERING_MODE_OPTION) {
return Some(NumberingMode::default()); return Some(NumberingMode::default());
} }
@ -507,12 +534,12 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
let double_space: bool = matches.opt_present(DOUBLE_SPACE_OPTION); let double_space: bool = matches.opt_present(DOUBLE_SPACE_OPTION);
let content_line_separator: String = if double_space { let content_line_separator: String = if double_space {
"\n\n".to_string() NEW_LINE.repeat(2)
} else { } 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) { let last_modified_time: String = if is_merge_mode || paths[0].eq(FILE_STDIN) {
current_time() current_time()
@ -520,48 +547,46 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
file_last_modified_time(paths.get(0).unwrap()) file_last_modified_time(paths.get(0).unwrap())
}; };
let invalid_pages_map = |i: Result<usize, ParseIntError>| { let start_page: usize = match matches
let unparsed_value: String = matches.opt_str(PAGE_RANGE_OPTION).unwrap(); .opt_str(PAGE_RANGE_OPTION)
match i { .map(|i| {
Ok(val) => Ok(val),
Err(_e) => Err(PrError::EncounteredErrors(format!("invalid --pages argument '{}'", unparsed_value)))
}
};
let start_page: Option<usize> = match matches.opt_str(PAGE_RANGE_OPTION).map(|i| {
let x: Vec<&str> = i.split(":").collect(); let x: Vec<&str> = i.split(":").collect();
x[0].parse::<usize>() x[0].to_string()
}).map(invalid_pages_map) })
.map(invalid_pages_map)
{ {
Some(res) => Some(res?), Some(res) => res?,
_ => None _ => 1,
}; };
let end_page: Option<usize> = match matches.opt_str(PAGE_RANGE_OPTION) let end_page: Option<usize> = match matches
.opt_str(PAGE_RANGE_OPTION)
.filter(|i: &String| i.contains(":")) .filter(|i: &String| i.contains(":"))
.map(|i: String| { .map(|i: String| {
let x: Vec<&str> = i.split(":").collect(); let x: Vec<&str> = i.split(":").collect();
x[1].parse::<usize>() x[1].to_string()
}) })
.map(invalid_pages_map) .map(invalid_pages_map)
{ {
Some(res) => Some(res?), Some(res) => Some(res?),
_ => None _ => None,
}; };
if start_page.is_some() && end_page.is_some() && 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.unwrap(), 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| { let page_length: usize =
i.parse::<usize>() parse_usize(matches, PAGE_LENGTH_OPTION).unwrap_or(Ok(LINES_PER_PAGE))?;
}) {
Some(res) => res?,
_ => LINES_PER_PAGE
};
let page_length_le_ht: bool = 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: 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 { let content_lines_per_page: usize = if page_length_le_ht {
page_length page_length
@ -569,45 +594,31 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
page_length - (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE) page_length - (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE)
}; };
let page_separator_char: String = matches.opt_str(FORM_FEED_OPTION).map(|_i| { let page_separator_char: String = matches
'\u{000A}'.to_string() .opt_str(FORM_FEED_OPTION)
}).unwrap_or("\n".to_string()); .map(|_i| '\u{000A}'.to_string())
.unwrap_or(NEW_LINE.to_string());
let column_width: Option<usize> = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::<usize>()) { let column_width: usize =
Some(res) => Some(res?), parse_usize(matches, COLUMN_WIDTH_OPTION).unwrap_or(Ok(DEFAULT_COLUMN_WIDTH))?;
_ => None
};
let across_mode: bool = matches.opt_present(ACROSS_OPTION); 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()); .unwrap_or(DEFAULT_COLUMN_SEPARATOR.to_string());
let column_mode_options: Option<ColumnModeOptions> = match parse_usize(matches, COLUMN_OPTION) {
let column_mode_options: Option<ColumnModeOptions> = match matches.opt_str(COLUMN_OPTION).map(|i| { Some(res) => Some(ColumnModeOptions {
i.parse::<usize>()
}) {
Some(res) => {
Some(ColumnModeOptions {
columns: res?, columns: res?,
width: match column_width { width: column_width,
Some(x) => Some(x),
None => Some(DEFAULT_COLUMN_WIDTH)
},
column_separator, column_separator,
across_mode, across_mode,
}) }),
} _ => None,
_ => None
}; };
let offset_spaces: usize = matches.opt_str(OFFSET_SPACES_OPTION) let offset_spaces: usize = parse_usize(matches, OFFSET_SPACES_OPTION).unwrap_or(Ok(0))?;
.map(|i| {
match i.parse::<usize>() {
Ok(val)=> Ok(val),
Err(e)=> Err(PrError::EncounteredErrors("".to_string()))
}
}).unwrap_or(Ok(0))?;
Ok(OutputOptions { Ok(OutputOptions {
number: numbering_options, number: numbering_options,
@ -634,93 +645,80 @@ fn open(path: &str) -> Result<Box<Read>, PrError> {
return Ok(Box::new(stdin) as Box<Read>); return Ok(Box::new(stdin) as Box<Read>);
} }
metadata(path).map(|i: Metadata| { metadata(path)
.map(|i: Metadata| {
let path_string = path.to_string(); let path_string = path.to_string();
match i.file_type() { match i.file_type() {
#[cfg(unix)] #[cfg(unix)]
ft if ft.is_block_device() => ft if ft.is_block_device() => Err(PrError::UnknownFiletype(path_string)),
{
Err(PrError::UnknownFiletype(path_string))
}
#[cfg(unix)] #[cfg(unix)]
ft if ft.is_char_device() => ft if ft.is_char_device() => Err(PrError::UnknownFiletype(path_string)),
{
Err(PrError::UnknownFiletype(path_string))
}
#[cfg(unix)] #[cfg(unix)]
ft if ft.is_fifo() => ft if ft.is_fifo() => Err(PrError::UnknownFiletype(path_string)),
{
Err(PrError::UnknownFiletype(path_string))
}
#[cfg(unix)] #[cfg(unix)]
ft if ft.is_socket() => ft if ft.is_socket() => Err(PrError::IsSocket(path_string)),
{
Err(PrError::IsSocket(path_string))
}
ft if ft.is_dir() => Err(PrError::IsDirectory(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<Read>), ft if ft.is_file() || ft.is_symlink() => {
_ => Err(PrError::UnknownFiletype(path_string)) Ok(Box::new(File::open(path).context(path)?) as Box<Read>)
} }
}).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<i32, PrError> { fn pr(path: &String, options: &OutputOptions) -> Result<i32, PrError> {
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 start_line_number: usize = get_start_line_number(options);
let last_page: Option<&usize> = options.end_page.as_ref(); let last_page: Option<&usize> = options.end_page.as_ref();
let lines_needed_per_page: usize = lines_to_read_for_page(options); let lines_needed_per_page: usize = lines_to_read_for_page(options);
let file_line_groups: GroupBy<usize, Map<TakeWhile<SkipWhile<Map<Enumerate<Lines<BufReader<Box<Read>>>>, _>, _>, _>, _>, _> = let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page;
BufReader::with_capacity(READ_BUFFER_SIZE, open(path).unwrap()) let file_line_groups: GroupBy<
usize,
Map<TakeWhile<SkipWhile<Map<Enumerate<Lines<BufReader<Box<Read>>>>, _>, _>, _>, _>,
_,
> = BufReader::with_capacity(READ_BUFFER_SIZE, open(path).unwrap())
.lines() .lines()
.enumerate() .enumerate()
.map(move |i: (usize, Result<String, Error>)| { .map(|i: (usize, Result<String, IOError>)| FileLine {
FileLine {
file_id: 0, file_id: 0,
line_number: i.0, line_number: i.0,
line_content: i.1, line_content: i.1,
page_number: 0, page_number: 0,
key: 0, group_key: 0,
}
}) })
.skip_while(move |file_line: &FileLine| { .skip_while(|file_line: &FileLine| {
// Skip the initial lines if not in page range // 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) file_line.line_number < (start_line_index_of_start_page)
}) })
.take_while(move |file_line: &FileLine| { .take_while(|file_line: &FileLine| {
// Only read the file until provided last page reached // Only read the file until provided last page reached
last_page last_page
.map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page)) .map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page))
.unwrap_or(true) .unwrap_or(true)
}) })
.map(move |file_line: FileLine| { .map(|file_line: FileLine| {
let page_number = ((file_line.line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize; let page_number =
((file_line.line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize;
FileLine { FileLine {
line_number: file_line.line_number + start_line_number, line_number: file_line.line_number + start_line_number,
page_number, page_number,
key: page_number, group_key: page_number,
..file_line ..file_line
} }
}) // get display line number with line content }) // get display line number with line content
.group_by(|file_line: &FileLine| { .group_by(|file_line: &FileLine| file_line.group_key);
file_line.page_number
});
for (page_number, file_line_group) in file_line_groups.into_iter() { for (page_number, file_line_group) in file_line_groups.into_iter() {
let mut lines: Vec<FileLine> = Vec::new(); let mut lines: Vec<FileLine> = Vec::new();
for file_line in file_line_group { for file_line in file_line_group {
if file_line.line_content.is_err() { 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); lines.push(file_line);
} }
let print_status: Result<usize, Error> = print_page(&lines, options, &page_number); print_page(&lines, options, &page_number)?;
if print_status.is_err() {
return Err(PrError::from(print_status.unwrap_err()));
} }
}
return Ok(0); return Ok(0);
} }
@ -730,10 +728,18 @@ fn mpr(paths: &Vec<String>, options: &OutputOptions) -> Result<i32, PrError> {
let lines_needed_per_page: usize = lines_to_read_for_page(options); 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 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 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<usize, KMergeBy<Map<TakeWhile<SkipWhile<Map<Enumerate<Lines<BufReader<Box<Read>>>>, _>, _>, _>, _>, _>, _> = paths let file_line_groups: GroupBy<
usize,
KMergeBy<
Map<TakeWhile<SkipWhile<Map<Enumerate<Lines<BufReader<Box<Read>>>>, _>, _>, _>, _>,
_,
>,
_,
> = paths
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|indexed_path: (usize, &String)| { .map(|indexed_path: (usize, &String)| {
@ -741,63 +747,56 @@ fn mpr(paths: &Vec<String>, options: &OutputOptions) -> Result<i32, PrError> {
BufReader::with_capacity(READ_BUFFER_SIZE, open(indexed_path.1).unwrap()) BufReader::with_capacity(READ_BUFFER_SIZE, open(indexed_path.1).unwrap())
.lines() .lines()
.enumerate() .enumerate()
.map(move |i: (usize, Result<String, Error>)| { .map(move |i: (usize, Result<String, IOError>)| FileLine {
FileLine {
file_id: indexed_path.0, file_id: indexed_path.0,
line_number: i.0, line_number: i.0,
line_content: i.1, line_content: i.1,
page_number: 0, page_number: 0,
key: 0, group_key: 0,
}
}) })
.skip_while(move |file_line: &FileLine| { .skip_while(move |file_line: &FileLine| {
// Skip the initial lines if not in page range // 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) file_line.line_number < (start_line_index_of_start_page)
}) })
.take_while(move |file_line: &FileLine| { .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 last_page
.map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page)) .map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page))
.unwrap_or(true) .unwrap_or(true)
}) })
.map(move |file_line: FileLine| { .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 { FileLine {
line_number: file_line.line_number + start_line_number, line_number: file_line.line_number + start_line_number,
page_number, page_number,
key: page_number * nfiles + file_line.file_id, group_key: page_number * nfiles + file_line.file_id,
..file_line ..file_line
} }
}) // get display line number with line content }) // get display line number with line content
}) })
.kmerge_by(|a: &FileLine, b: &FileLine| { .kmerge_by(|a: &FileLine, b: &FileLine| {
if a.key == b.key { if a.group_key == b.group_key {
a.line_number < b.line_number a.line_number < b.line_number
} else { } else {
a.key < b.key a.group_key < b.group_key
} }
}) })
.group_by(|file_line: &FileLine| { .group_by(|file_line: &FileLine| file_line.group_key);
file_line.key
});
let mut lines: Vec<FileLine> = Vec::new(); let mut lines: Vec<FileLine> = 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; let mut page_counter: usize = *start_page;
for (_key, file_line_group) in file_line_groups.into_iter() { for (_key, file_line_group) in file_line_groups.into_iter() {
for file_line in file_line_group { for file_line in file_line_group {
if file_line.line_content.is_err() { 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; let new_page_number = file_line.page_number;
if page_counter != new_page_number { if page_counter != new_page_number {
fill_missing_files(&mut lines, lines_needed_per_page, &nfiles, page_counter); fill_missing_lines(&mut lines, lines_needed_per_page, &nfiles, page_counter);
let print_status: Result<usize, Error> = print_page(&lines, options, &page_counter); print_page(&lines, options, &page_counter)?;
if print_status.is_err() {
return Err(PrError::from(print_status.unwrap_err()));
}
lines = Vec::new(); lines = Vec::new();
} }
lines.push(file_line); lines.push(file_line);
@ -805,76 +804,73 @@ fn mpr(paths: &Vec<String>, options: &OutputOptions) -> Result<i32, PrError> {
} }
} }
fill_missing_files(&mut lines, lines_needed_per_page, &nfiles, page_counter); fill_missing_lines(&mut lines, lines_needed_per_page, &nfiles, page_counter);
let print_status: Result<usize, Error> = print_page(&lines, options, &page_counter); print_page(&lines, options, &page_counter)?;
if print_status.is_err() {
return Err(PrError::from(print_status.unwrap_err()));
}
return Ok(0); return Ok(0);
} }
fn fill_missing_files(lines: &mut Vec<FileLine>, lines_per_file: usize, nfiles: &usize, page_number: usize) { fn fill_missing_lines(
lines: &mut Vec<FileLine>,
lines_per_file: usize,
nfiles: &usize,
page_number: usize,
) {
let init_line_number = (page_number - 1) * lines_per_file + 1; 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 file_id_counter: usize = 0;
let mut line_number_counter: usize = init_line_number; let mut line_number_counter: usize = init_line_number;
let mut lines_processed: usize = 0; let mut lines_processed_per_file: usize = 0;
let mut file_id_missing: bool = false;
for mut i in 0..lines_per_file * nfiles { 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); 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; line_number_counter = init_line_number;
file_id_counter += 1; file_id_counter += 1;
lines_processed = 0; lines_processed_per_file = 0;
file_id_missing = false;
}
if file_id_counter >= *nfiles {
file_id_counter = 0;
file_id_missing = false;
} }
if file_id != file_id_counter { if file_id != file_id_counter {
file_id_missing = true;
}
if file_id_missing {
// Insert missing file_ids // Insert missing file_ids
lines.insert(i, FileLine { lines.insert(
i,
FileLine {
file_id: file_id_counter, file_id: file_id_counter,
line_number: line_number_counter, line_number: line_number_counter,
line_content: Ok("".to_string()), line_content: Ok("".to_string()),
page_number, page_number,
key: 0, group_key: 0,
}); },
);
line_number_counter += 1; line_number_counter += 1;
} else { } else if line_number < line_number_counter {
// Insert missing lines for a file_id // Insert missing lines for a file_id
if line_number < line_number_counter {
line_number_counter += 1; line_number_counter += 1;
lines.insert(i, FileLine { lines.insert(
i,
FileLine {
file_id, file_id,
line_number: line_number_counter, line_number: line_number_counter,
line_content: Ok("".to_string()), line_content: Ok("".to_string()),
page_number, page_number,
key: 0, group_key: 0,
}); },
);
} else { } else {
line_number_counter = line_number; line_number_counter = line_number;
if line_number_counter == final_line_number {
line_number_counter = init_line_number;
}
}
} }
lines_processed += 1; lines_processed_per_file += 1;
} }
} }
fn print_page(lines: &Vec<FileLine>, options: &OutputOptions, page: &usize) -> Result<usize, Error> { fn print_page(
lines: &Vec<FileLine>,
options: &OutputOptions,
page: &usize,
) -> Result<usize, IOError> {
let page_separator = options.page_separator_char.as_bytes(); let page_separator = options.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);
@ -902,7 +898,11 @@ fn print_page(lines: &Vec<FileLine>, options: &OutputOptions, page: &usize) -> R
Ok(lines_written) Ok(lines_written)
} }
fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdout) -> Result<usize, Error> { fn write_columns(
lines: &Vec<FileLine>,
options: &OutputOptions,
out: &mut Stdout,
) -> Result<usize, IOError> {
let line_separator = options.content_line_separator.as_bytes(); let line_separator = options.content_line_separator.as_bytes();
let content_lines_per_page = if options.double_space { let content_lines_per_page = if options.double_space {
options.content_lines_per_page / 2 options.content_lines_per_page / 2
@ -910,12 +910,10 @@ fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdou
options.content_lines_per_page options.content_lines_per_page
}; };
let width: usize = options let width: usize = options.number.as_ref().map(|i| i.width).unwrap_or(0);
.number.as_ref()
.map(|i| i.width)
.unwrap_or(0);
let number_separator: String = options let number_separator: String = options
.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);
@ -923,25 +921,31 @@ fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdou
let columns = options.merge_files_print.unwrap_or(get_columns(options)); let columns = options.merge_files_print.unwrap_or(get_columns(options));
let def_sep = DEFAULT_COLUMN_SEPARATOR.to_string(); let def_sep = DEFAULT_COLUMN_SEPARATOR.to_string();
let col_sep: &String = options let col_sep: &String = options
.column_mode_options.as_ref() .column_mode_options
.as_ref()
.map(|i| &i.column_separator) .map(|i| &i.column_separator)
.unwrap_or(options .unwrap_or(
options
.merge_files_print .merge_files_print
.map(|_k| &def_sep) .map(|_k| &def_sep)
.unwrap_or(&blank_line) .unwrap_or(&blank_line),
); );
// TODO simplify
let col_width: Option<usize> = options let col_width: Option<usize> = options
.column_mode_options.as_ref() .column_mode_options
.map(|i| i.width) .as_ref()
.unwrap_or(options .map(|i| Some(i.width))
.unwrap_or(
options
.merge_files_print .merge_files_print
.map(|_k| Some(DEFAULT_COLUMN_WIDTH)) .map(|_k| Some(DEFAULT_COLUMN_WIDTH))
.unwrap_or(None) .unwrap_or(None),
); );
let across_mode = options let across_mode = options
.column_mode_options.as_ref() .column_mode_options
.as_ref()
.map(|i| i.across_mode) .map(|i| i.across_mode)
.unwrap_or(false); .unwrap_or(false);
@ -951,18 +955,16 @@ fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdou
let is_number_mode = options.number.is_some(); let is_number_mode = options.number.is_some();
let fetch_indexes: Vec<Vec<usize>> = if across_mode { let fetch_indexes: Vec<Vec<usize>> = if across_mode {
(0..content_lines_per_page) (0..content_lines_per_page)
.map(|a| .map(|a| (0..columns).map(|i| a * columns + i).collect())
(0..columns)
.map(|i| a * columns + i)
.collect() .collect()
).collect()
} else { } else {
(0..content_lines_per_page) (0..content_lines_per_page)
.map(|start| .map(|start| {
(0..columns) (0..columns)
.map(|i| start + content_lines_per_page * i) .map(|i| start + content_lines_per_page * i)
.collect() .collect()
).collect() })
.collect()
}; };
let spaces = " ".repeat(*offset_spaces); let spaces = " ".repeat(*offset_spaces);
@ -975,10 +977,20 @@ fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdou
break; break;
} }
let file_line: &FileLine = lines.get(index).unwrap(); let file_line: &FileLine = lines.get(index).unwrap();
let trimmed_line: String = format!("{}{}", spaces, get_line_for_printing( let trimmed_line: String = format!(
file_line, &width, &number_separator, columns, col_width, "{}{}",
is_number_mode, &options.merge_files_print, &i, 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())?; out.write(trimmed_line.as_bytes())?;
if (i + 1) != indexes { if (i + 1) != indexes {
out.write(col_sep.as_bytes())?; out.write(col_sep.as_bytes())?;
@ -990,30 +1002,37 @@ fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdou
Ok(lines_printed) Ok(lines_printed)
} }
fn get_line_for_printing(file_line: &FileLine, width: &usize, fn get_line_for_printing(
separator: &String, columns: usize, file_line: &FileLine,
width: &usize,
separator: &String,
columns: usize,
col_width: Option<usize>, col_width: Option<usize>,
is_number_mode: bool, merge_files_print: &Option<usize>, is_number_mode: bool,
merge_files_print: &Option<usize>,
index: &usize, index: &usize,
) -> String { ) -> 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 should_show_line_number = is_number_mode && should_show_line_number_merge_file;
let fmtd_line_number: String = if should_show_line_number { let fmtd_line_number: String = if should_show_line_number {
get_fmtd_line_number(&width, file_line.line_number, &separator) get_fmtd_line_number(&width, file_line.line_number, &separator)
} else { } else {
"".to_string() "".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 let tab_count: usize = complete_line.chars().filter(|i| i == &TAB).count();
.chars()
.filter(|i| i == &TAB)
.count();
let display_length = complete_line.len() + (tab_count * 7); let display_length = complete_line.len() + (tab_count * 7);
// TODO Adjust the width according to -n option // TODO Adjust the width according to -n option
// TODO actual len of the string vs display len of string because of tabs // TODO actual len of the string vs display len of string because of tabs
col_width.map(|i| { col_width
.map(|i| {
let min_width = (i - (columns - 1)) / columns; let min_width = (i - (columns - 1)) / columns;
if display_length < min_width { if display_length < min_width {
for _i in 0..(min_width - display_length) { for _i in 0..(min_width - display_length) {
@ -1021,27 +1040,38 @@ fn get_line_for_printing(file_line: &FileLine, width: &usize,
} }
} }
complete_line complete_line.chars().take(min_width).collect()
.chars() })
.take(min_width) .unwrap_or(complete_line)
.collect()
}).unwrap_or(complete_line)
} }
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 {
format!("{:>width$}{}", &line_str[line_str.len() - *width..], separator, width = width) format!(
"{:>width$}{}",
&line_str[line_str.len() - *width..],
separator,
width = width
)
} else { } else {
format!("{:>width$}{}", line_str, separator, width = width) format!("{:>width$}{}", line_str, separator, width = width)
} }
} }
fn header_content(options: &OutputOptions, page: &usize) -> Vec<String> { fn header_content(options: &OutputOptions, page: &usize) -> Vec<String> {
if options.display_header { if options.display_header {
let first_line: String = format!("{} {} Page {}", options.last_modified_time, options.header, page); let first_line: String = format!(
vec!["".to_string(), "".to_string(), first_line, "".to_string(), "".to_string()] "{} {} 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 { } else {
Vec::new() Vec::new()
} }
@ -1049,12 +1079,17 @@ fn header_content(options: &OutputOptions, page: &usize) -> Vec<String> {
fn file_last_modified_time(path: &str) -> String { fn file_last_modified_time(path: &str) -> String {
let file_metadata = metadata(path); let file_metadata = metadata(path);
return file_metadata.map(|i| { return file_metadata
return i.modified().map(|x| { .map(|i| {
return i
.modified()
.map(|x| {
let datetime: DateTime<Local> = x.into(); let datetime: DateTime<Local> = x.into();
datetime.format("%b %d %H:%M %Y").to_string() datetime.format("%b %d %H:%M %Y").to_string()
}).unwrap_or(String::new()); })
}).unwrap_or(String::new()); .unwrap_or(String::new());
})
.unwrap_or(String::new());
} }
fn current_time() -> String { fn current_time() -> String {
@ -1064,7 +1099,13 @@ fn current_time() -> String {
fn trailer_content(options: &OutputOptions) -> Vec<String> { fn trailer_content(options: &OutputOptions) -> Vec<String> {
if options.as_ref().display_trailer { 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 { } else {
Vec::new() Vec::new()
} }
@ -1076,10 +1117,7 @@ fn trailer_content(options: &OutputOptions) -> Vec<String> {
/// # Arguments /// # Arguments
/// * `opts` - A reference to OutputOptions /// * `opts` - A reference to OutputOptions
fn get_start_line_number(opts: &OutputOptions) -> usize { fn get_start_line_number(opts: &OutputOptions) -> usize {
opts.number opts.number.as_ref().map(|i| i.first_number).unwrap_or(1)
.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. /// Returns number of lines to read from input for constructing one page of pr output.

View file

@ -2,25 +2,29 @@ extern crate chrono;
use common::util::*; use common::util::*;
use std::fs::metadata; use std::fs::metadata;
use test_pr::chrono::DateTime;
use test_pr::chrono::offset::Local; use test_pr::chrono::offset::Local;
use test_pr::chrono::DateTime;
fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String { fn file_last_modified_time(ucmd: &UCommand, path: &str) -> String {
let tmp_dir_path = ucmd.get_full_fixture_path(path); let tmp_dir_path = ucmd.get_full_fixture_path(path);
let file_metadata = metadata(tmp_dir_path); let file_metadata = metadata(tmp_dir_path);
return file_metadata.map(|i| { return file_metadata
return i.modified().map(|x| { .map(|i| {
return i
.modified()
.map(|x| {
let datetime: DateTime<Local> = x.into(); let datetime: DateTime<Local> = x.into();
datetime.format("%b %d %H:%M %Y").to_string() datetime.format("%b %d %H:%M %Y").to_string()
}).unwrap_or(String::new()); })
}).unwrap_or(String::new()); .unwrap_or(String::new());
})
.unwrap_or(String::new());
} }
fn now_time() -> String { fn now_time() -> String {
Local::now().format("%b %d %H:%M %Y").to_string() Local::now().format("%b %d %H:%M %Y").to_string()
} }
#[test] #[test]
fn test_without_any_options() { fn test_without_any_options() {
let test_file_path = "test_one_page.log"; let test_file_path = "test_one_page.log";
@ -30,7 +34,10 @@ fn test_without_any_options() {
scenario scenario
.args(&[test_file_path]) .args(&[test_file_path])
.succeeds() .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] #[test]
@ -42,7 +49,10 @@ fn test_with_numbering_option() {
scenario scenario
.args(&["-n", test_file_path]) .args(&["-n", test_file_path])
.succeeds() .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] #[test]
@ -54,7 +64,10 @@ fn test_with_numbering_option_when_content_is_less_than_page() {
scenario scenario
.args(&["-n", test_file_path]) .args(&["-n", test_file_path])
.succeeds() .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] #[test]
@ -66,7 +79,10 @@ fn test_with_numbering_option_with_number_width() {
scenario scenario
.args(&["-n", "2", test_file_path]) .args(&["-n", "2", test_file_path])
.succeeds() .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] #[test]
@ -79,10 +95,13 @@ fn test_with_header_option() {
scenario scenario
.args(&["-h", header, test_file_path]) .args(&["-h", header, test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
expected_test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value), (&"{last_modified_time}".to_string(), &value),
(&"{header}".to_string(), &header.to_string()) (&"{header}".to_string(), &header.to_string()),
]); ],
);
} }
#[test] #[test]
@ -95,10 +114,13 @@ fn test_with_long_header_option() {
scenario scenario
.args(&["--header=new file", test_file_path]) .args(&["--header=new file", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
expected_test_file_path,
vec![
(&"{last_modified_time}".to_string(), &value), (&"{last_modified_time}".to_string(), &value),
(&"{header}".to_string(), &header.to_string()) (&"{header}".to_string(), &header.to_string()),
]); ],
);
} }
#[test] #[test]
@ -110,9 +132,10 @@ fn test_with_double_space_option() {
scenario scenario
.args(&["-d", test_file_path]) .args(&["-d", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -124,9 +147,10 @@ fn test_with_long_double_space_option() {
scenario scenario
.args(&["--double-space", test_file_path]) .args(&["--double-space", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -138,9 +162,10 @@ fn test_with_first_line_number_option() {
scenario scenario
.args(&["-N", "5", "-n", test_file_path]) .args(&["-N", "5", "-n", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -152,9 +177,10 @@ fn test_with_first_line_number_long_option() {
scenario scenario
.args(&["--first-line-number=5", "-n", test_file_path]) .args(&["--first-line-number=5", "-n", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -166,9 +192,10 @@ fn test_with_number_option_with_custom_separator_char() {
scenario scenario
.args(&["-nc", test_file_path]) .args(&["-nc", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -180,9 +207,10 @@ fn test_with_number_option_with_custom_separator_char_and_width() {
scenario scenario
.args(&["-nc1", test_file_path]) .args(&["-nc1", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -197,9 +225,7 @@ fn test_with_valid_page_ranges() {
new_ucmd!() new_ucmd!()
.args(&["--pages=1:5", test_file_path]) .args(&["--pages=1:5", test_file_path])
.succeeds(); .succeeds();
new_ucmd!() new_ucmd!().args(&["--pages=1", test_file_path]).succeeds();
.args(&["--pages=1", test_file_path])
.succeeds();
new_ucmd!() new_ucmd!()
.args(&["--pages=-1:5", test_file_path]) .args(&["--pages=-1:5", test_file_path])
.fails() .fails()
@ -227,15 +253,17 @@ fn test_with_page_range() {
scenario scenario
.args(&["--pages=15", test_file_path]) .args(&["--pages=15", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
new_ucmd!() new_ucmd!()
.args(&["--pages=15:17", test_file_path]) .args(&["--pages=15:17", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path1, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path1,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -259,9 +287,10 @@ fn test_with_page_length_option() {
scenario scenario
.args(&["--pages=2:3", "-l", "100", "-n", test_file_path]) .args(&["--pages=2:3", "-l", "100", "-n", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
new_ucmd!() new_ucmd!()
.args(&["--pages=2:3", "-l", "5", "-n", test_file_path]) .args(&["--pages=2:3", "-l", "5", "-n", test_file_path])
@ -288,9 +317,10 @@ fn test_with_stdin() {
.pipe_in_fixture("stdin.log") .pipe_in_fixture("stdin.log")
.args(&["--pages=1:2", "-n", "-"]) .args(&["--pages=1:2", "-n", "-"])
.run() .run()
.stdout_is_templated_fixture(expected_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &now_time()), expected_file_path,
]); vec![(&"{last_modified_time}".to_string(), &now_time())],
);
} }
#[test] #[test]
@ -302,9 +332,10 @@ fn test_with_column() {
scenario scenario
.args(&["--pages=3:5", "--column=3", "-n", test_file_path]) .args(&["--pages=3:5", "--column=3", "-n", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -316,9 +347,10 @@ fn test_with_column_across_option() {
scenario scenario
.args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path]) .args(&["--pages=3:5", "--column=3", "-a", "-n", test_file_path])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -328,11 +360,19 @@ fn test_with_column_across_option_and_column_separator() {
let mut scenario = new_ucmd!(); let mut scenario = new_ucmd!();
let value = file_last_modified_time(&scenario, test_file_path); let value = file_last_modified_time(&scenario, test_file_path);
scenario 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() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }
#[test] #[test]
@ -345,23 +385,35 @@ fn test_with_mpr() {
new_ucmd!() new_ucmd!()
.args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1]) .args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &now_time()), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &now_time())],
);
new_ucmd!() new_ucmd!()
.args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1]) .args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1])
.succeeds() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path1, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &now_time()), expected_test_file_path1,
]); vec![(&"{last_modified_time}".to_string(), &now_time())],
);
new_ucmd!() 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() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path2, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &now_time()), expected_test_file_path2,
]); vec![(&"{last_modified_time}".to_string(), &now_time())],
);
} }
#[test] #[test]
@ -380,7 +432,6 @@ fn test_with_mpr_and_column_options() {
.stdout_is(""); .stdout_is("");
} }
#[test] #[test]
fn test_with_offset_space_option() { fn test_with_offset_space_option() {
let test_file_path = "column.log"; let test_file_path = "column.log";
@ -388,9 +439,18 @@ fn test_with_offset_space_option() {
let mut scenario = new_ucmd!(); let mut scenario = new_ucmd!();
let value = file_last_modified_time(&scenario, test_file_path); let value = file_last_modified_time(&scenario, test_file_path);
scenario 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() .succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![ .stdout_is_templated_fixture(
(&"{last_modified_time}".to_string(), &value), expected_test_file_path,
]); vec![(&"{last_modified_time}".to_string(), &value)],
);
} }