mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-30 12:37:49 +00:00
pr: add -t option to not print header, trailer and -l to print line numbers
pr: Add -l option set number of lines pr: Refactor opts
This commit is contained in:
parent
55043d7a15
commit
9e023b8a91
1 changed files with 74 additions and 15 deletions
89
src/pr/pr.rs
89
src/pr/pr.rs
|
@ -24,19 +24,22 @@ use std::fs::{metadata, File};
|
||||||
use std::os::unix::fs::FileTypeExt;
|
use std::os::unix::fs::FileTypeExt;
|
||||||
use quick_error::ResultExt;
|
use quick_error::ResultExt;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
use getopts::HasArg;
|
||||||
|
use getopts::Occur;
|
||||||
|
|
||||||
static NAME: &str = "pr";
|
static NAME: &str = "pr";
|
||||||
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
static VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
static LINES_PER_PAGE: usize = 66;
|
static LINES_PER_PAGE: usize = 66;
|
||||||
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 CONTENT_LINES_PER_PAGE: usize = LINES_PER_PAGE - HEADER_LINES_PER_PAGE - TRAILER_LINES_PER_PAGE;
|
|
||||||
static NUMBERING_MODE_DEFAULT_SEPARATOR: &str = "\t";
|
static NUMBERING_MODE_DEFAULT_SEPARATOR: &str = "\t";
|
||||||
static NUMBERING_MODE_DEFAULT_WIDTH: usize = 5;
|
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 = "page";
|
||||||
|
static NO_HEADER_TRAILER_OPTION: &str = "t";
|
||||||
|
static PAGE_LENGTH_OPTION: &str = "l";
|
||||||
static FILE_STDIN: &str = "-";
|
static FILE_STDIN: &str = "-";
|
||||||
static READ_BUFFER_SIZE: usize = 1024 * 64;
|
static READ_BUFFER_SIZE: usize = 1024 * 64;
|
||||||
|
|
||||||
|
@ -49,6 +52,9 @@ struct OutputOptions {
|
||||||
last_modified_time: String,
|
last_modified_time: String,
|
||||||
start_page: Option<usize>,
|
start_page: Option<usize>,
|
||||||
end_page: Option<usize>,
|
end_page: Option<usize>,
|
||||||
|
display_header: bool,
|
||||||
|
display_trailer: bool,
|
||||||
|
content_lines_per_page: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<OutputOptions> for OutputOptions {
|
impl AsRef<OutputOptions> for OutputOptions {
|
||||||
|
@ -129,29 +135,36 @@ 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.optflagopt(
|
opts.opt(
|
||||||
"",
|
"",
|
||||||
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.optopt(
|
opts.opt(
|
||||||
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.optflag(
|
opts.opt(
|
||||||
DOUBLE_SPACE_OPTION,
|
DOUBLE_SPACE_OPTION,
|
||||||
"double-space",
|
"double-space",
|
||||||
"Produce output that is double spaced. An extra <newline> character is output following every <newline>
|
"Produce output that is double spaced. An extra <newline> character is output following every <newline>
|
||||||
found in the input.",
|
found in the input.",
|
||||||
|
"",
|
||||||
|
HasArg::No,
|
||||||
|
Occur::Optional,
|
||||||
);
|
);
|
||||||
|
|
||||||
opts.optflagopt(
|
opts.opt(
|
||||||
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
|
||||||
|
@ -159,6 +172,29 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
||||||
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(
|
||||||
|
NO_HEADER_TRAILER_OPTION,
|
||||||
|
"omit-header",
|
||||||
|
"Write neither the five-line identifying header nor the five-line trailer usually supplied for each page. Quit
|
||||||
|
writing after the last line of each file without spacing to the end of the page.",
|
||||||
|
"",
|
||||||
|
HasArg::No,
|
||||||
|
Occur::Optional,
|
||||||
|
);
|
||||||
|
|
||||||
|
opts.opt(
|
||||||
|
PAGE_LENGTH_OPTION,
|
||||||
|
"length",
|
||||||
|
"Override the 66-line default and reset the page length to lines. If lines is not greater than the sum of both
|
||||||
|
the header and trailer depths (in lines), the pr utility shall suppress both the header and trailer, as if the
|
||||||
|
-t option were in effect.",
|
||||||
|
"lines",
|
||||||
|
HasArg::Yes,
|
||||||
|
Occur::Optional,
|
||||||
);
|
);
|
||||||
|
|
||||||
opts.optflag("", "help", "display this help and exit");
|
opts.optflag("", "help", "display this help and exit");
|
||||||
|
@ -262,8 +298,6 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result<Ou
|
||||||
"\n".to_string()
|
"\n".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let last_modified_time = if path.eq(FILE_STDIN) {
|
let last_modified_time = if path.eq(FILE_STDIN) {
|
||||||
current_time()
|
current_time()
|
||||||
} else {
|
} else {
|
||||||
|
@ -292,6 +326,19 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result<Ou
|
||||||
return Err(PrError::EncounteredErrors(format!("invalid page range ‘{}:{}’", start_page.unwrap(), end_page.unwrap())));
|
return Err(PrError::EncounteredErrors(format!("invalid page range ‘{}:{}’", start_page.unwrap(), end_page.unwrap())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let page_length = match matches.opt_str(PAGE_LENGTH_OPTION).map(|i| {
|
||||||
|
i.parse::<usize>()
|
||||||
|
}) {
|
||||||
|
Some(res) => res?,
|
||||||
|
_ => LINES_PER_PAGE
|
||||||
|
};
|
||||||
|
|
||||||
|
let content_lines_per_page = page_length - (HEADER_LINES_PER_PAGE - TRAILER_LINES_PER_PAGE);
|
||||||
|
|
||||||
|
let display_header_and_trailer = !(page_length < (HEADER_LINES_PER_PAGE + TRAILER_LINES_PER_PAGE))
|
||||||
|
&& !matches.opt_present(NO_HEADER_TRAILER_OPTION);
|
||||||
|
|
||||||
|
|
||||||
Ok(OutputOptions {
|
Ok(OutputOptions {
|
||||||
number: numbering_options,
|
number: numbering_options,
|
||||||
header: header.to_string(),
|
header: header.to_string(),
|
||||||
|
@ -300,6 +347,9 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result<Ou
|
||||||
last_modified_time,
|
last_modified_time,
|
||||||
start_page,
|
start_page,
|
||||||
end_page,
|
end_page,
|
||||||
|
display_header: display_header_and_trailer,
|
||||||
|
display_trailer: display_header_and_trailer,
|
||||||
|
content_lines_per_page,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,10 +373,11 @@ fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
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 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
|
||||||
} else {
|
} else {
|
||||||
CONTENT_LINES_PER_PAGE
|
content_lines_per_page
|
||||||
};
|
};
|
||||||
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 {
|
||||||
|
@ -349,16 +400,24 @@ fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
|
||||||
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 start_page = options.as_ref().start_page.as_ref();
|
||||||
let last_page = options.as_ref().end_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()) &&
|
let is_within_print_range = (start_page.is_none() || page >= start_page.unwrap()) &&
|
||||||
(last_page.is_none() || page <= last_page.unwrap());
|
(last_page.is_none() || page <= last_page.unwrap());
|
||||||
if !is_within_print_range {
|
if !is_within_print_range {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
let header: Vec<String> = header_content(options, page);
|
let header: Vec<String> = if options.as_ref().display_header {
|
||||||
let trailer_content: Vec<String> = trailer_content();
|
header_content(options, page)
|
||||||
assert_eq!(lines.len() <= CONTENT_LINES_PER_PAGE, true, "Only {} lines of content allowed in a pr output page", CONTENT_LINES_PER_PAGE);
|
} else {
|
||||||
assert_eq!(header.len(), HEADER_LINES_PER_PAGE, "Only {} lines of content allowed in a pr header", HEADER_LINES_PER_PAGE);
|
Vec::new()
|
||||||
assert_eq!(trailer_content.len(), TRAILER_LINES_PER_PAGE, "Only {} lines of content allowed in a pr trailer", TRAILER_LINES_PER_PAGE);
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
let mut lines_written = 0;
|
let mut lines_written = 0;
|
||||||
|
@ -379,7 +438,7 @@ fn print_page(lines: &Vec<String>, options: &OutputOptions, page: &usize) -> Res
|
||||||
.map(|i| i.separator.to_string())
|
.map(|i| i.separator.to_string())
|
||||||
.unwrap_or(NumberingMode::default().separator);
|
.unwrap_or(NumberingMode::default().separator);
|
||||||
|
|
||||||
let prev_lines = CONTENT_LINES_PER_PAGE * (page - 1);
|
let prev_lines = content_lines_per_page * (page - 1);
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
for x in lines {
|
for x in lines {
|
||||||
if options.number.is_none() {
|
if options.number.is_none() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue