From 0098cfe5b7cc4b244b42b690632c418cc220f526 Mon Sep 17 00:00:00 2001 From: tilakpatidar Date: Fri, 14 Dec 2018 09:09:19 +0530 Subject: [PATCH] pr: add ColumnModeOptions and fix reading of input after page range is finished --- src/pr/pr.rs | 184 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 121 insertions(+), 63 deletions(-) diff --git a/src/pr/pr.rs b/src/pr/pr.rs index c95ffa45a..4b4632355 100644 --- a/src/pr/pr.rs +++ b/src/pr/pr.rs @@ -37,13 +37,17 @@ static NUMBERING_MODE_DEFAULT_WIDTH: usize = 5; static STRING_HEADER_OPTION: &str = "h"; static DOUBLE_SPACE_OPTION: &str = "d"; static NUMBERING_MODE_OPTION: &str = "n"; -static PAGE_RANGE_OPTION: &str = "page"; +static PAGE_RANGE_OPTION: &str = "pages"; static NO_HEADER_TRAILER_OPTION: &str = "t"; static PAGE_LENGTH_OPTION: &str = "l"; static SUPPRESS_PRINTING_ERROR: &str = "r"; static FORM_FEED_OPTION: &str = "F"; +static COLUMN_WIDTH_OPTION: &str = "w"; +static COLUMN_OPTION: &str = "column"; static FILE_STDIN: &str = "-"; static READ_BUFFER_SIZE: usize = 1024 * 64; +static DEFAULT_COLUMN_WIDTH: usize = 72; +static DEFAULT_COLUMN_SEPARATOR: &str = "\t"; struct OutputOptions { /// Line numbering mode @@ -59,6 +63,13 @@ struct OutputOptions { content_lines_per_page: usize, suppress_errors: bool, page_separator_char: String, + column_mode_options: Option, +} + +struct ColumnModeOptions { + width: usize, + columns: usize, + column_separator: String, } impl AsRef for OutputOptions { @@ -139,23 +150,19 @@ quick_error! { pub fn uumain(args: Vec) -> i32 { let mut opts = getopts::Options::new(); - opts.opt( + opts.optflagopt( "", PAGE_RANGE_OPTION, "Begin and stop printing with page FIRST_PAGE[:LAST_PAGE]", "FIRST_PAGE[:LAST_PAGE]", - HasArg::Yes, - Occur::Optional, ); - opts.opt( + opts.optopt( STRING_HEADER_OPTION, "header", "Use the string header to replace the file name \ in the header line.", - "STRING", - HasArg::Yes, - Occur::Optional, + "STRING" ); opts.opt( @@ -168,16 +175,14 @@ pub fn uumain(args: Vec) -> i32 { Occur::Optional, ); - opts.opt( + opts.optflagopt( NUMBERING_MODE_OPTION, "", "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 character) is given, it is appended to the line number to separate it from whatever follows. The default for char is a . Line numbers longer than width columns are truncated.", - "[char][width]", - HasArg::Yes, - Occur::Optional, + "[char][width]" ); opts.opt( @@ -219,6 +224,30 @@ pub fn uumain(args: Vec) -> i32 { 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("V", "version", "output version information and exit"); @@ -368,6 +397,24 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result()) { + Some(res) => res?, + _ => DEFAULT_COLUMN_WIDTH + }; + + let column_mode_options = match matches.opt_str(COLUMN_OPTION).map(|i| { + i.parse::() + }) { + Some(res) => { + Some(ColumnModeOptions { + columns: res?, + width: column_width, + column_separator: DEFAULT_COLUMN_SEPARATOR.to_string(), + }) + } + _ => None + }; + Ok(OutputOptions { number: numbering_options, header: header.to_string(), @@ -381,6 +428,7 @@ fn build_options(matches: &Matches, header: &String, path: &String) -> Result Result { let mut page: usize = 0; let mut buffered_content: Vec = Vec::new(); 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 { - content_lines_per_page / 2 + (content_lines_per_page / 2) * columns } else { - content_lines_per_page + content_lines_per_page * columns }; for line in BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines() { if i == lines_per_page { page = page + 1; i = 0; + if !_is_within_page_range(options, &page) { + return Ok(0) + } print_page(&buffered_content, options, &page)?; buffered_content = Vec::new(); } @@ -422,33 +474,26 @@ fn pr(path: &str, options: &OutputOptions) -> Result { } if i != 0 { + if !_is_within_page_range(options, &page) { + return Ok(0) + } page = page + 1; print_page(&buffered_content, options, &page)?; } + return Ok(0); } -fn print_page(lines: &Vec, options: &OutputOptions, page: &usize) -> Result { +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(); - 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(); - if !is_within_print_range { - return Ok(0); - } - let header: Vec = if options.as_ref().display_header { - header_content(options, page) - } else { - Vec::new() - }; + (start_page.is_none() || page >= start_page.unwrap()) && (last_page.is_none() || page <= last_page.unwrap()) +} - let trailer_content: Vec = if options.as_ref().display_trailer { - trailer_content() - } else { - Vec::new() - }; +fn print_page(lines: &Vec, options: &OutputOptions, page: &usize) -> Result { + let page_separator = options.as_ref().page_separator_char.as_bytes(); + let header: Vec = header_content(options, page); + let trailer_content: Vec = trailer_content(options); let out: &mut Stdout = &mut stdout(); let line_separator = options.as_ref().line_separator.as_bytes(); @@ -461,34 +506,8 @@ fn print_page(lines: &Vec, options: &OutputOptions, page: &usize) -> Res lines_written += 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); + lines_written += write_columns(lines, options, page_separator, out, line_separator, page)?; - 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() { let x: &String = trailer_content.get(index).unwrap(); out.write(x.as_bytes())?; @@ -503,6 +522,37 @@ fn print_page(lines: &Vec, options: &OutputOptions, page: &usize) -> Res Ok(lines_written) } +fn write_columns(lines: &Vec, options: &OutputOptions, page_separator: &[u8], out: &mut Stdout, line_separator: &[u8], page: &usize) -> Result { + 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 { let line_str = line_number.to_string(); 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 { - let first_line: String = format!("{} {} Page {}", options.last_modified_time, options.header, page); - vec!["".to_string(), "".to_string(), first_line, "".to_string(), "".to_string()] + if options.as_ref().display_header { + let first_line: String = format!("{} {} Page {}", options.last_modified_time, options.header, page); + vec!["".to_string(), "".to_string(), first_line, "".to_string(), "".to_string()] + } else { + Vec::new() + } } 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() } -fn trailer_content() -> Vec { - vec!["".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string()] +fn trailer_content(options: &OutputOptions) -> Vec { + if options.as_ref().display_trailer { + vec!["".to_string(), "".to_string(), "".to_string(), "".to_string(), "".to_string()] + } else { + Vec::new() + } } fn get_input_type(path: &str) -> Option {