Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/uu/pr/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,14 @@ pr-help-indent =
pr-help-join-lines =
merge full lines, turns off -W line truncation, no column
alignment, --sep-string[=STRING] sets separators
pr-help-expand-tabs = expand input CHARs (TABs) to tab WIDTH (8)
pr-help-help = Print help information

# Page header text
pr-page = Page

pr-try-help-message = Try 'pr --help' for more information.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message in other utilities we get from using the UError from uucore, it just wraps the error message with this for each utility. For example this error message: https://github.com/uutils/coreutils/blob/main/src/uu/stty/src/stty.rs#L447 is wrapped with: "Try "stty --help" for more imformation."


# Error messages
pr-error-reading-input = pr: Reading from input {$file} gave error
pr-error-unknown-filetype = pr: {$file}: unknown filetype
Expand All @@ -98,3 +101,4 @@ pr-error-no-such-file = pr: cannot open {$file}, No such file or directory
pr-error-column-merge-conflict = cannot specify number of columns when printing in parallel
pr-error-across-merge-conflict = cannot specify both printing across and printing in parallel
pr-error-invalid-pages-range = invalid --pages argument '{$start}:{$end}'
pr-error-invalid-expand-tab-argument='-e' extra characters or invalid number in the argument: ‘{$arg}’
4 changes: 4 additions & 0 deletions src/uu/pr/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,14 @@ pr-help-indent =
pr-help-join-lines =
fusionner les lignes complètes, désactive la troncature de ligne -W, aucun
alignement de colonne, --sep-string[=CHAÎNE] définit les séparateurs
pr-help-expand-tabs = convertir les CHARs d'entrée (TABs) en largeur de tabulation WIDTH (8)
pr-help-help = Afficher les informations d'aide

# Texte d'en-tête de page
pr-page = Page

pr-try-help-message = Essayez 'pr --help' pour plus d'informations.

# Messages d'erreur
pr-error-reading-input = pr : La lecture depuis l'entrée {$file} a donné une erreur
pr-error-unknown-filetype = pr : {$file} : type de fichier inconnu
Expand All @@ -97,3 +100,4 @@ pr-error-no-such-file = pr : impossible d'ouvrir {$file}, Aucun fichier ou répe
pr-error-column-merge-conflict = impossible de spécifier le nombre de colonnes lors de l'impression en parallèle
pr-error-across-merge-conflict = impossible de spécifier à la fois l'impression transversale et l'impression en parallèle
pr-error-invalid-pages-range = argument --pages invalide '{$start}:{$end}'
pr-error-invalid-expand-tab-argument= Caractères supplémentaires ou nombre invalide dans l'argument de '-e': '{$arg}'
97 changes: 93 additions & 4 deletions src/uu/pr/src/pr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ mod options {
pub const JOIN_LINES: &str = "join-lines";
pub const HELP: &str = "help";
pub const FILES: &str = "files";
pub const EXPAND_TABS: &str = "expand-tabs";
}

