1
Fork 0
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:
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;
#[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 += 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 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| {
// TODO Adjust the width according to -n option
// TODO actual len of the string vs display len of string because of tabs
col_width
.map(|i| {
let min_width = (i - (columns - 1)) / columns;
if display_length < min_width {
for _i in 0..(min_width - display_length) {
@ -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.

View file

@ -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)],
);
}