mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +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:
parent
5956894d00
commit
5c6c524334
2 changed files with 528 additions and 430 deletions
546
src/pr/pr.rs
546
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<usize>,
|
||||
start_page: usize,
|
||||
end_page: Option<usize>,
|
||||
display_header: bool,
|
||||
display_trailer: bool,
|
||||
|
@ -75,15 +77,15 @@ struct OutputOptions {
|
|||
page_separator_char: String,
|
||||
column_mode_options: Option<ColumnModeOptions>,
|
||||
merge_files_print: Option<usize>,
|
||||
offset_spaces: usize
|
||||
offset_spaces: usize,
|
||||
}
|
||||
|
||||
struct FileLine {
|
||||
file_id: usize,
|
||||
line_number: usize,
|
||||
page_number: usize,
|
||||
key: usize,
|
||||
line_content: Result<String, Error>,
|
||||
group_key: usize,
|
||||
line_content: Result<String, IOError>,
|
||||
}
|
||||
|
||||
impl AsRef<FileLine> for FileLine {
|
||||
|
@ -93,7 +95,7 @@ impl AsRef<FileLine> for FileLine {
|
|||
}
|
||||
|
||||
struct ColumnModeOptions {
|
||||
width: Option<usize>,
|
||||
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<Error> for PrError {
|
||||
fn from(err: Error) -> Self {
|
||||
PrError::EncounteredErrors(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::ParseIntError> for PrError {
|
||||
fn from(err: std::num::ParseIntError) -> Self {
|
||||
impl From<IOError> for PrError {
|
||||
fn from(err: IOError) -> Self {
|
||||
PrError::EncounteredErrors(err.to_string())
|
||||
}
|
||||
}
|
||||
|
@ -137,8 +133,8 @@ impl From<std::num::ParseIntError> 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)
|
||||
}
|
||||
|
@ -353,7 +349,6 @@ pub fn uumain(args: Vec<String>) -> 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<String>) -> 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<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> {
|
||||
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);
|
||||
|
||||
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,7 +475,9 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
|
|||
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()
|
||||
} else {
|
||||
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 first_number: usize = matches.opt_str(FIRST_LINE_NUMBER_OPTION).map(|n| {
|
||||
n.parse::<usize>().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<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 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) {
|
||||
NumberingMode::default().width
|
||||
} else {
|
||||
i[1..].parse::<usize>().unwrap_or(NumberingMode::default().width)
|
||||
i[1..]
|
||||
.parse::<usize>()
|
||||
.unwrap_or(NumberingMode::default().width)
|
||||
}
|
||||
} else {
|
||||
parse_result.unwrap()
|
||||
|
@ -497,7 +523,8 @@ fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions
|
|||
separator,
|
||||
first_number,
|
||||
}
|
||||
}).or_else(|| {
|
||||
})
|
||||
.or_else(|| {
|
||||
if matches.opt_present(NUMBERING_MODE_OPTION) {
|
||||
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 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<String>) -> Result<OutputOptions
|
|||
file_last_modified_time(paths.get(0).unwrap())
|
||||
};
|
||||
|
||||
let invalid_pages_map = |i: Result<usize, ParseIntError>| {
|
||||
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: Option<usize> = match matches.opt_str(PAGE_RANGE_OPTION).map(|i| {
|
||||
let start_page: usize = match matches
|
||||
.opt_str(PAGE_RANGE_OPTION)
|
||||
.map(|i| {
|
||||
let x: Vec<&str> = i.split(":").collect();
|
||||
x[0].parse::<usize>()
|
||||
}).map(invalid_pages_map)
|
||||
x[0].to_string()
|
||||
})
|
||||
.map(invalid_pages_map)
|
||||
{
|
||||
Some(res) => Some(res?),
|
||||
_ => None
|
||||
Some(res) => res?,
|
||||
_ => 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(":"))
|
||||
.map(|i: String| {
|
||||
let x: Vec<&str> = i.split(":").collect();
|
||||
x[1].parse::<usize>()
|
||||
x[1].to_string()
|
||||
})
|
||||
.map(invalid_pages_map)
|
||||
{
|
||||
Some(res) => Some(res?),
|
||||
_ => None
|
||||
_ => 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::<usize>()
|
||||
}) {
|
||||
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<String>) -> Result<OutputOptions
|
|||
page_length - (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE)
|
||||
};
|
||||
|
||||
let page_separator_char: String = matches.opt_str(FORM_FEED_OPTION).map(|_i| {
|
||||
'\u{000A}'.to_string()
|
||||
}).unwrap_or("\n".to_string());
|
||||
let page_separator_char: String = matches
|
||||
.opt_str(FORM_FEED_OPTION)
|
||||
.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>()) {
|
||||
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<ColumnModeOptions> = match matches.opt_str(COLUMN_OPTION).map(|i| {
|
||||
i.parse::<usize>()
|
||||
}) {
|
||||
Some(res) => {
|
||||
Some(ColumnModeOptions {
|
||||
let column_mode_options: Option<ColumnModeOptions> = match parse_usize(matches, COLUMN_OPTION) {
|
||||
Some(res) => Some(ColumnModeOptions {
|
||||
columns: res?,
|
||||
width: match column_width {
|
||||
Some(x) => Some(x),
|
||||
None => Some(DEFAULT_COLUMN_WIDTH)
|
||||
},
|
||||
width: column_width,
|
||||
column_separator,
|
||||
across_mode,
|
||||
})
|
||||
}
|
||||
_ => None
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let offset_spaces: usize = matches.opt_str(OFFSET_SPACES_OPTION)
|
||||
.map(|i| {
|
||||
match i.parse::<usize>() {
|
||||
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,93 +645,80 @@ fn open(path: &str) -> Result<Box<Read>, PrError> {
|
|||
return Ok(Box::new(stdin) as Box<Read>);
|
||||
}
|
||||
|
||||
metadata(path).map(|i: Metadata| {
|
||||
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))
|
||||
}
|
||||
ft if ft.is_block_device() => Err(PrError::UnknownFiletype(path_string)),
|
||||
#[cfg(unix)]
|
||||
ft if ft.is_char_device() =>
|
||||
{
|
||||
Err(PrError::UnknownFiletype(path_string))
|
||||
}
|
||||
ft if ft.is_char_device() => Err(PrError::UnknownFiletype(path_string)),
|
||||
#[cfg(unix)]
|
||||
ft if ft.is_fifo() =>
|
||||
{
|
||||
Err(PrError::UnknownFiletype(path_string))
|
||||
}
|
||||
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_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<Read>),
|
||||
_ => Err(PrError::UnknownFiletype(path_string))
|
||||
ft if ft.is_file() || ft.is_symlink() => {
|
||||
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> {
|
||||
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<usize, Map<TakeWhile<SkipWhile<Map<Enumerate<Lines<BufReader<Box<Read>>>>, _>, _>, _>, _>, _> =
|
||||
BufReader::with_capacity(READ_BUFFER_SIZE, open(path).unwrap())
|
||||
let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page;
|
||||
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()
|
||||
.enumerate()
|
||||
.map(move |i: (usize, Result<String, Error>)| {
|
||||
FileLine {
|
||||
.map(|i: (usize, Result<String, IOError>)| FileLine {
|
||||
file_id: 0,
|
||||
line_number: i.0,
|
||||
line_content: i.1,
|
||||
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
|
||||
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| {
|
||||
.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(move |file_line: FileLine| {
|
||||
let page_number = ((file_line.line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize;
|
||||
.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,
|
||||
key: page_number,
|
||||
group_key: page_number,
|
||||
..file_line
|
||||
}
|
||||
}) // get display line number with line content
|
||||
.group_by(|file_line: &FileLine| {
|
||||
file_line.page_number
|
||||
});
|
||||
|
||||
.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<FileLine> = 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<usize, Error> = 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<String>, options: &OutputOptions) -> Result<i32, PrError> {
|
|||
|
||||
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<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()
|
||||
.enumerate()
|
||||
.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())
|
||||
.lines()
|
||||
.enumerate()
|
||||
.map(move |i: (usize, Result<String, Error>)| {
|
||||
FileLine {
|
||||
.map(move |i: (usize, Result<String, IOError>)| FileLine {
|
||||
file_id: indexed_path.0,
|
||||
line_number: i.0,
|
||||
line_content: i.1,
|
||||
page_number: 0,
|
||||
key: 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
|
||||
|
||||
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<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;
|
||||
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<usize, Error> = 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<String>, options: &OutputOptions) -> Result<i32, PrError> {
|
|||
}
|
||||
}
|
||||
|
||||
fill_missing_files(&mut lines, lines_needed_per_page, &nfiles, page_counter);
|
||||
let print_status: Result<usize, Error> = 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<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 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 {
|
||||
lines.insert(
|
||||
i,
|
||||
FileLine {
|
||||
file_id: file_id_counter,
|
||||
line_number: line_number_counter,
|
||||
line_content: Ok("".to_string()),
|
||||
page_number,
|
||||
key: 0,
|
||||
});
|
||||
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 {
|
||||
lines.insert(
|
||||
i,
|
||||
FileLine {
|
||||
file_id,
|
||||
line_number: line_number_counter,
|
||||
line_content: Ok("".to_string()),
|
||||
page_number,
|
||||
key: 0,
|
||||
});
|
||||
group_key: 0,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
line_number_counter = line_number;
|
||||
if line_number_counter == final_line_number {
|
||||
line_number_counter = init_line_number;
|
||||
}
|
||||
|
||||
lines_processed_per_file += 1;
|
||||
}
|
||||
}
|
||||
|
||||
lines_processed += 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 header: Vec<String> = header_content(options, page);
|
||||
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)
|
||||
}
|
||||
|
||||
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 content_lines_per_page = if options.double_space {
|
||||
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
|
||||
};
|
||||
|
||||
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,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 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
|
||||
.unwrap_or(
|
||||
options
|
||||
.merge_files_print
|
||||
.map(|_k| &def_sep)
|
||||
.unwrap_or(&blank_line)
|
||||
.unwrap_or(&blank_line),
|
||||
);
|
||||
|
||||
// TODO simplify
|
||||
let col_width: Option<usize> = options
|
||||
.column_mode_options.as_ref()
|
||||
.map(|i| i.width)
|
||||
.unwrap_or(options
|
||||
.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)
|
||||
.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);
|
||||
|
||||
|
@ -951,18 +955,16 @@ fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdou
|
|||
let is_number_mode = options.number.is_some();
|
||||
let fetch_indexes: Vec<Vec<usize>> = if across_mode {
|
||||
(0..content_lines_per_page)
|
||||
.map(|a|
|
||||
(0..columns)
|
||||
.map(|i| a * columns + i)
|
||||
.map(|a| (0..columns).map(|i| a * columns + i).collect())
|
||||
.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<FileLine>, 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,30 +1002,37 @@ fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdou
|
|||
Ok(lines_printed)
|
||||
}
|
||||
|
||||
fn get_line_for_printing(file_line: &FileLine, width: &usize,
|
||||
separator: &String, columns: usize,
|
||||
fn get_line_for_printing(
|
||||
file_line: &FileLine,
|
||||
width: &usize,
|
||||
separator: &String,
|
||||
columns: 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,
|
||||
) -> 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| {
|
||||
col_width
|
||||
.map(|i| {
|
||||
let min_width = (i - (columns - 1)) / columns;
|
||||
if display_length < min_width {
|
||||
for _i in 0..(min_width - display_length) {
|
||||
|
@ -1021,27 +1040,38 @@ fn get_line_for_printing(file_line: &FileLine, width: &usize,
|
|||
}
|
||||
}
|
||||
|
||||
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<String> {
|
||||
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<String> {
|
|||
|
||||
fn file_last_modified_time(path: &str) -> String {
|
||||
let file_metadata = metadata(path);
|
||||
return file_metadata.map(|i| {
|
||||
return i.modified().map(|x| {
|
||||
return file_metadata
|
||||
.map(|i| {
|
||||
return i
|
||||
.modified()
|
||||
.map(|x| {
|
||||
let datetime: DateTime<Local> = x.into();
|
||||
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 {
|
||||
|
@ -1064,7 +1099,13 @@ fn current_time() -> String {
|
|||
|
||||
fn trailer_content(options: &OutputOptions) -> Vec<String> {
|
||||
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<String> {
|
|||
/// # 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.
|
||||
|
|
208
tests/test_pr.rs
208
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| {
|
||||
return file_metadata
|
||||
.map(|i| {
|
||||
return i
|
||||
.modified()
|
||||
.map(|x| {
|
||||
let datetime: DateTime<Local> = x.into();
|
||||
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 {
|
||||
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![
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
vec![
|
||||
(&"{last_modified_time}".to_string(), &value),
|
||||
(&"{header}".to_string(), &header.to_string())
|
||||
]);
|
||||
(&"{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![
|
||||
.stdout_is_templated_fixture(
|
||||
expected_test_file_path,
|
||||
vec![
|
||||
(&"{last_modified_time}".to_string(), &value),
|
||||
(&"{header}".to_string(), &header.to_string())
|
||||
]);
|
||||
(&"{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)],
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue