mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-29 12:07:46 +00:00
pr: add ColumnModeOptions and fix reading of input after page range is finished
This commit is contained in:
parent
fd4447785b
commit
0098cfe5b7
1 changed files with 121 additions and 63 deletions
180
src/pr/pr.rs
180
src/pr/pr.rs
|
@ -37,13 +37,17 @@ static NUMBERING_MODE_DEFAULT_WIDTH: usize = 5;
|
||||||
static STRING_HEADER_OPTION: &str = "h";
|
static STRING_HEADER_OPTION: &str = "h";
|
||||||
static DOUBLE_SPACE_OPTION: &str = "d";
|
static DOUBLE_SPACE_OPTION: &str = "d";
|
||||||
static NUMBERING_MODE_OPTION: &str = "n";
|
static NUMBERING_MODE_OPTION: &str = "n";
|
||||||
static PAGE_RANGE_OPTION: &str = "page";
|
static PAGE_RANGE_OPTION: &str = "pages";
|
||||||
static NO_HEADER_TRAILER_OPTION: &str = "t";
|
static NO_HEADER_TRAILER_OPTION: &str = "t";
|
||||||
static PAGE_LENGTH_OPTION: &str = "l";
|
static PAGE_LENGTH_OPTION: &str = "l";
|
||||||
static SUPPRESS_PRINTING_ERROR: &str = "r";
|
static SUPPRESS_PRINTING_ERROR: &str = "r";
|
||||||
static FORM_FEED_OPTION: &str = "F";
|
static FORM_FEED_OPTION: &str = "F";
|
||||||
|
static COLUMN_WIDTH_OPTION: &str = "w";
|
||||||
|
static COLUMN_OPTION: &str = "column";
|
||||||
static FILE_STDIN: &str = "-";
|
static FILE_STDIN: &str = "-";
|
||||||
static READ_BUFFER_SIZE: usize = 1024 * 64;
|
static READ_BUFFER_SIZE: usize = 1024 * 64;
|
||||||
|
static DEFAULT_COLUMN_WIDTH: usize = 72;
|
||||||
|
static DEFAULT_COLUMN_SEPARATOR: &str = "\t";
|
||||||
|
|
||||||
struct OutputOptions {
|
struct OutputOptions {
|
||||||
/// Line numbering mode
|
/// Line numbering mode
|
||||||
|
@ -59,6 +63,13 @@ struct OutputOptions {
|
||||||
content_lines_per_page: usize,
|
content_lines_per_page: usize,
|
||||||
suppress_errors: bool,
|
suppress_errors: bool,
|
||||||
page_separator_char: String,
|
page_separator_char: String,
|
||||||
|
column_mode_options: Option<ColumnModeOptions>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ColumnModeOptions {
|
||||||
|
width: usize,
|
||||||
|
columns: usize,
|
||||||
|
column_separator: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<OutputOptions> for OutputOptions {
|
impl AsRef<OutputOptions> for OutputOptions {
|
||||||
|
@ -139,23 +150,19 @@ quick_error! {
|
||||||
pub fn uumain(args: Vec<String>) -> i32 {
|
pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
let mut opts = getopts::Options::new();
|
let mut opts = getopts::Options::new();
|
||||||
|
|
||||||
opts.opt(
|
opts.optflagopt(
|
||||||
"",
|
"",
|
||||||
PAGE_RANGE_OPTION,
|
PAGE_RANGE_OPTION,
|
||||||
"Begin and stop printing with page FIRST_PAGE[:LAST_PAGE]",
|
"Begin and stop printing with page FIRST_PAGE[:LAST_PAGE]",
|
||||||
"FIRST_PAGE[:LAST_PAGE]",
|
"FIRST_PAGE[:LAST_PAGE]",
|
||||||
HasArg::Yes,
|
|
||||||
Occur::Optional,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
opts.opt(
|
opts.optopt(
|
||||||
STRING_HEADER_OPTION,
|
STRING_HEADER_OPTION,
|
||||||
"header",
|
"header",
|
||||||
"Use the string header to replace the file name \
|
"Use the string header to replace the file name \
|
||||||
in the header line.",
|
in the header line.",
|
||||||
"STRING",
|
"STRING"
|
||||||
HasArg::Yes,
|
|
||||||
Occur::Optional,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
opts.opt(
|
opts.opt(
|
||||||
|
@ -168,16 +175,14 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
Occur::Optional,
|
Occur::Optional,
|
||||||
);
|
);
|
||||||
|
|
||||||
opts.opt(
|
opts.optflagopt(
|
||||||
NUMBERING_MODE_OPTION,
|
NUMBERING_MODE_OPTION,
|
||||||
"",
|
"",
|
||||||
"Provide width digit line numbering. The default for width, if not specified, is 5. The number occupies
|
"Provide width digit line numbering. The default for width, if not specified, is 5. The number occupies
|
||||||
the first width column positions of each text column or each line of -m output. If char (any nondigit
|
the first width column positions of each text column or each line of -m output. If char (any nondigit
|
||||||
character) is given, it is appended to the line number to separate it from whatever follows. The default
|
character) is given, it is appended to the line number to separate it from whatever follows. The default
|
||||||
for char is a <tab>. Line numbers longer than width columns are truncated.",
|
for char is a <tab>. Line numbers longer than width columns are truncated.",
|
||||||
"[char][width]",
|
"[char][width]"
|
||||||
HasArg::Yes,
|
|
||||||
Occur::Optional,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
opts.opt(
|
opts.opt(
|
||||||
|
@ -219,6 +224,30 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
Occur::Optional,
|
Occur::Optional,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
opts.opt(
|
||||||
|
"",
|
||||||
|
COLUMN_OPTION,
|
||||||
|
"Produce multi-column output that is arranged in column columns (the default shall be 1) and is written down each
|
||||||
|
column in the order in which the text is received from the input file. This option should not be used with -m.
|
||||||
|
The options -e and -i shall be assumed for multiple text-column output. Whether or not text columns are pro‐
|
||||||
|
duced with identical vertical lengths is unspecified, but a text column shall never exceed the length of the
|
||||||
|
page (see the -l option). When used with -t, use the minimum number of lines to write the output.",
|
||||||
|
"[column]",
|
||||||
|
HasArg::Yes,
|
||||||
|
Occur::Optional,
|
||||||
|
);
|
||||||
|
|
||||||
|
opts.opt(
|
||||||
|
COLUMN_WIDTH_OPTION,
|
||||||
|
"width",
|
||||||
|
"Set the width of the line to width column positions for multiple text-column output only. If the -w option is
|
||||||
|
not specified and the -s option is not specified, the default width shall be 72. If the -w option is not speci‐
|
||||||
|
fied and the -s option is specified, the default width shall be 512.",
|
||||||
|
"[width]",
|
||||||
|
HasArg::Yes,
|
||||||
|
Occur::Optional,
|
||||||
|
);
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
|
@ -368,6 +397,24 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result<Ou
|
||||||
'\u{000A}'.to_string()
|
'\u{000A}'.to_string()
|
||||||
}).unwrap_or("\n".to_string());
|
}).unwrap_or("\n".to_string());
|
||||||
|
|
||||||
|
let column_width = match matches.opt_str(COLUMN_WIDTH_OPTION).map(|i| i.parse::<usize>()) {
|
||||||
|
Some(res) => res?,
|
||||||
|
_ => DEFAULT_COLUMN_WIDTH
|
||||||
|
};
|
||||||
|
|
||||||
|
let column_mode_options = match matches.opt_str(COLUMN_OPTION).map(|i| {
|
||||||
|
i.parse::<usize>()
|
||||||
|
}) {
|
||||||
|
Some(res) => {
|
||||||
|
Some(ColumnModeOptions {
|
||||||
|
columns: res?,
|
||||||
|
width: column_width,
|
||||||
|
column_separator: DEFAULT_COLUMN_SEPARATOR.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(OutputOptions {
|
Ok(OutputOptions {
|
||||||
number: numbering_options,
|
number: numbering_options,
|
||||||
header: header.to_string(),
|
header: header.to_string(),
|
||||||
|
@ -381,6 +428,7 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result<Ou
|
||||||
content_lines_per_page,
|
content_lines_per_page,
|
||||||
suppress_errors,
|
suppress_errors,
|
||||||
page_separator_char,
|
page_separator_char,
|
||||||
|
column_mode_options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,15 +453,19 @@ fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
|
||||||
let mut page: usize = 0;
|
let mut page: usize = 0;
|
||||||
let mut buffered_content: Vec<String> = Vec::new();
|
let mut buffered_content: Vec<String> = Vec::new();
|
||||||
let content_lines_per_page = options.as_ref().content_lines_per_page;
|
let content_lines_per_page = options.as_ref().content_lines_per_page;
|
||||||
|
let columns = options.as_ref().column_mode_options.as_ref().map(|i| i.columns).unwrap_or(1);
|
||||||
let lines_per_page = if options.as_ref().double_space {
|
let lines_per_page = if options.as_ref().double_space {
|
||||||
content_lines_per_page / 2
|
(content_lines_per_page / 2) * columns
|
||||||
} else {
|
} else {
|
||||||
content_lines_per_page
|
content_lines_per_page * columns
|
||||||
};
|
};
|
||||||
for line in BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines() {
|
for line in BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines() {
|
||||||
if i == lines_per_page {
|
if i == lines_per_page {
|
||||||
page = page + 1;
|
page = page + 1;
|
||||||
i = 0;
|
i = 0;
|
||||||
|
if !_is_within_page_range(options, &page) {
|
||||||
|
return Ok(0)
|
||||||
|
}
|
||||||
print_page(&buffered_content, options, &page)?;
|
print_page(&buffered_content, options, &page)?;
|
||||||
buffered_content = Vec::new();
|
buffered_content = Vec::new();
|
||||||
}
|
}
|
||||||
|
@ -422,33 +474,26 @@ fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
|
if !_is_within_page_range(options, &page) {
|
||||||
|
return Ok(0)
|
||||||
|
}
|
||||||
page = page + 1;
|
page = page + 1;
|
||||||
print_page(&buffered_content, options, &page)?;
|
print_page(&buffered_content, options, &page)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn _is_within_page_range(options: &OutputOptions, page: &usize) -> bool {
|
||||||
|
let start_page = options.as_ref().start_page.as_ref();
|
||||||
|
let last_page = options.as_ref().end_page.as_ref();
|
||||||
|
(start_page.is_none() || page >= start_page.unwrap()) && (last_page.is_none() || page <= last_page.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize) -> Result<usize, Error> {
|
fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize) -> Result<usize, Error> {
|
||||||
let start_page = options.as_ref().start_page.as_ref();
|
|
||||||
let last_page = options.as_ref().end_page.as_ref();
|
|
||||||
let content_lines_per_page = options.as_ref().content_lines_per_page;
|
|
||||||
let is_within_print_range = (start_page.is_none() || page >= start_page.unwrap()) &&
|
|
||||||
(last_page.is_none() || page <= last_page.unwrap());
|
|
||||||
let page_separator = options.as_ref().page_separator_char.as_bytes();
|
let page_separator = options.as_ref().page_separator_char.as_bytes();
|
||||||
if !is_within_print_range {
|
let header: Vec<String> = header_content(options, page);
|
||||||
return Ok(0);
|
let trailer_content: Vec<String> = trailer_content(options);
|
||||||
}
|
|
||||||
let header: Vec<String> = if options.as_ref().display_header {
|
|
||||||
header_content(options, page)
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let trailer_content: Vec<String> = if options.as_ref().display_trailer {
|
|
||||||
trailer_content()
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
|
|
||||||
let out: &mut Stdout = &mut stdout();
|
let out: &mut Stdout = &mut stdout();
|
||||||
let line_separator = options.as_ref().line_separator.as_bytes();
|
let line_separator = options.as_ref().line_separator.as_bytes();
|
||||||
|
@ -461,34 +506,8 @@ fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize) -> Res
|
||||||
lines_written += 1;
|
lines_written += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let width: usize = options.as_ref()
|
lines_written += write_columns(lines, options, page_separator, out, line_separator, page)?;
|
||||||
.number.as_ref()
|
|
||||||
.map(|i| i.width)
|
|
||||||
.unwrap_or(0);
|
|
||||||
let separator: String = options.as_ref()
|
|
||||||
.number.as_ref()
|
|
||||||
.map(|i| i.separator.to_string())
|
|
||||||
.unwrap_or(NumberingMode::default().separator);
|
|
||||||
|
|
||||||
let prev_lines = content_lines_per_page * (page - 1);
|
|
||||||
let mut i = 1;
|
|
||||||
for x in lines {
|
|
||||||
if options.number.is_none() {
|
|
||||||
out.write(x.as_bytes())?;
|
|
||||||
} else {
|
|
||||||
let fmtd_line_number: String = get_fmtd_line_number(&width, prev_lines + i, &separator);
|
|
||||||
out.write(format!("{}{}", fmtd_line_number, x).as_bytes())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == trailer_content.len() {
|
|
||||||
out.write(page_separator)?;
|
|
||||||
} else {
|
|
||||||
out.write(line_separator)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
lines_written += i - 1;
|
|
||||||
for index in 0..trailer_content.len() {
|
for index in 0..trailer_content.len() {
|
||||||
let x: &String = trailer_content.get(index).unwrap();
|
let x: &String = trailer_content.get(index).unwrap();
|
||||||
out.write(x.as_bytes())?;
|
out.write(x.as_bytes())?;
|
||||||
|
@ -503,6 +522,37 @@ fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize) -> Res
|
||||||
Ok(lines_written)
|
Ok(lines_written)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_columns(lines: &Vec<String>, options: &OutputOptions, page_separator: &[u8], out: &mut Stdout, line_separator: &[u8], page: &usize) -> Result<usize, Error> {
|
||||||
|
let content_lines_per_page = options.as_ref().content_lines_per_page;
|
||||||
|
let prev_lines = content_lines_per_page * (page - 1);
|
||||||
|
let width: usize = options.as_ref()
|
||||||
|
.number.as_ref()
|
||||||
|
.map(|i| i.width)
|
||||||
|
.unwrap_or(0);
|
||||||
|
let separator: String = options.as_ref()
|
||||||
|
.number.as_ref()
|
||||||
|
.map(|i| i.separator.to_string())
|
||||||
|
.unwrap_or(NumberingMode::default().separator);
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
for x in lines {
|
||||||
|
if options.number.is_none() {
|
||||||
|
out.write(x.as_bytes())?;
|
||||||
|
} else {
|
||||||
|
let fmtd_line_number: String = get_fmtd_line_number(&width, prev_lines + i, &separator);
|
||||||
|
out.write(format!("{}{}", fmtd_line_number, x).as_bytes())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == lines.len() {
|
||||||
|
out.write(page_separator)?;
|
||||||
|
} else {
|
||||||
|
out.write(line_separator)?;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
Ok(i)
|
||||||
|
}
|
||||||
|
|
||||||
fn get_fmtd_line_number(width: &usize, line_number: usize, separator: &String) -> String {
|
fn get_fmtd_line_number(width: &usize, line_number: usize, separator: &String) -> String {
|
||||||
let line_str = line_number.to_string();
|
let line_str = line_number.to_string();
|
||||||
if line_str.len() >= *width {
|
if line_str.len() >= *width {
|
||||||
|
@ -514,8 +564,12 @@ fn get_fmtd_line_number(width: &usize, line_number: usize, separator: &String) -
|
||||||
|
|
||||||
|
|
||||||
fn header_content(options: &OutputOptions, page: &usize) -> Vec<String> {
|
fn header_content(options: &OutputOptions, page: &usize) -> Vec<String> {
|
||||||
|
if options.as_ref().display_header {
|
||||||
let first_line: String = format!("{} {} Page {}", options.last_modified_time, options.header, page);
|
let first_line: String = format!("{} {} Page {}", options.last_modified_time, options.header, page);
|
||||||
vec!["".to_string(), "".to_string(), first_line, "".to_string(), "".to_string()]
|
vec!["".to_string(), "".to_string(), first_line, "".to_string(), "".to_string()]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file_last_modified_time(path: &str) -> String {
|
fn file_last_modified_time(path: &str) -> String {
|
||||||
|
@ -533,8 +587,12 @@ fn current_time() -> String {
|
||||||
datetime.format("%b %d %H:%M %Y").to_string()
|
datetime.format("%b %d %H:%M %Y").to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trailer_content() -> Vec<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!["".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string()]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_input_type(path: &str) -> Option<InputType> {
|
fn get_input_type(path: &str) -> Option<InputType> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue