1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 03:27:44 +00:00

pr: add -m and -o option

pr: Add -o option
This commit is contained in:
Tilak Patidar 2018-12-24 11:59:12 +05:30 committed by Max Semenik
parent dd07aed4d1
commit 5956894d00
7 changed files with 1151 additions and 94 deletions

View file

@ -15,12 +15,12 @@ extern crate chrono;
extern crate getopts;
extern crate uucore;
use std::io::{BufRead, BufReader, stdin, stdout, stderr, Error, Read, Write, Stdout, Lines};
use std::io::{BufRead, BufReader, stdin, stdout, stderr, Error, Read, Write, Stdout, Lines, Stdin};
use std::vec::Vec;
use chrono::offset::Local;
use chrono::DateTime;
use getopts::{Matches, Options};
use std::fs::{metadata, File};
use std::fs::{metadata, File, Metadata};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
use quick_error::ResultExt;
@ -29,6 +29,7 @@ use getopts::{HasArg, Occur};
use std::num::ParseIntError;
use itertools::{Itertools, GroupBy};
use std::iter::{Enumerate, Map, TakeWhile, SkipWhile};
use itertools::structs::KMergeBy;
static NAME: &str = "pr";
static VERSION: &str = env!("CARGO_PKG_VERSION");
@ -51,6 +52,8 @@ static COLUMN_WIDTH_OPTION: &str = "w";
static ACROSS_OPTION: &str = "a";
static COLUMN_OPTION: &str = "column";
static COLUMN_SEPARATOR_OPTION: &str = "s";
static MERGE_FILES_PRINT: &str = "m";
static OFFSET_SPACES_OPTION: &str = "o";
static FILE_STDIN: &str = "-";
static READ_BUFFER_SIZE: usize = 1024 * 64;
static DEFAULT_COLUMN_WIDTH: usize = 72;
@ -71,6 +74,22 @@ struct OutputOptions {
content_lines_per_page: usize,
page_separator_char: String,
column_mode_options: Option<ColumnModeOptions>,
merge_files_print: Option<usize>,
offset_spaces: usize
}
struct FileLine {
file_id: usize,
line_number: usize,
page_number: usize,
key: usize,
line_content: Result<String, Error>,
}
impl AsRef<FileLine> for FileLine {
fn as_ref(&self) -> &FileLine {
self
}
}
struct ColumnModeOptions {
@ -283,6 +302,27 @@ pub fn uumain(args: Vec<String>) -> i32 {
Occur::Optional,
);
opts.opt(
MERGE_FILES_PRINT,
"merge",
"Merge files. Standard output shall be formatted so the pr utility writes one line from each file specified by a
file operand, side by side into text columns of equal fixed widths, in terms of the number of column positions.
Implementations shall support merging of at least nine file operands.",
"",
HasArg::No,
Occur::Optional,
);
opts.opt(
OFFSET_SPACES_OPTION,
"indent",
"Each line of output shall be preceded by offset <space>s. If the -o option is not specified, the default offset
shall be zero. The space taken is in addition to the output line width (see the -w option below).",
"offset",
HasArg::Yes,
Occur::Optional,
);
opts.optflag("", "help", "display this help and exit");
opts.optflag("V", "version", "output version information and exit");
@ -297,21 +337,20 @@ pub fn uumain(args: Vec<String>) -> i32 {
}
let mut files: Vec<String> = matches.free.clone();
if files.is_empty() {
// -n value is optional if -n <path> is given the opts gets confused
if matches.opt_present(NUMBERING_MODE_OPTION) {
let maybe_file = matches.opt_str(NUMBERING_MODE_OPTION).unwrap();
let is_afile = is_a_file(&maybe_file);
if !is_afile {
print_error(&matches, PrError::NotExists(maybe_file));
// if -n is used just before file path it might be captured as value of -n
if matches.opt_str(NUMBERING_MODE_OPTION).is_some() {
let maybe_a_file_path: String = matches.opt_str(NUMBERING_MODE_OPTION).unwrap();
let is_file: bool = is_a_file(&maybe_a_file_path);
if !is_file && files.is_empty() {
print_error(&matches, PrError::NotExists(maybe_a_file_path));
return 1;
} else {
files.push(maybe_file);
} else if is_file {
files.insert(0, maybe_a_file_path);
}
} else {
} else if files.is_empty() {
//For stdin
files.push(FILE_STDIN.to_owned());
}
files.insert(0, FILE_STDIN.to_owned());
}
@ -319,14 +358,25 @@ pub fn uumain(args: Vec<String>) -> i32 {
return print_usage(&mut opts, &matches);
}
for f in files {
let result_options = build_options(&matches, &f);
let file_groups: Vec<Vec<String>> = if matches.opt_present(MERGE_FILES_PRINT) {
vec![files]
} else {
files.into_iter().map(|i| vec![i]).collect()
};
for file_group in file_groups {
let result_options: Result<OutputOptions, PrError> = build_options(&matches, &file_group);
if result_options.is_err() {
print_error(&matches, result_options.err().unwrap());
return 1;
}
let options = &result_options.unwrap();
let status: i32 = match pr(&f, options) {
let options: &OutputOptions = &result_options.unwrap();
let cmd_result: Result<i32, PrError> = if file_group.len() == 1 {
pr(&file_group.get(0).unwrap(), options)
} else {
mpr(&file_group, options)
};
let status: i32 = match cmd_result {
Err(error) => {
print_error(&matches, error);
1
@ -385,22 +435,44 @@ fn print_usage(opts: &mut Options, matches: &Matches) -> i32 {
return 0;
}
fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrError> {
let header: String = matches.opt_str(STRING_HEADER_OPTION).unwrap_or(if path == FILE_STDIN {
fn build_options(matches: &Matches, paths: &Vec<String>) -> Result<OutputOptions, PrError> {
let is_merge_mode: bool = matches.opt_present(MERGE_FILES_PRINT);
if is_merge_mode && matches.opt_present(COLUMN_OPTION) {
let err_msg: String = "cannot specify number of columns when printing in parallel".to_string();
return Err(PrError::EncounteredErrors(err_msg));
}
if is_merge_mode && matches.opt_present(ACROSS_OPTION) {
let err_msg: String = "cannot specify both printing across and printing in parallel".to_string();
return Err(PrError::EncounteredErrors(err_msg));
}
let merge_files_print: Option<usize> = if matches.opt_present(MERGE_FILES_PRINT) {
Some(paths.len())
} else {
None
};
let header: String = matches.opt_str(STRING_HEADER_OPTION).unwrap_or(if is_merge_mode {
"".to_string()
} else {
path.to_string()
if paths[0].to_string() == FILE_STDIN {
"".to_string()
} else {
paths[0].to_string()
}
});
let default_first_number = NumberingMode::default().first_number;
let first_number = matches.opt_str(FIRST_LINE_NUMBER_OPTION).map(|n| {
let default_first_number: usize = NumberingMode::default().first_number;
let first_number: usize = matches.opt_str(FIRST_LINE_NUMBER_OPTION).map(|n| {
n.parse::<usize>().unwrap_or(default_first_number)
}).unwrap_or(default_first_number);
let numbering_options: Option<NumberingMode> = matches.opt_str(NUMBERING_MODE_OPTION).map(|i| {
let parse_result = i.parse::<usize>();
let parse_result: Result<usize, ParseIntError> = i.parse::<usize>();
let separator = if parse_result.is_err() {
let separator: String = if parse_result.is_err() {
if is_a_file(&i) {
NumberingMode::default().separator
} else {
@ -410,7 +482,7 @@ fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrEr
NumberingMode::default().separator
};
let width = if parse_result.is_err() {
let width: usize = if parse_result.is_err() {
if is_a_file(&i) {
NumberingMode::default().width
} else {
@ -432,7 +504,7 @@ fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrEr
return None;
});
let double_space = matches.opt_present(DOUBLE_SPACE_OPTION);
let double_space: bool = matches.opt_present(DOUBLE_SPACE_OPTION);
let content_line_separator: String = if double_space {
"\n\n".to_string()
@ -442,21 +514,21 @@ fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrEr
let line_separator: String = "\n".to_string();
let last_modified_time = if path.eq(FILE_STDIN) {
let last_modified_time: String = if is_merge_mode || paths[0].eq(FILE_STDIN) {
current_time()
} else {
file_last_modified_time(path)
file_last_modified_time(paths.get(0).unwrap())
};
let invalid_pages_map = |i: Result<usize, ParseIntError>| {
let unparsed_value = matches.opt_str(PAGE_RANGE_OPTION).unwrap();
let unparsed_value: String = matches.opt_str(PAGE_RANGE_OPTION).unwrap();
match i {
Ok(val) => Ok(val),
Err(_e) => Err(PrError::EncounteredErrors(format!("invalid --pages argument '{}'", unparsed_value)))
}
};
let start_page = match matches.opt_str(PAGE_RANGE_OPTION).map(|i| {
let start_page: Option<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)
@ -465,9 +537,9 @@ fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrEr
_ => None
};
let end_page = match matches.opt_str(PAGE_RANGE_OPTION)
.filter(|i| i.contains(":"))
.map(|i| {
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>()
})
@ -481,38 +553,38 @@ fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrEr
return Err(PrError::EncounteredErrors(format!("invalid --pages argument '{}:{}'", start_page.unwrap(), end_page.unwrap())));
}
let page_length = match matches.opt_str(PAGE_LENGTH_OPTION).map(|i| {
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_le_ht = page_length < (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE);
let page_length_le_ht: bool = page_length < (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE);
let display_header_and_trailer = !(page_length_le_ht) && !matches.opt_present(NO_HEADER_TRAILER_OPTION);
let display_header_and_trailer: bool = !(page_length_le_ht) && !matches.opt_present(NO_HEADER_TRAILER_OPTION);
let content_lines_per_page = if page_length_le_ht {
let content_lines_per_page: usize = if page_length_le_ht {
page_length
} else {
page_length - (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE)
};
let page_separator_char = matches.opt_str(FORM_FEED_OPTION).map(|_i| {
let page_separator_char: String = matches.opt_str(FORM_FEED_OPTION).map(|_i| {
'\u{000A}'.to_string()
}).unwrap_or("\n".to_string());
let column_width = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::<usize>()) {
let column_width: Option<usize> = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::<usize>()) {
Some(res) => Some(res?),
_ => None
};
let across_mode = matches.opt_present(ACROSS_OPTION);
let across_mode: bool = matches.opt_present(ACROSS_OPTION);
let column_separator = matches.opt_str(COLUMN_SEPARATOR_OPTION)
let column_separator: String = matches.opt_str(COLUMN_SEPARATOR_OPTION)
.unwrap_or(DEFAULT_COLUMN_SEPARATOR.to_string());
let column_mode_options = match matches.opt_str(COLUMN_OPTION).map(|i| {
let column_mode_options: Option<ColumnModeOptions> = match matches.opt_str(COLUMN_OPTION).map(|i| {
i.parse::<usize>()
}) {
Some(res) => {
@ -529,6 +601,14 @@ fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrEr
_ => 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))?;
Ok(OutputOptions {
number: numbering_options,
header,
@ -543,16 +623,18 @@ fn build_options(matches: &Matches, path: &String) -> Result<OutputOptions, PrEr
content_lines_per_page,
page_separator_char,
column_mode_options,
merge_files_print,
offset_spaces,
})
}
fn open(path: &str) -> Result<Box<Read>, PrError> {
if path == FILE_STDIN {
let stdin = stdin();
let stdin: Stdin = stdin();
return Ok(Box::new(stdin) as Box<Read>);
}
metadata(path).map(|i| {
metadata(path).map(|i: Metadata| {
let path_string = path.to_string();
match i.file_type() {
#[cfg(unix)]
@ -582,50 +664,217 @@ fn open(path: &str) -> Result<Box<Read>, PrError> {
}).unwrap_or(Err(PrError::NotExists(path.to_string())))
}
fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
fn pr(path: &String, options: &OutputOptions) -> Result<i32, PrError> {
let start_page: &usize = options.start_page.as_ref().unwrap_or(&1);
let start_line_number: usize = get_start_line_number(options);
let last_page: Option<&usize> = options.end_page.as_ref();
let lines_needed_per_page: usize = lines_to_read_for_page(options);
let start_line_number: usize = get_start_line_number(options);
let pages: GroupBy<usize, Map<TakeWhile<SkipWhile<Enumerate<Lines<BufReader<Box<Read>>>>, _>, _>, _>, _> =
BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?)
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()
.skip_while(|line_index: &(usize, Result<String, Error>)| {
// Skip the initial lines if not in page range
let start_line_index_of_start_page = (*start_page - 1) * lines_needed_per_page;
line_index.0 < (start_line_index_of_start_page)
.map(move |i: (usize, Result<String, Error>)| {
FileLine {
file_id: 0,
line_number: i.0,
line_content: i.1,
page_number: 0,
key: 0,
}
})
.take_while(|i: &(usize, Result<String, Error>)| {
.skip_while(move |file_line: &FileLine| {
// Skip the initial lines if not in page range
let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page;
file_line.line_number < (start_line_index_of_start_page)
})
.take_while(move |file_line: &FileLine| {
// Only read the file until provided last page reached
last_page
.map(|lp| i.0 < ((*lp) * lines_needed_per_page))
.map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page))
.unwrap_or(true)
})
.map(|i: (usize, Result<String, Error>)| (i.0 + start_line_number, i.1)) // get display line number with line content
.group_by(|i: &(usize, Result<String, Error>)| {
((i.0 - start_line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize
}); // group them by page number
for (page_number, content_with_line_number) in pages.into_iter() {
let mut lines: Vec<(usize, String)> = Vec::new();
for line_number_and_line in content_with_line_number {
let line_number: usize = line_number_and_line.0;
let line: Result<String, Error> = line_number_and_line.1;
let x = line?;
lines.push((line_number, x));
.map(move |file_line: FileLine| {
let page_number = ((file_line.line_number + 1) as f64 / lines_needed_per_page as f64).ceil() as usize;
FileLine {
line_number: file_line.line_number + start_line_number,
page_number,
key: page_number,
..file_line
}
}) // get display line number with line content
.group_by(|file_line: &FileLine| {
file_line.page_number
});
print_page(&lines, options, &page_number);
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()));
}
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()));
}
}
return Ok(0);
}
fn print_page(lines: &Vec<(usize, String)>, options: &OutputOptions, page: &usize) -> Result<usize, Error> {
fn mpr(paths: &Vec<String>, options: &OutputOptions) -> Result<i32, PrError> {
let nfiles = paths.len();
let lines_needed_per_page: usize = lines_to_read_for_page(options);
let lines_needed_per_page_f64: f64 = lines_needed_per_page as f64;
let start_page: &usize = options.start_page.as_ref().unwrap_or(&1);
let last_page: Option<&usize> = options.end_page.as_ref();
let file_line_groups: GroupBy<usize, KMergeBy<Map<TakeWhile<SkipWhile<Map<Enumerate<Lines<BufReader<Box<Read>>>>, _>, _>, _>, _>, _>, _> = paths
.into_iter()
.enumerate()
.map(|indexed_path: (usize, &String)| {
let start_line_number: usize = get_start_line_number(options);
BufReader::with_capacity(READ_BUFFER_SIZE, open(indexed_path.1).unwrap())
.lines()
.enumerate()
.map(move |i: (usize, Result<String, Error>)| {
FileLine {
file_id: indexed_path.0,
line_number: i.0,
line_content: i.1,
page_number: 0,
key: 0,
}
})
.skip_while(move |file_line: &FileLine| {
// Skip the initial lines if not in page range
let start_line_index_of_start_page = (start_page - 1) * lines_needed_per_page;
file_line.line_number < (start_line_index_of_start_page)
})
.take_while(move |file_line: &FileLine| {
// Only read the file until provided last page reached
last_page
.map(|lp| file_line.line_number < ((*lp) * lines_needed_per_page))
.unwrap_or(true)
})
.map(move |file_line: FileLine| {
let page_number = ((file_line.line_number + 2 - start_line_number) as f64 / (lines_needed_per_page_f64)).ceil() as usize;
FileLine {
line_number: file_line.line_number + start_line_number,
page_number,
key: page_number * nfiles + file_line.file_id,
..file_line
}
}) // get display line number with line content
})
.kmerge_by(|a: &FileLine, b: &FileLine| {
if a.key == b.key {
a.line_number < b.line_number
} else {
a.key < b.key
}
})
.group_by(|file_line: &FileLine| {
file_line.key
});
let mut lines: Vec<FileLine> = Vec::new();
let start_page: &usize = options.start_page.as_ref().unwrap_or(&1);
let mut page_counter: usize = *start_page;
for (_key, file_line_group) in file_line_groups.into_iter() {
for file_line in file_line_group {
if file_line.line_content.is_err() {
return Err(PrError::from(file_line.line_content.unwrap_err()));
}
let new_page_number = file_line.page_number;
if page_counter != new_page_number {
fill_missing_files(&mut lines, lines_needed_per_page, &nfiles, page_counter);
let print_status: Result<usize, Error> = print_page(&lines, options, &page_counter);
if print_status.is_err() {
return Err(PrError::from(print_status.unwrap_err()));
}
lines = Vec::new();
}
lines.push(file_line);
page_counter = new_page_number;
}
}
fill_missing_files(&mut lines, lines_needed_per_page, &nfiles, page_counter);
let print_status: Result<usize, Error> = print_page(&lines, options, &page_counter);
if print_status.is_err() {
return Err(PrError::from(print_status.unwrap_err()));
}
return Ok(0);
}
fn fill_missing_files(lines: &mut Vec<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;
for mut i in 0..lines_per_file * nfiles {
let file_id = lines.get(i).map(|i: &FileLine| i.file_id).unwrap_or(file_id_counter);
let line_number = lines.get(i).map(|i: &FileLine| i.line_number).unwrap_or(1);
if lines_processed == lines_per_file {
line_number_counter = init_line_number;
file_id_counter += 1;
lines_processed = 0;
file_id_missing = false;
}
if file_id_counter >= *nfiles {
file_id_counter = 0;
file_id_missing = false;
}
if file_id != file_id_counter {
file_id_missing = true;
}
if file_id_missing {
// Insert missing file_ids
lines.insert(i, FileLine {
file_id: file_id_counter,
line_number: line_number_counter,
line_content: Ok("".to_string()),
page_number,
key: 0,
});
line_number_counter += 1;
} else {
// Insert missing lines for a file_id
if line_number < line_number_counter {
line_number_counter += 1;
lines.insert(i, FileLine {
file_id,
line_number: line_number_counter,
line_content: Ok("".to_string()),
page_number,
key: 0,
});
} else {
line_number_counter = line_number;
if line_number_counter == final_line_number {
line_number_counter = init_line_number;
}
}
}
lines_processed += 1;
}
}
fn print_page(lines: &Vec<FileLine>, options: &OutputOptions, page: &usize) -> Result<usize, Error> {
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);
@ -653,7 +902,7 @@ fn print_page(lines: &Vec<(usize, String)>, options: &OutputOptions, page: &usiz
Ok(lines_written)
}
fn write_columns(lines: &Vec<(usize, String)>, options: &OutputOptions, out: &mut Stdout) -> Result<usize, Error> {
fn write_columns(lines: &Vec<FileLine>, options: &OutputOptions, out: &mut Stdout) -> Result<usize, Error> {
let line_separator = options.content_line_separator.as_bytes();
let content_lines_per_page = if options.double_space {
options.content_lines_per_page / 2
@ -671,26 +920,35 @@ fn write_columns(lines: &Vec<(usize, String)>, options: &OutputOptions, out: &mu
.unwrap_or(NumberingMode::default().separator);
let blank_line = "".to_string();
let columns = get_columns(options);
let columns = options.merge_files_print.unwrap_or(get_columns(options));
let def_sep = DEFAULT_COLUMN_SEPARATOR.to_string();
let col_sep: &String = options
.column_mode_options.as_ref()
.map(|i| &i.column_separator)
.unwrap_or(&blank_line);
.unwrap_or(options
.merge_files_print
.map(|_k| &def_sep)
.unwrap_or(&blank_line)
);
let col_width: Option<usize> = options
.column_mode_options.as_ref()
.map(|i| i.width)
.unwrap_or(None);
.unwrap_or(options
.merge_files_print
.map(|_k| Some(DEFAULT_COLUMN_WIDTH))
.unwrap_or(None)
);
let across_mode = options
.column_mode_options.as_ref()
.map(|i| i.across_mode)
.unwrap_or(false);
let offset_spaces: &usize = &options.offset_spaces;
let mut lines_printed = 0;
let is_number_mode = options.number.is_some();
let fetch_indexes: Vec<Vec<usize>> = if across_mode {
(0..content_lines_per_page)
.map(|a|
@ -707,19 +965,20 @@ fn write_columns(lines: &Vec<(usize, String)>, options: &OutputOptions, out: &mu
).collect()
};
let spaces = " ".repeat(*offset_spaces);
for fetch_index in fetch_indexes {
let indexes = fetch_index.len();
for i in 0..indexes {
let index = fetch_index[i];
let index: usize = fetch_index[i];
if lines.get(index).is_none() {
break;
}
let read_line: &String = &lines.get(index).unwrap().1;
let next_line_number: usize = lines.get(index).unwrap().0;
let trimmed_line = get_line_for_printing(
next_line_number, &width,
&number_separator, columns, col_width,
read_line, is_number_mode);
let file_line: &FileLine = lines.get(index).unwrap();
let trimmed_line: String = format!("{}{}", spaces, get_line_for_printing(
file_line, &width, &number_separator, columns, col_width,
is_number_mode, &options.merge_files_print, &i,
));
out.write(trimmed_line.as_bytes())?;
if (i + 1) != indexes {
out.write(col_sep.as_bytes())?;
@ -731,16 +990,20 @@ fn write_columns(lines: &Vec<(usize, String)>, options: &OutputOptions, out: &mu
Ok(lines_printed)
}
fn get_line_for_printing(line_number: usize, width: &usize,
fn get_line_for_printing(file_line: &FileLine, width: &usize,
separator: &String, columns: usize,
col_width: Option<usize>,
read_line: &String, is_number_mode: bool) -> String {
let fmtd_line_number: String = if is_number_mode {
get_fmtd_line_number(&width, line_number, &separator)
is_number_mode: bool, merge_files_print: &Option<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 = is_number_mode && should_show_line_number_merge_file;
let fmtd_line_number: String = if should_show_line_number {
get_fmtd_line_number(&width, file_line.line_number, &separator)
} else {
"".to_string()
};
let mut complete_line = format!("{}{}", fmtd_line_number, read_line);
let mut complete_line = format!("{}{}", fmtd_line_number, file_line.line_content.as_ref().unwrap());
let tab_count: usize = complete_line
.chars()

View file

@ -0,0 +1,198 @@
{last_modified_time} column.log Page 3
337 337 338 338 339 339
340 340 341 341 342 342
343 343 344 344 345 345
346 346 347 347 348 348
349 349 350 350 351 351
352 352 353 353 354 354
355 355 356 356 357 357
358 358 359 359 360 360
361 361 362 362 363 363
364 364 365 365 366 366
367 367 368 368 369 369
370 370 371 371 372 372
373 373 374 374 375 375
376 376 377 377 378 378
379 379 380 380 381 381
382 382 383 383 384 384
385 385 386 386 387 387
388 388 389 389 390 390
391 391 392 392 393 393
394 394 395 395 396 396
397 397 398 398 399 399
400 400 401 401 402 402
403 403 404 404 405 405
406 406 407 407 408 408
409 409 410 410 411 411
412 412 413 413 414 414
415 415 416 416 417 417
418 418 419 419 420 420
421 421 422 422 423 423
424 424 425 425 426 426
427 427 428 428 429 429
430 430 431 431 432 432
433 433 434 434 435 435
436 436 437 437 438 438
439 439 440 440 441 441
442 442 443 443 444 444
445 445 446 446 447 447
448 448 449 449 450 450
451 451 452 452 453 453
454 454 455 455 456 456
457 457 458 458 459 459
460 460 461 461 462 462
463 463 464 464 465 465
466 466 467 467 468 468
469 469 470 470 471 471
472 472 473 473 474 474
475 475 476 476 477 477
478 478 479 479 480 480
481 481 482 482 483 483
484 484 485 485 486 486
487 487 488 488 489 489
490 490 491 491 492 492
493 493 494 494 495 495
496 496 497 497 498 498
499 499 500 500 501 501
502 502 503 503 504 504
{last_modified_time} column.log Page 4
505 505 506 506 507 507
508 508 509 509 510 510
511 511 512 512 513 513
514 514 515 515 516 516
517 517 518 518 519 519
520 520 521 521 522 522
523 523 524 524 525 525
526 526 527 527 528 528
529 529 530 530 531 531
532 532 533 533 534 534
535 535 536 536 537 537
538 538 539 539 540 540
541 541 542 542 543 543
544 544 545 545 546 546
547 547 548 548 549 549
550 550 551 551 552 552
553 553 554 554 555 555
556 556 557 557 558 558
559 559 560 560 561 561
562 562 563 563 564 564
565 565 566 566 567 567
568 568 569 569 570 570
571 571 572 572 573 573
574 574 575 575 576 576
577 577 578 578 579 579
580 580 581 581 582 582
583 583 584 584 585 585
586 586 587 587 588 588
589 589 590 590 591 591
592 592 593 593 594 594
595 595 596 596 597 597
598 598 599 599 600 600
601 601 602 602 603 603
604 604 605 605 606 606
607 607 608 608 609 609
610 610 611 611 612 612
613 613 614 614 615 615
616 616 617 617 618 618
619 619 620 620 621 621
622 622 623 623 624 624
625 625 626 626 627 627
628 628 629 629 630 630
631 631 632 632 633 633
634 634 635 635 636 636
637 637 638 638 639 639
640 640 641 641 642 642
643 643 644 644 645 645
646 646 647 647 648 648
649 649 650 650 651 651
652 652 653 653 654 654
655 655 656 656 657 657
658 658 659 659 660 660
661 661 662 662 663 663
664 664 665 665 666 666
667 667 668 668 669 669
670 670 671 671 672 672
{last_modified_time} column.log Page 5
673 673 674 674 675 675
676 676 677 677 678 678
679 679 680 680 681 681
682 682 683 683 684 684
685 685 686 686 687 687
688 688 689 689 690 690
691 691 692 692 693 693
694 694 695 695 696 696
697 697 698 698 699 699
700 700 701 701 702 702
703 703 704 704 705 705
706 706 707 707 708 708
709 709 710 710 711 711
712 712 713 713 714 714
715 715 716 716 717 717
718 718 719 719 720 720
721 721 722 722 723 723
724 724 725 725 726 726
727 727 728 728 729 729
730 730 731 731 732 732
733 733 734 734 735 735
736 736 737 737 738 738
739 739 740 740 741 741
742 742 743 743 744 744
745 745 746 746 747 747
748 748 749 749 750 750
751 751 752 752 753 753
754 754 755 755 756 756
757 757 758 758 759 759
760 760 761 761 762 762
763 763 764 764 765 765
766 766 767 767 768 768
769 769 770 770 771 771
772 772 773 773 774 774
775 775 776 776 777 777
778 778 779 779 780 780
781 781 782 782 783 783
784 784 785 785 786 786
787 787 788 788 789 789
790 790 791 791 792 792
793 793 794 794 795 795
796 796 797 797 798 798
799 799 800 800 801 801
802 802 803 803 804 804
805 805 806 806 807 807
808 808 809 809 810 810
811 811 812 812 813 813
814 814 815 815 816 816
817 817 818 818 819 819
820 820 821 821 822 822
823 823 824 824 825 825
826 826 827 827 828 828
829 829 830 830 831 831
832 832 833 833 834 834
835 835 836 836 837 837
838 838 839 839 840 840

11
tests/fixtures/pr/hosts.log vendored Normal file
View file

@ -0,0 +1,11 @@
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting. Do not change this entry.
##
127.0.0.1 localhost
127.0.0.1 Techopss-MacBook-Pro.local
127.0.0.1 tilakpr
255.255.255.255 broadcasthost
::1 localhost

132
tests/fixtures/pr/mpr.log.expected vendored Normal file
View file

@ -0,0 +1,132 @@
{last_modified_time} Page 1
1 1 ##
2 2 # Host Database
3 3 #
4 4 # localhost is used to configure th
5 5 # when the system is booting. Do n
6 6 ##
7 7 127.0.0.1 localhost
8 8 127.0.0.1 Techopss-MacBook-Pro.loca
9 9 127.0.0.1 tilakpr
10 10 255.255.255.255 broadcasthost
11 11 ::1 localhost
12 12
13 13
14 14
15 15
16 16
17 17
18 18
19 19
20 20
21 21
22 22
23 23
24 24
25 25
26 26
27 27
28 28
29 29
30 30
31 31
32 32
33 33
34 34
35 35
36 36
37 37
38 38
39 39
40 40
41 41
42 42
43 43
44 44
45 45
46 46
47 47
48 48
49 49
50 50
51 51
52 52
53 53
54 54
55 55
56 56
{last_modified_time} Page 2
57 57
58 58
59 59
60 60
61 61
62 62
63 63
64 64
65 65
66 66
67 67
68 68
69 69
70 70
71 71
72 72
73 73
74 74
75 75
76 76
77 77
78 78
79 79
80 80
81 81
82 82
83 83
84 84
85 85
86 86
87 87
88 88
89 89
90 90
91 91
92 92
93 93
94 94
95 95
96 96
97 97
98 98
99 99
100 100
101 101
102 102
103 103
104 104
105 105
106 106
107 107
108 108
109 109
110 110
111 111
112 112

198
tests/fixtures/pr/mpr1.log.expected vendored Normal file
View file

@ -0,0 +1,198 @@
{last_modified_time} Page 2
57 57
58 58
59 59
60 60
61 61
62 62
63 63
64 64
65 65
66 66
67 67
68 68
69 69
70 70
71 71
72 72
73 73
74 74
75 75
76 76
77 77
78 78
79 79
80 80
81 81
82 82
83 83
84 84
85 85
86 86
87 87
88 88
89 89
90 90
91 91
92 92
93 93
94 94
95 95
96 96
97 97
98 98
99 99
100 100
101 101
102 102
103 103
104 104
105 105
106 106
107 107
108 108
109 109
110 110
111 111
112 112
{last_modified_time} Page 3
113 113
114 114
115 115
116 116
117 117
118 118
119 119
120 120
121 121
122 122
123 123
124 124
125 125
126 126
127 127
128 128
129 129
130 130
131 131
132 132
133 133
134 134
135 135
136 136
137 137
138 138
139 139
140 140
141 141
142 142
143 143
144 144
145 145
146 146
147 147
148 148
149 149
150 150
151 151
152 152
153 153
154 154
155 155
156 156
157 157
158 158
159 159
160 160
161 161
162 162
163 163
164 164
165 165
166 166
167 167
168 168
{last_modified_time} Page 4
169 169
170 170
171 171
172 172
173 173
174 174
175 175
176 176
177 177
178 178
179 179
180 180
181 181
182 182
183 183
184 184
185 185
186 186
187 187
188 188
189 189
190 190
191 191
192 192
193 193
194 194
195 195
196 196
197 197
198 198
199 199
200 200
201 201
202 202
203 203
204 204
205 205
206 206
207 207
208 208
209 209
210 210
211 211
212 212
213 213
214 214
215 215
216 216
217 217
218 218
219 219
220 220
221 221
222 222
223 223
224 224

200
tests/fixtures/pr/mpr2.log.expected vendored Normal file
View file

@ -0,0 +1,200 @@
{last_modified_time} Page 1
1 1 ## 1
2 2 # Host Database 2
3 3 # 3
4 4 # localhost is used to 4
5 5 # when the system is bo 5
6 6 ## 6
7 7 127.0.0.1 localhost 7
8 8 127.0.0.1 Techopss-MacB 8
9 9 127.0.0.1 tilakpr 9
10 10 255.255.255.255 broadca 10
11 11 ::1 localho 11
12 12 12
13 13 13
14 14 14
15 15 15
16 16 16
17 17 17
18 18 18
19 19 19
20 20 20
21 21 21
22 22 22
23 23 23
24 24 24
25 25 25
26 26 26
27 27 27
28 28 28
29 29 29
30 30 30
31 31 31
32 32 32
33 33 33
34 34 34
35 35 35
36 36 36
37 37 37
38 38 38
39 39 39
40 40 40
41 41 41
42 42 42
43 43 43
44 44 44
45 45 45
46 46 46
47 47 47
48 48 48
49 49 49
50 50 50
51 51 51
52 52 52
53 53 53
54 54 54
55 55 55
56 56 56
57 57 57
58 58 58
59 59 59
60 60 60
61 61 61
62 62 62
63 63 63
64 64 64
65 65 65
66 66 66
67 67 67
68 68 68
69 69 69
70 70 70
71 71 71
72 72 72
73 73 73
74 74 74
75 75 75
76 76 76
77 77 77
78 78 78
79 79 79
80 80 80
81 81 81
82 82 82
83 83 83
84 84 84
85 85 85
86 86 86
87 87 87
88 88 88
89 89 89
90 90 90
{last_modified_time} Page 2
91 91 91
92 92 92
93 93 93
94 94 94
95 95 95
96 96 96
97 97 97
98 98 98
99 99 99
100 100 100
101 101 101
102 102 102
103 103 103
104 104 104
105 105 105
106 106 106
107 107 107
108 108 108
109 109 109
110 110 110
111 111 111
112 112 112
113 113 113
114 114 114
115 115 115
116 116 116
117 117 117
118 118 118
119 119 119
120 120 120
121 121 121
122 122 122
123 123 123
124 124 124
125 125 125
126 126 126
127 127 127
128 128 128
129 129 129
130 130 130
131 131 131
132 132 132
133 133 133
134 134 134
135 135 135
136 136 136
137 137 137
138 138 138
139 139 139
140 140 140
141 141 141
142 142 142
143 143 143
144 144 144
145 145 145
146 146 146
147 147 147
148 148 148
149 149 149
150 150 150
151 151 151
152 152 152
153 153 153
154 154 154
155 155 155
156 156 156
157 157 157
158 158 158
159 159 159
160 160 160
161 161 161
162 162 162
163 163 163
164 164 164
165 165 165
166 166 166
167 167 167
168 168 168
169 169 169
170 170 170
171 171 171
172 172 172
173 173 173
174 174 174
175 175 175
176 176 176
177 177 177
178 178 178
179 179 179
180 180 180

View file

@ -282,7 +282,6 @@ fn test_with_suppress_error_option() {
#[test]
fn test_with_stdin() {
let test_file_path = "stdin.log";
let expected_file_path = "stdin.log.expected";
let mut scenario = new_ucmd!();
scenario
@ -306,7 +305,6 @@ fn test_with_column() {
.stdout_is_templated_fixture(expected_test_file_path, vec![
(&"{last_modified_time}".to_string(), &value),
]);
}
#[test]
@ -321,7 +319,6 @@ fn test_with_column_across_option() {
.stdout_is_templated_fixture(expected_test_file_path, vec![
(&"{last_modified_time}".to_string(), &value),
]);
}
#[test]
@ -336,6 +333,64 @@ fn test_with_column_across_option_and_column_separator() {
.stdout_is_templated_fixture(expected_test_file_path, vec![
(&"{last_modified_time}".to_string(), &value),
]);
}
#[test]
fn test_with_mpr() {
let test_file_path = "column.log";
let test_file_path1 = "hosts.log";
let expected_test_file_path = "mpr.log.expected";
let expected_test_file_path1 = "mpr1.log.expected";
let expected_test_file_path2 = "mpr2.log.expected";
new_ucmd!()
.args(&["--pages=1:2", "-m", "-n", test_file_path, test_file_path1])
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![
(&"{last_modified_time}".to_string(), &now_time()),
]);
new_ucmd!()
.args(&["--pages=2:4", "-m", "-n", test_file_path, test_file_path1])
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path1, vec![
(&"{last_modified_time}".to_string(), &now_time()),
]);
new_ucmd!()
.args(&["--pages=1:2", "-l", "100", "-n", "-m", test_file_path, test_file_path1, test_file_path])
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path2, vec![
(&"{last_modified_time}".to_string(), &now_time()),
]);
}
#[test]
fn test_with_mpr_and_column_options() {
let test_file_path = "column.log";
new_ucmd!()
.args(&["--column=2", "-m", "-n", test_file_path])
.fails()
.stderr_is("pr: cannot specify number of columns when printing in parallel")
.stdout_is("");
new_ucmd!()
.args(&["-a", "-m", "-n", test_file_path])
.fails()
.stderr_is("pr: cannot specify both printing across and printing in parallel")
.stdout_is("");
}
#[test]
fn test_with_offset_space_option() {
let test_file_path = "column.log";
let expected_test_file_path = "column_spaces_across.log.expected";
let mut scenario = new_ucmd!();
let value = file_last_modified_time(&scenario, test_file_path);
scenario
.args(&["-o", "5", "--pages=3:5", "--column=3", "-a", "-n", test_file_path])
.succeeds()
.stdout_is_templated_fixture(expected_test_file_path, vec![
(&"{last_modified_time}".to_string(), &value),
]);
}