struct OutputOptions {
Expand All @@ -80,6 +81,7 @@ struct OutputOptions {
join_lines: bool,
col_sep_for_printing: String,
line_width: Option<usize>,
expand_tabs: Option<ExpandTabsOptions>,
}

struct FileLine {
Expand All @@ -105,6 +107,21 @@ struct NumberingMode {
first_number: usize,
}

#[derive(Debug)]
struct ExpandTabsOptions {
input_char: char,
width: i32,
}

impl Default for ExpandTabsOptions {
fn default() -> Self {
Self {
width: 8,
input_char: TAB,
}
}
}

impl Default for NumberingMode {
fn default() -> Self {
Self {
Expand Down Expand Up @@ -328,6 +345,14 @@ pub fn uu_app() -> Command {
.action(ArgAction::Append)
.value_hint(clap::ValueHint::FilePath),
)
.arg(
Arg::new(options::EXPAND_TABS)
.long(options::EXPAND_TABS)
.short('e')
.num_args(1)
.value_name("[CHAR][WIDTH]")
.help(translate!("pr-help-expand-tabs")),
)
}

#[uucore::main]
Expand Down Expand Up @@ -392,6 +417,7 @@ fn recreate_arguments(args: &[String]) -> Vec<String> {
let column_page_option = Regex::new(r"^[-+]\d+.*").unwrap();
let num_regex = Regex::new(r"^[^-]\d*$").unwrap();
let n_regex = Regex::new(r"^-n\s*$").unwrap();
let e_regex = Regex::new(r"^-e").unwrap();
let mut arguments = args.to_owned();
let num_option = args.iter().find_position(|x| n_regex.is_match(x.trim()));
if let Some((pos, _value)) = num_option {
Expand All @@ -404,6 +430,17 @@ fn recreate_arguments(args: &[String]) -> Vec<String> {
}
}

// To ensure not to accidentally delete the next argument after a short flag for -e we insert
// the default values for the -e flag is '-e' is present without direct arguments.
let expand_tabs_option = arguments
.iter()
.find_position(|x| e_regex.is_match(x.trim()));
if let Some((pos, value)) = expand_tabs_option {
if value.trim().len() <= 2 {
arguments[pos] = "-e\t8".to_string();
}
}

arguments
.into_iter()
.filter(|i| !column_page_option.is_match(i))
Expand Down Expand Up @@ -525,6 +562,26 @@ fn build_options(
}
});

let expand_tabs = matches
.get_one::<String>(options::EXPAND_TABS)
.map(|s| {
s.chars().next().map_or(Ok(ExpandTabsOptions::default()), |c| {
if c.is_ascii_digit() {
s
.parse()
.map_err(|_e| PrError::EncounteredErrors { msg: format!("{}\n{}", translate!("pr-error-invalid-expand-tab-argument", "arg" => s), translate!("pr-try-help-message")) })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.map(|width| ExpandTabsOptions{input_char: TAB, width})
} else if s.len() > 1 {
s[1..]
.parse()
.map_err(|_e| PrError::EncounteredErrors { msg: format!("{}\n{}", translate!("pr-error-invalid-expand-tab-argument", "arg" => &s[1..]), translate!("pr-try-help-message")) })
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.map(|width| ExpandTabsOptions{input_char: c, width})
} else {
Ok(ExpandTabsOptions{input_char: c, width: 8})
}
})
}).transpose()?;

let double_space = matches.get_flag(options::DOUBLE_SPACE);

let content_line_separator = if double_space {
Expand Down Expand Up @@ -761,6 +818,7 @@ fn build_options(
join_lines,
col_sep_for_printing,
line_width,
expand_tabs,
})
}

Expand Down Expand Up @@ -807,7 +865,10 @@ fn open(path: &str) -> Result<Box<dyn Read>, PrError> {
)
}

fn split_lines_if_form_feed(file_content: Result<String, std::io::Error>) -> Vec<FileLine> {
fn split_lines_if_form_feed(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit tricky to review from the context that when trying to see if the implementation of -e is correct I don't have the baseline that the other aspects of the pr utility is working correctly.

I would personally just try to implement the handler first and then making sure that the base case for pr actually produces the same output as pr before implementing the logic for the many flags and options that can be provided.

file_content: Result<String, std::io::Error>,
expand_options: Option<&ExpandTabsOptions>,
) -> Vec<FileLine> {
file_content.map_or_else(
|e| {
vec![FileLine {
Expand All @@ -832,7 +893,14 @@ fn split_lines_if_form_feed(file_content: Result<String, std::io::Error>) -> Vec
});
chunk.clear();
}
chunk.push(*byte);

// The -e flag determines that an encountered input char (TAB) shall be expanded.
// If the -e flag was not set we pass the byte through unchanged.
match expand_options {
Some(expand_options) => apply_expand_tab(&mut chunk, *byte, expand_options),
None => chunk.push(*byte),
}

f_occurred = 0;
}
}
Expand All @@ -848,6 +916,27 @@ fn split_lines_if_form_feed(file_content: Result<String, std::io::Error>) -> Vec
)
}

fn apply_expand_tab(chunk: &mut Vec<u8>, byte: u8, expand_options: &ExpandTabsOptions) {
if byte == expand_options.input_char as u8 {
// If the byte encountered is the input char we use width to calculate
// the amount of spaces needed (if no input char given we stored '\t'
// in our struct)
let spaces_needed =
expand_options.width as usize - (chunk.len() % expand_options.width as usize);
chunk.resize(chunk.len() + spaces_needed, b' ');
} else if byte == TAB as u8 {
// If a byte got passed to the -e flag (eg -ea1) which is not '\t' GNU
// still expands it but does not use an optionally given width parameter
// but does the '\t' expansion with the default value (8)
let spaces_needed = 8 - (chunk.len() % 8);
chunk.resize(chunk.len() + spaces_needed, b' ');
} else {
// This arm means the byte is neither '\t' nor the bytes to be
// expanded
chunk.push(byte);
}
}

fn pr(path: &str, options: &OutputOptions) -> Result<i32, PrError> {
let lines = BufReader::with_capacity(READ_BUFFER_SIZE, open(path)?).lines();

Expand All @@ -866,15 +955,15 @@ fn read_stream_and_create_pages(
options: &OutputOptions,
lines: Lines<BufReader<Box<dyn Read>>>,
file_id: usize,
) -> Box<dyn Iterator<Item = (usize, Vec<FileLine>)>> {
) -> Box<dyn Iterator<Item = (usize, Vec<FileLine>)> + '_> {
let start_page = options.start_page;
let start_line_number = get_start_line_number(options);
let last_page = options.end_page;
let lines_needed_per_page = lines_to_read_for_page(options);

Box::new(
lines
.flat_map(split_lines_if_form_feed)
.flat_map(|s| split_lines_if_form_feed(s, options.expand_tabs.as_ref()))
.enumerate()
.map(move |(i, line)| FileLine {
line_number: i + start_line_number,
Expand Down
Loading
Loading