mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
pr: refactor get_lines_for_printing, write_columns, recreate_arguments
pr: extract recreate_arguments pr: refactor get_line_for_printing pr: refactor get_lines_for_printing pr: refactor fetch_indexes generate for write_columns pr: refactor write_columns pr: refactor write_columns
This commit is contained in:
parent
f87ada5a11
commit
054c05d5d8
2 changed files with 304 additions and 287 deletions
544
src/pr/pr.rs
544
src/pr/pr.rs
|
@ -38,8 +38,8 @@ 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 LINES_PER_PAGE_FOR_FORM_FEED: usize = 63;
|
||||||
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 STRING_HEADER_OPTION: &str = "h";
|
static STRING_HEADER_OPTION: &str = "h";
|
||||||
|
@ -66,7 +66,6 @@ static READ_BUFFER_SIZE: usize = 1024 * 64;
|
||||||
static DEFAULT_COLUMN_WIDTH: usize = 72;
|
static DEFAULT_COLUMN_WIDTH: usize = 72;
|
||||||
static DEFAULT_COLUMN_WIDTH_WITH_S_OPTION: usize = 512;
|
static DEFAULT_COLUMN_WIDTH_WITH_S_OPTION: usize = 512;
|
||||||
static DEFAULT_COLUMN_SEPARATOR: &char = &TAB;
|
static DEFAULT_COLUMN_SEPARATOR: &char = &TAB;
|
||||||
static BLANK_STRING: &str = "";
|
|
||||||
static FF: u8 = 0x0C as u8;
|
static FF: u8 = 0x0C as u8;
|
||||||
|
|
||||||
struct OutputOptions {
|
struct OutputOptions {
|
||||||
|
@ -79,16 +78,16 @@ struct OutputOptions {
|
||||||
last_modified_time: String,
|
last_modified_time: String,
|
||||||
start_page: usize,
|
start_page: usize,
|
||||||
end_page: Option<usize>,
|
end_page: Option<usize>,
|
||||||
display_header: bool,
|
display_header_and_trailer: bool,
|
||||||
display_trailer: bool,
|
|
||||||
content_lines_per_page: usize,
|
content_lines_per_page: usize,
|
||||||
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: String,
|
||||||
form_feed_used: bool,
|
form_feed_used: bool,
|
||||||
page_width: Option<usize>,
|
|
||||||
join_lines: bool,
|
join_lines: bool,
|
||||||
|
col_sep_for_printing: String,
|
||||||
|
line_width: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FileLine {
|
struct FileLine {
|
||||||
|
@ -143,7 +142,7 @@ impl Default for FileLine {
|
||||||
line_number: 0,
|
line_number: 0,
|
||||||
page_number: 0,
|
page_number: 0,
|
||||||
group_key: 0,
|
group_key: 0,
|
||||||
line_content: Ok(BLANK_STRING.to_string()),
|
line_content: Ok(String::new()),
|
||||||
form_feeds_after: 0,
|
form_feeds_after: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -388,34 +387,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
opts.optflag("", "help", "display this help and exit");
|
opts.optflag("", "help", "display this help and exit");
|
||||||
opts.optflag("V", "version", "output version information and exit");
|
opts.optflag("V", "version", "output version information and exit");
|
||||||
|
|
||||||
// Remove -column and +page option as getopts cannot parse things like -3 etc
|
let opt_args: Vec<String> = recreate_arguments(&args);
|
||||||
let column_page_option = Regex::new(r"^[-+]\d+.*").unwrap();
|
|
||||||
let num_regex: Regex = Regex::new(r"(.\d+)|(\d+)|^[^-]$").unwrap();
|
|
||||||
let a_file: Regex = Regex::new(r"^[^-+].*").unwrap();
|
|
||||||
let n_regex: Regex = Regex::new(r"^-n\s*$").unwrap();
|
|
||||||
let mut arguments = args.clone();
|
|
||||||
let num_option: Option<(usize, &String)> =
|
|
||||||
args.iter().find_position(|x| n_regex.is_match(x.trim()));
|
|
||||||
if num_option.is_some() {
|
|
||||||
let (pos, _value) = num_option.unwrap();
|
|
||||||
let num_val_opt = args.get(pos + 1);
|
|
||||||
if num_val_opt.is_some() {
|
|
||||||
if !num_regex.is_match(num_val_opt.unwrap()) {
|
|
||||||
let could_be_file = arguments.remove(pos + 1);
|
|
||||||
arguments.insert(pos + 1, format!("{}", NumberingMode::default().width));
|
|
||||||
if a_file.is_match(could_be_file.trim().as_ref()) {
|
|
||||||
arguments.insert(pos + 2, could_be_file);
|
|
||||||
} else {
|
|
||||||
arguments.insert(pos + 2, could_be_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let opt_args: Vec<&String> = arguments
|
|
||||||
.iter()
|
|
||||||
.filter(|i| !column_page_option.is_match(i))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let matches = match opts.parse(&opt_args[1..]) {
|
let matches = match opts.parse(&opt_args[1..]) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
|
@ -474,6 +446,40 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns re-written arguments which are passed to the program.
|
||||||
|
/// Removes -column and +page option as getopts cannot parse things like -3 etc
|
||||||
|
/// # Arguments
|
||||||
|
/// * `args` - Command line arguments
|
||||||
|
fn recreate_arguments(args: &Vec<String>) -> Vec<String> {
|
||||||
|
let column_page_option = Regex::new(r"^[-+]\d+.*").unwrap();
|
||||||
|
let num_regex: Regex = Regex::new(r"(.\d+)|(\d+)|^[^-]$").unwrap();
|
||||||
|
let a_file: Regex = Regex::new(r"^[^-+].*").unwrap();
|
||||||
|
let n_regex: Regex = Regex::new(r"^-n\s*$").unwrap();
|
||||||
|
let mut arguments = args.clone();
|
||||||
|
let num_option: Option<(usize, &String)> =
|
||||||
|
args.iter().find_position(|x| n_regex.is_match(x.trim()));
|
||||||
|
if num_option.is_some() {
|
||||||
|
let (pos, _value) = num_option.unwrap();
|
||||||
|
let num_val_opt = args.get(pos + 1);
|
||||||
|
if num_val_opt.is_some() {
|
||||||
|
if !num_regex.is_match(num_val_opt.unwrap()) {
|
||||||
|
let could_be_file = arguments.remove(pos + 1);
|
||||||
|
arguments.insert(pos + 1, format!("{}", NumberingMode::default().width));
|
||||||
|
if a_file.is_match(could_be_file.trim().as_ref()) {
|
||||||
|
arguments.insert(pos + 2, could_be_file);
|
||||||
|
} else {
|
||||||
|
arguments.insert(pos + 2, could_be_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return arguments
|
||||||
|
.into_iter()
|
||||||
|
.filter(|i| !column_page_option.is_match(i))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
fn print_error(matches: &Matches, err: PrError) {
|
fn print_error(matches: &Matches, err: PrError) {
|
||||||
if !matches.opt_present(SUPPRESS_PRINTING_ERROR) {
|
if !matches.opt_present(SUPPRESS_PRINTING_ERROR) {
|
||||||
writeln!(&mut stderr(), "{}", err);
|
writeln!(&mut stderr(), "{}", err);
|
||||||
|
@ -545,24 +551,17 @@ fn build_options(
|
||||||
let form_feed_used =
|
let form_feed_used =
|
||||||
matches.opt_present(FORM_FEED_OPTION) || matches.opt_present(FORM_FEED_OPTION_SMALL);
|
matches.opt_present(FORM_FEED_OPTION) || matches.opt_present(FORM_FEED_OPTION_SMALL);
|
||||||
|
|
||||||
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 =
|
let err_msg: String =
|
||||||
"cannot specify number of columns when printing in parallel".to_string();
|
String::from("cannot specify number of columns when printing in parallel");
|
||||||
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 =
|
let err_msg: String =
|
||||||
"cannot specify both printing across and printing in parallel".to_string();
|
String::from("cannot specify both printing across and printing in parallel");
|
||||||
return Err(PrError::EncounteredErrors(err_msg));
|
return Err(PrError::EncounteredErrors(err_msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,10 +574,10 @@ fn build_options(
|
||||||
let header: String = matches
|
let header: String = matches
|
||||||
.opt_str(STRING_HEADER_OPTION)
|
.opt_str(STRING_HEADER_OPTION)
|
||||||
.unwrap_or(if is_merge_mode {
|
.unwrap_or(if is_merge_mode {
|
||||||
"".to_string()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
if paths[0].to_string() == FILE_STDIN {
|
if paths[0].to_string() == FILE_STDIN {
|
||||||
"".to_string()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
paths[0].to_string()
|
paths[0].to_string()
|
||||||
}
|
}
|
||||||
|
@ -588,7 +587,7 @@ fn build_options(
|
||||||
let first_number: usize =
|
let first_number: usize =
|
||||||
parse_usize(matches, FIRST_LINE_NUMBER_OPTION).unwrap_or(Ok(default_first_number))?;
|
parse_usize(matches, FIRST_LINE_NUMBER_OPTION).unwrap_or(Ok(default_first_number))?;
|
||||||
|
|
||||||
let numbering_options: Option<NumberingMode> = matches
|
let number: Option<NumberingMode> = matches
|
||||||
.opt_str(NUMBERING_MODE_OPTION)
|
.opt_str(NUMBERING_MODE_OPTION)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let parse_result: Result<usize, ParseIntError> = i.parse::<usize>();
|
let parse_result: Result<usize, ParseIntError> = i.parse::<usize>();
|
||||||
|
@ -623,22 +622,23 @@ fn build_options(
|
||||||
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 {
|
||||||
NEW_LINE.repeat(2)
|
"\n".repeat(2)
|
||||||
} else {
|
} else {
|
||||||
NEW_LINE.to_string()
|
"\n".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let line_separator: String = NEW_LINE.to_string();
|
let line_separator: String = "\n".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()
|
let datetime: DateTime<Local> = Local::now();
|
||||||
|
datetime.format("%b %d %H:%M %Y").to_string()
|
||||||
} else {
|
} else {
|
||||||
file_last_modified_time(paths.get(0).unwrap())
|
file_last_modified_time(paths.get(0).unwrap())
|
||||||
};
|
};
|
||||||
|
|
||||||
// +page option is less priority than --pages
|
// +page option is less priority than --pages
|
||||||
let re = Regex::new(r"\s*\+(\d+:*\d*)\s*").unwrap();
|
let page_plus_re = Regex::new(r"\s*\+(\d+:*\d*)\s*").unwrap();
|
||||||
let start_page_in_plus_option: usize = match re.captures(&free_args).map(|i| {
|
let start_page_in_plus_option: usize = match page_plus_re.captures(&free_args).map(|i| {
|
||||||
let unparsed_num = i.get(1).unwrap().as_str().trim();
|
let unparsed_num = i.get(1).unwrap().as_str().trim();
|
||||||
let x: Vec<&str> = unparsed_num.split(":").collect();
|
let x: Vec<&str> = unparsed_num.split(":").collect();
|
||||||
x[0].to_string().parse::<usize>().map_err(|_e| {
|
x[0].to_string().parse::<usize>().map_err(|_e| {
|
||||||
|
@ -649,7 +649,7 @@ fn build_options(
|
||||||
_ => 1,
|
_ => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let end_page_in_plus_option: Option<usize> = match re
|
let end_page_in_plus_option: Option<usize> = match page_plus_re
|
||||||
.captures(&free_args)
|
.captures(&free_args)
|
||||||
.map(|i| i.get(1).unwrap().as_str().trim())
|
.map(|i| i.get(1).unwrap().as_str().trim())
|
||||||
.filter(|i| i.contains(":"))
|
.filter(|i| i.contains(":"))
|
||||||
|
@ -663,6 +663,13 @@ fn build_options(
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 start_page: usize = match matches
|
let start_page: usize = match matches
|
||||||
.opt_str(PAGE_RANGE_OPTION)
|
.opt_str(PAGE_RANGE_OPTION)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
|
@ -696,7 +703,11 @@ fn build_options(
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let default_lines_per_page = if form_feed_used { 63 } else { LINES_PER_PAGE };
|
let default_lines_per_page = if form_feed_used {
|
||||||
|
LINES_PER_PAGE_FOR_FORM_FEED
|
||||||
|
} else {
|
||||||
|
LINES_PER_PAGE
|
||||||
|
};
|
||||||
|
|
||||||
let page_length: usize =
|
let page_length: usize =
|
||||||
parse_usize(matches, PAGE_LENGTH_OPTION).unwrap_or(Ok(default_lines_per_page))?;
|
parse_usize(matches, PAGE_LENGTH_OPTION).unwrap_or(Ok(default_lines_per_page))?;
|
||||||
|
@ -716,7 +727,7 @@ fn build_options(
|
||||||
let bytes = vec![FF];
|
let bytes = vec![FF];
|
||||||
String::from_utf8(bytes).unwrap()
|
String::from_utf8(bytes).unwrap()
|
||||||
} else {
|
} else {
|
||||||
NEW_LINE.to_string()
|
"\n".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let across_mode: bool = matches.opt_present(ACROSS_OPTION);
|
let across_mode: bool = matches.opt_present(ACROSS_OPTION);
|
||||||
|
@ -776,11 +787,37 @@ fn build_options(
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let offset_spaces: usize = parse_usize(matches, OFFSET_SPACES_OPTION).unwrap_or(Ok(0))?;
|
let offset_spaces: String =
|
||||||
|
" ".repeat(parse_usize(matches, OFFSET_SPACES_OPTION).unwrap_or(Ok(0))?);
|
||||||
let join_lines: bool = matches.opt_present(JOIN_LINES_OPTION);
|
let join_lines: bool = matches.opt_present(JOIN_LINES_OPTION);
|
||||||
|
|
||||||
|
let col_sep_for_printing = column_mode_options
|
||||||
|
.as_ref()
|
||||||
|
.map(|i| i.column_separator.clone())
|
||||||
|
.unwrap_or(
|
||||||
|
merge_files_print
|
||||||
|
.map(|_k| DEFAULT_COLUMN_SEPARATOR.to_string())
|
||||||
|
.unwrap_or(String::new()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let columns_to_print =
|
||||||
|
merge_files_print.unwrap_or(column_mode_options.as_ref().map(|i| i.columns).unwrap_or(1));
|
||||||
|
|
||||||
|
let line_width: Option<usize> = if join_lines {
|
||||||
|
None
|
||||||
|
} else if columns_to_print > 1 {
|
||||||
|
Some(
|
||||||
|
column_mode_options
|
||||||
|
.as_ref()
|
||||||
|
.map(|i| i.width)
|
||||||
|
.unwrap_or(DEFAULT_COLUMN_WIDTH),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
page_width
|
||||||
|
};
|
||||||
|
|
||||||
Ok(OutputOptions {
|
Ok(OutputOptions {
|
||||||
number: numbering_options,
|
number,
|
||||||
header,
|
header,
|
||||||
double_space,
|
double_space,
|
||||||
line_separator,
|
line_separator,
|
||||||
|
@ -788,16 +825,16 @@ fn build_options(
|
||||||
last_modified_time,
|
last_modified_time,
|
||||||
start_page,
|
start_page,
|
||||||
end_page,
|
end_page,
|
||||||
display_header: display_header_and_trailer,
|
display_header_and_trailer,
|
||||||
display_trailer: display_header_and_trailer,
|
|
||||||
content_lines_per_page,
|
content_lines_per_page,
|
||||||
page_separator_char,
|
page_separator_char,
|
||||||
column_mode_options,
|
column_mode_options,
|
||||||
merge_files_print,
|
merge_files_print,
|
||||||
offset_spaces,
|
offset_spaces,
|
||||||
form_feed_used,
|
form_feed_used,
|
||||||
page_width,
|
|
||||||
join_lines,
|
join_lines,
|
||||||
|
col_sep_for_printing,
|
||||||
|
line_width,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -870,21 +907,11 @@ fn split_lines_if_form_feed(file_content: Result<String, IOError>) -> Vec<FileLi
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
let start_line_number: usize = get_start_line_number(options);
|
|
||||||
let last_page: Option<usize> = options.end_page;
|
|
||||||
let lines_needed_per_page: usize = lines_to_read_for_page(options);
|
|
||||||
let lines: Lines<BufReader<Box<Read>>> =
|
let lines: Lines<BufReader<Box<Read>>> =
|
||||||
BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines();
|
BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines();
|
||||||
|
|
||||||
let pages: Box<Iterator<Item = (usize, Vec<FileLine>)>> = read_stream_and_create_pages(
|
let pages: Box<Iterator<Item = (usize, Vec<FileLine>)>> =
|
||||||
lines,
|
read_stream_and_create_pages(options, lines, 0);
|
||||||
start_line_number,
|
|
||||||
lines_needed_per_page,
|
|
||||||
start_page,
|
|
||||||
last_page,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
for page_with_page_number in pages {
|
for page_with_page_number in pages {
|
||||||
let page_number = page_with_page_number.0 + 1;
|
let page_number = page_with_page_number.0 + 1;
|
||||||
|
@ -895,13 +922,15 @@ fn pr(path: &String, options: &OutputOptions) -> Result<i32, PrError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_stream_and_create_pages(
|
fn read_stream_and_create_pages(
|
||||||
|
options: &OutputOptions,
|
||||||
lines: Lines<BufReader<Box<Read>>>,
|
lines: Lines<BufReader<Box<Read>>>,
|
||||||
start_line_number: usize,
|
|
||||||
lines_needed_per_page: usize,
|
|
||||||
start_page: usize,
|
|
||||||
last_page: Option<usize>,
|
|
||||||
file_id: usize,
|
file_id: usize,
|
||||||
) -> Box<Iterator<Item = (usize, Vec<FileLine>)>> {
|
) -> Box<Iterator<Item = (usize, Vec<FileLine>)>> {
|
||||||
|
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;
|
||||||
|
let lines_needed_per_page: usize = lines_to_read_for_page(options);
|
||||||
|
|
||||||
return Box::new(
|
return Box::new(
|
||||||
lines
|
lines
|
||||||
.map(split_lines_if_form_feed)
|
.map(split_lines_if_form_feed)
|
||||||
|
@ -958,10 +987,6 @@ fn read_stream_and_create_pages(
|
||||||
fn mpr(paths: &Vec<String>, options: &OutputOptions) -> Result<i32, PrError> {
|
fn mpr(paths: &Vec<String>, options: &OutputOptions) -> Result<i32, PrError> {
|
||||||
let nfiles = paths.len();
|
let nfiles = paths.len();
|
||||||
|
|
||||||
let lines_needed_per_page: usize = lines_to_read_for_page(options);
|
|
||||||
let start_page: usize = options.start_page;
|
|
||||||
let last_page: Option<usize> = options.end_page;
|
|
||||||
|
|
||||||
// Check if files exists
|
// Check if files exists
|
||||||
for path in paths {
|
for path in paths {
|
||||||
open(path)?;
|
open(path)?;
|
||||||
|
@ -972,34 +997,26 @@ fn mpr(paths: &Vec<String>, options: &OutputOptions) -> Result<i32, PrError> {
|
||||||
KMergeBy<FlatMap<Map<Box<Iterator<Item = (usize, Vec<FileLine>)>>, _>, _, _>, _>,
|
KMergeBy<FlatMap<Map<Box<Iterator<Item = (usize, Vec<FileLine>)>>, _>, _, _>, _>,
|
||||||
_,
|
_,
|
||||||
> = paths
|
> = paths
|
||||||
.into_iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|indexed_path: (usize, &String)| {
|
.map(|indexed_path: (usize, &String)| {
|
||||||
let start_line_number: usize = get_start_line_number(options);
|
|
||||||
let lines =
|
let lines =
|
||||||
BufReader::with_capacity(READ_BUFFER_SIZE, open(indexed_path.1).unwrap()).lines();
|
BufReader::with_capacity(READ_BUFFER_SIZE, open(indexed_path.1).unwrap()).lines();
|
||||||
|
|
||||||
read_stream_and_create_pages(
|
read_stream_and_create_pages(options, lines, indexed_path.0)
|
||||||
lines,
|
.map(move |x: (usize, Vec<FileLine>)| {
|
||||||
start_line_number,
|
let file_line = x.1;
|
||||||
lines_needed_per_page,
|
let page_number = x.0 + 1;
|
||||||
start_page,
|
file_line
|
||||||
last_page,
|
.into_iter()
|
||||||
indexed_path.0,
|
.map(|fl| FileLine {
|
||||||
)
|
page_number,
|
||||||
.map(move |x: (usize, Vec<FileLine>)| {
|
group_key: page_number * nfiles + fl.file_id,
|
||||||
let file_line = x.1;
|
..fl
|
||||||
let page_number = x.0 + 1;
|
})
|
||||||
file_line
|
.collect()
|
||||||
.into_iter()
|
})
|
||||||
.map(|fl| FileLine {
|
.flat_map(|x: Vec<FileLine>| x)
|
||||||
page_number,
|
|
||||||
group_key: page_number * nfiles + fl.file_id,
|
|
||||||
..fl
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.flat_map(|x: Vec<FileLine>| x)
|
|
||||||
})
|
})
|
||||||
.kmerge_by(|a: &FileLine, b: &FileLine| {
|
.kmerge_by(|a: &FileLine, b: &FileLine| {
|
||||||
if a.group_key == b.group_key {
|
if a.group_key == b.group_key {
|
||||||
|
@ -1039,18 +1056,19 @@ fn print_page(
|
||||||
options: &OutputOptions,
|
options: &OutputOptions,
|
||||||
page: &usize,
|
page: &usize,
|
||||||
) -> Result<usize, IOError> {
|
) -> Result<usize, IOError> {
|
||||||
|
let line_separator = options.line_separator.as_bytes();
|
||||||
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);
|
||||||
|
|
||||||
let out: &mut Stdout = &mut stdout();
|
let out: &mut Stdout = &mut stdout();
|
||||||
let line_separator = options.line_separator.as_bytes();
|
|
||||||
|
|
||||||
out.lock();
|
out.lock();
|
||||||
for x in header {
|
for x in header {
|
||||||
out.write(x.as_bytes())?;
|
out.write(x.as_bytes())?;
|
||||||
out.write(line_separator)?;
|
out.write(line_separator)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lines_written = write_columns(lines, options, out)?;
|
let lines_written = write_columns(lines, options, out)?;
|
||||||
|
|
||||||
for index in 0..trailer_content.len() {
|
for index in 0..trailer_content.len() {
|
||||||
|
@ -1071,159 +1089,90 @@ fn write_columns(
|
||||||
out: &mut Stdout,
|
out: &mut Stdout,
|
||||||
) -> Result<usize, IOError> {
|
) -> 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
|
||||||
} else {
|
} else {
|
||||||
options.content_lines_per_page
|
options.content_lines_per_page
|
||||||
};
|
};
|
||||||
|
|
||||||
let number_width: usize = options.number.as_ref().map(|i| i.width).unwrap_or(0);
|
|
||||||
let number_separator: String = options
|
|
||||||
.number
|
|
||||||
.as_ref()
|
|
||||||
.map(|i| i.separator.to_string())
|
|
||||||
.unwrap_or(NumberingMode::default().separator);
|
|
||||||
|
|
||||||
let blank_line = "".to_string();
|
|
||||||
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 line_width: Option<usize> = options.line_width;
|
||||||
let col_sep: &String = options
|
let mut lines_printed = 0;
|
||||||
|
let feed_line_present = options.form_feed_used;
|
||||||
|
let mut not_found_break = false;
|
||||||
|
|
||||||
|
let across_mode = options
|
||||||
.column_mode_options
|
.column_mode_options
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|i| &i.column_separator)
|
.map(|i| i.across_mode)
|
||||||
.unwrap_or(
|
.unwrap_or(false);
|
||||||
options
|
|
||||||
.merge_files_print
|
|
||||||
.map(|_k| &def_sep)
|
|
||||||
.unwrap_or(&blank_line),
|
|
||||||
);
|
|
||||||
|
|
||||||
let line_width: Option<usize> = if options.join_lines {
|
let mut filled_lines: Vec<Option<&FileLine>> = Vec::new();
|
||||||
None
|
if options.merge_files_print.is_some() {
|
||||||
} else if columns > 1 {
|
let mut offset: usize = 0;
|
||||||
options
|
|
||||||
.column_mode_options
|
|
||||||
.as_ref()
|
|
||||||
.map(|i| Some(i.width))
|
|
||||||
.unwrap_or(Some(DEFAULT_COLUMN_WIDTH))
|
|
||||||
} else {
|
|
||||||
options.page_width
|
|
||||||
};
|
|
||||||
|
|
||||||
let offset_spaces: &usize = &options.offset_spaces;
|
|
||||||
let mut lines_printed = 0;
|
|
||||||
let is_number_mode = options.number.is_some();
|
|
||||||
let feed_line_present = options.form_feed_used;
|
|
||||||
let spaces = " ".repeat(*offset_spaces);
|
|
||||||
let mut not_found_break = false;
|
|
||||||
if options.merge_files_print.is_none() {
|
|
||||||
let across_mode = options
|
|
||||||
.column_mode_options
|
|
||||||
.as_ref()
|
|
||||||
.map(|i| i.across_mode)
|
|
||||||
.unwrap_or(false);
|
|
||||||
|
|
||||||
let fetch_indexes: Vec<Vec<usize>> = if across_mode {
|
|
||||||
(0..content_lines_per_page)
|
|
||||||
.map(|a| (0..columns).map(|i| a * columns + i).collect())
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
(0..content_lines_per_page)
|
|
||||||
.map(|start| {
|
|
||||||
(0..columns)
|
|
||||||
.map(|i| start + content_lines_per_page * i)
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
for fetch_index in fetch_indexes {
|
|
||||||
let indexes = fetch_index.len();
|
|
||||||
for i in 0..indexes {
|
|
||||||
let index: usize = fetch_index[i];
|
|
||||||
if lines.get(index).is_none() {
|
|
||||||
not_found_break = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let file_line: &FileLine = lines.get(index).unwrap();
|
|
||||||
let trimmed_line: String = format!(
|
|
||||||
"{}{}",
|
|
||||||
spaces,
|
|
||||||
get_line_for_printing(
|
|
||||||
file_line,
|
|
||||||
&number_width,
|
|
||||||
&number_separator,
|
|
||||||
columns,
|
|
||||||
is_number_mode,
|
|
||||||
&options.merge_files_print,
|
|
||||||
&i,
|
|
||||||
line_width,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
out.write(trimmed_line.as_bytes())?;
|
|
||||||
if (i + 1) != indexes && !options.join_lines {
|
|
||||||
out.write(col_sep.as_bytes())?;
|
|
||||||
}
|
|
||||||
lines_printed += 1;
|
|
||||||
}
|
|
||||||
if not_found_break && feed_line_present {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
out.write(line_separator)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut index: usize = 0;
|
|
||||||
let mut batches: Vec<Vec<&FileLine>> = Vec::new();
|
|
||||||
for col in 0..columns {
|
for col in 0..columns {
|
||||||
let mut batch: Vec<&FileLine> = vec![];
|
let mut inserted = 0;
|
||||||
for i in index..lines.len() {
|
for i in offset..lines.len() {
|
||||||
let line = lines.get(i).unwrap();
|
let line = lines.get(i).unwrap();
|
||||||
if line.file_id != col {
|
if line.file_id != col {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
batch.push(line);
|
filled_lines.push(Some(line));
|
||||||
index += 1;
|
offset += 1;
|
||||||
|
inserted += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for _i in inserted..content_lines_per_page {
|
||||||
|
filled_lines.push(None);
|
||||||
}
|
}
|
||||||
batches.push(batch);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let blank_line = &&FileLine::default();
|
let table: Vec<Vec<Option<&FileLine>>> = (0..content_lines_per_page)
|
||||||
|
.map(move |a| {
|
||||||
|
(0..columns)
|
||||||
|
.map(|i| {
|
||||||
|
if across_mode {
|
||||||
|
lines.get(a * columns + i)
|
||||||
|
} else if options.merge_files_print.is_some() {
|
||||||
|
*filled_lines
|
||||||
|
.get(content_lines_per_page * i + a)
|
||||||
|
.unwrap_or(&None)
|
||||||
|
} else {
|
||||||
|
lines.get(content_lines_per_page * i + a)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
for _i in 0..content_lines_per_page {
|
let blank_line: FileLine = FileLine::default();
|
||||||
for col_index in 0..columns {
|
for row in table {
|
||||||
let col: Option<&Vec<&FileLine>> = batches.get(col_index);
|
let indexes = row.len();
|
||||||
let file_line = if col.is_some() {
|
for (i, cell) in row.iter().enumerate() {
|
||||||
let opt_file_line: Option<&&FileLine> = col.unwrap().get(_i);
|
if cell.is_none() && options.merge_files_print.is_some() {
|
||||||
opt_file_line.unwrap_or(blank_line)
|
out.write(
|
||||||
} else {
|
get_line_for_printing(&options, &blank_line, columns, &i, &line_width, indexes)
|
||||||
blank_line
|
.as_bytes(),
|
||||||
};
|
)?;
|
||||||
|
} else if cell.is_none() {
|
||||||
|
not_found_break = true;
|
||||||
|
break;
|
||||||
|
} else if cell.is_some() {
|
||||||
|
let file_line: &FileLine = cell.unwrap();
|
||||||
|
|
||||||
let trimmed_line: String = format!(
|
out.write(
|
||||||
"{}{}",
|
get_line_for_printing(&options, file_line, columns, &i, &line_width, indexes)
|
||||||
spaces,
|
.as_bytes(),
|
||||||
get_line_for_printing(
|
)?;
|
||||||
file_line,
|
|
||||||
&number_width,
|
|
||||||
&number_separator,
|
|
||||||
columns,
|
|
||||||
is_number_mode,
|
|
||||||
&options.merge_files_print,
|
|
||||||
&col_index,
|
|
||||||
line_width,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
out.write(trimmed_line.as_bytes())?;
|
|
||||||
if (col_index + 1) != columns && !options.join_lines {
|
|
||||||
out.write(col_sep.as_bytes())?;
|
|
||||||
}
|
|
||||||
lines_printed += 1;
|
lines_printed += 1;
|
||||||
}
|
}
|
||||||
if feed_line_present {
|
}
|
||||||
break;
|
if not_found_break && feed_line_present {
|
||||||
} else {
|
break;
|
||||||
out.write(line_separator)?;
|
} else {
|
||||||
}
|
out.write(line_separator)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1231,72 +1180,94 @@ fn write_columns(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_line_for_printing(
|
fn get_line_for_printing(
|
||||||
|
options: &OutputOptions,
|
||||||
file_line: &FileLine,
|
file_line: &FileLine,
|
||||||
number_width: &usize,
|
|
||||||
separator: &String,
|
|
||||||
columns: usize,
|
columns: usize,
|
||||||
is_number_mode: bool,
|
|
||||||
merge_files_print: &Option<usize>,
|
|
||||||
index: &usize,
|
index: &usize,
|
||||||
line_width: Option<usize>,
|
line_width: &Option<usize>,
|
||||||
|
indexes: usize,
|
||||||
) -> String {
|
) -> String {
|
||||||
let should_show_line_number_merge_file =
|
// Check this condition
|
||||||
merge_files_print.is_none() || index == &usize::min_value();
|
let blank_line = String::new();
|
||||||
let should_show_line_number = is_number_mode && should_show_line_number_merge_file;
|
let fmtd_line_number: String = get_fmtd_line_number(&options, file_line.line_number, index);
|
||||||
let fmtd_line_number: String = if should_show_line_number {
|
|
||||||
get_fmtd_line_number(&number_width, file_line.line_number, &separator)
|
|
||||||
} else {
|
|
||||||
"".to_string()
|
|
||||||
};
|
|
||||||
let mut complete_line = format!(
|
let mut complete_line = format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
fmtd_line_number,
|
fmtd_line_number,
|
||||||
file_line.line_content.as_ref().unwrap()
|
file_line.line_content.as_ref().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let offset_spaces: &String = &options.offset_spaces;
|
||||||
|
|
||||||
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);
|
let display_length = complete_line.len() + (tab_count * 7);
|
||||||
line_width
|
|
||||||
.map(|i| {
|
|
||||||
let min_width = (i - (columns - 1)) / columns;
|
|
||||||
if display_length < min_width {
|
|
||||||
for _i in 0..(min_width - display_length) {
|
|
||||||
complete_line.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
complete_line.chars().take(min_width).collect()
|
let sep = if (index + 1) != indexes && !options.join_lines {
|
||||||
})
|
&options.col_sep_for_printing
|
||||||
.unwrap_or(complete_line)
|
} else {
|
||||||
|
&blank_line
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{}{}{}",
|
||||||
|
offset_spaces,
|
||||||
|
line_width
|
||||||
|
.map(|i| {
|
||||||
|
let min_width = (i - (columns - 1)) / columns;
|
||||||
|
if display_length < min_width {
|
||||||
|
for _i in 0..(min_width - display_length) {
|
||||||
|
complete_line.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_line.chars().take(min_width).collect()
|
||||||
|
})
|
||||||
|
.unwrap_or(complete_line),
|
||||||
|
sep
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_fmtd_line_number(width: &usize, line_number: usize, separator: &String) -> String {
|
fn get_fmtd_line_number(opts: &OutputOptions, line_number: usize, index: &usize) -> String {
|
||||||
let line_str = line_number.to_string();
|
let should_show_line_number =
|
||||||
if line_str.len() >= *width {
|
opts.number.is_some() && (opts.merge_files_print.is_none() || index == &0);
|
||||||
format!(
|
if should_show_line_number && line_number != 0 {
|
||||||
"{:>width$}{}",
|
let line_str = line_number.to_string();
|
||||||
&line_str[line_str.len() - *width..],
|
let num_opt = opts.number.as_ref().unwrap();
|
||||||
separator,
|
let width = num_opt.width;
|
||||||
width = width
|
let separator = &num_opt.separator;
|
||||||
)
|
if line_str.len() >= width {
|
||||||
|
format!(
|
||||||
|
"{:>width$}{}",
|
||||||
|
&line_str[line_str.len() - width..],
|
||||||
|
separator,
|
||||||
|
width = width
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!("{:>width$}{}", line_str, separator, width = width)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
format!("{:>width$}{}", line_str, separator, width = width)
|
String::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a five line header content if displaying header is not disabled by
|
||||||
|
/// using `NO_HEADER_TRAILER_OPTION` option.
|
||||||
|
/// # Arguments
|
||||||
|
/// * `options` - A reference to OutputOptions
|
||||||
|
/// * `page` - A reference to page number
|
||||||
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_and_trailer {
|
||||||
let first_line: String = format!(
|
let first_line: String = format!(
|
||||||
"{} {} Page {}",
|
"{} {} Page {}",
|
||||||
options.last_modified_time, options.header, page
|
options.last_modified_time, options.header, page
|
||||||
);
|
);
|
||||||
vec![
|
vec![
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
first_line,
|
first_line,
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
@ -1318,19 +1289,18 @@ fn file_last_modified_time(path: &str) -> String {
|
||||||
.unwrap_or(String::new());
|
.unwrap_or(String::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_time() -> String {
|
/// Returns five empty lines as trailer content if displaying trailer
|
||||||
let datetime: DateTime<Local> = Local::now();
|
/// is not disabled by using `NO_HEADER_TRAILER_OPTION`option.
|
||||||
datetime.format("%b %d %H:%M %Y").to_string()
|
/// # Arguments
|
||||||
}
|
/// * `opts` - A reference to OutputOptions
|
||||||
|
|
||||||
fn trailer_content(options: &OutputOptions) -> Vec<String> {
|
fn trailer_content(options: &OutputOptions) -> Vec<String> {
|
||||||
if options.as_ref().display_trailer && !options.form_feed_used {
|
if options.display_header_and_trailer && !options.form_feed_used {
|
||||||
vec![
|
vec![
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
BLANK_STRING.to_string(),
|
String::new(),
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|
|
@ -17,3 +17,50 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue