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
37 changes: 36 additions & 1 deletion src/uu/expand/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,11 +199,42 @@ impl Options {

/// Preprocess command line arguments and expand shortcuts. For example, "-7" is expanded to
/// "--tabs=7" and "-1,3" to "--tabs=1 --tabs=3".
/// Whether `arg` is a `-t`/`--tabs` spelling that takes the *next* argument as
/// its value, so that argument is a tab list rather than an obsolete `-N`.
fn takes_tabs_value(arg: &str) -> bool {
if let Some(long) = arg.strip_prefix("--") {
!long.is_empty() && !long.contains('=') && options::TABS.starts_with(long)
} else if let Some(short) = arg.strip_prefix('-') {
// Only a trailing `t` takes the next argument; in `-t8` the value is
// attached. A bare `-` is stdin and leaves nothing to index.
!short.is_empty() && short.find('t') == Some(short.len() - 1)
} else {
false
}
}

fn expand_shortcuts(args: Vec<OsString>) -> Vec<OsString> {
let mut processed_args = Vec::with_capacity(args.len());

let mut expecting_tabs = false;
let mut end_of_options = false;

for arg in args {
if let Some(arg) = arg.to_str()
// `-1` is the obsolete spelling of `--tabs=1`, but only where an
// option is expected. As the value of `-t` it is a (bad) tab list that
// has to reach the tab-list check, and past `--` it is a file name.
let is_operand = end_of_options || expecting_tabs;
if let Some(text) = arg.to_str() {
if is_operand {
expecting_tabs = false;
} else if text == "--" {
end_of_options = true;
} else {
expecting_tabs = takes_tabs_value(text);
}
}
if !is_operand
&& let Some(arg) = arg.to_str()
&& arg.starts_with('-')
&& arg[1..].chars().all(is_digit_or_comma)
{
Expand Down Expand Up @@ -248,6 +279,10 @@ pub fn uu_app() -> Command {
.long(options::TABS)
.short('t')
.value_name("N, LIST")
// A tab list may start with a hyphen. It is not valid, but the
// check below reports it far better than clap does, so it has to
// reach that check rather than be taken for an option.
.allow_hyphen_values(true)
.action(ArgAction::Append)
.help(translate!("expand-help-tabs")),
)
Expand Down
36 changes: 35 additions & 1 deletion src/uu/unexpand/src/unexpand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,44 @@ fn is_digit_or_comma(c: char) -> bool {
/// Preprocess command line arguments and expand shortcuts. For example, "-7" is expanded to
/// "--tabs=7 --first-only" and "-1,3" to "--tabs=1 --tabs=3 --first-only". However, if "-a" or
/// "--all" is provided, "--first-only" is omitted.
/// Whether `arg` is a `-t`/`--tabs` spelling that takes the *next* argument as
/// its value, so that argument is a tab list rather than an obsolete `-N`.
fn takes_tabs_value(arg: &str) -> bool {
if let Some(long) = arg.strip_prefix("--") {
!long.is_empty() && !long.contains('=') && options::TABS.starts_with(long)
} else if let Some(short) = arg.strip_prefix('-') {
// Only a trailing `t` takes the next argument; in `-t8` the value is
// attached. A bare `-` is stdin and leaves nothing to index.
!short.is_empty() && short.find('t') == Some(short.len() - 1)
} else {
false
}
}

fn expand_shortcuts(args: Vec<OsString>) -> Vec<OsString> {
let mut processed_args = Vec::with_capacity(args.len());
let mut is_all_arg_provided = false;
let mut has_shortcuts = false;

let mut expecting_tabs = false;
let mut end_of_options = false;

for arg in args {
// `-1` is the obsolete spelling of `--tabs=1`, but only where an
// option is expected. As the value of `-t` it is a (bad) tab list that
// has to reach the tab-list check, and past `--` it is a file name.
let is_operand = end_of_options || expecting_tabs;
if let Some(text) = arg.to_str() {
if is_operand {
expecting_tabs = false;
} else if text == "--" {
end_of_options = true;
} else {
expecting_tabs = takes_tabs_value(text);
}
}
if let Some(arg) = arg.to_str() {
if arg.starts_with('-') && arg[1..].chars().all(is_digit_or_comma) {
if !is_operand && arg.starts_with('-') && arg[1..].chars().all(is_digit_or_comma) {
arg[1..]
.split(',')
.filter(|s| !s.is_empty())
Expand Down Expand Up @@ -268,6 +298,10 @@ pub fn uu_app() -> Command {
Arg::new(options::TABS)
.short('t')
.long(options::TABS)
// A tab list may start with a hyphen. It is not valid, but the
// check below reports it far better than clap does, so it has
// to reach that check rather than be taken for an option.
.allow_hyphen_values(true)
.help(translate!("unexpand-help-tabs"))
.action(ArgAction::Append)
.value_name("N, LIST"),
Expand Down
16 changes: 16 additions & 0 deletions tests/by-util/test_expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,19 @@ fn test_buffered_reads_new_line_no_tabs_in_first_chunk() {
.succeeds()
.stdout_is_fixture("new_line_in_chunk_expected.txt");
}

#[test]
fn test_tab_list_starting_with_hyphen() {
// `-1` is the obsolete spelling of `--tabs=1`, but as the value of -t it
// is a tab list. It used to be rewritten to `--tabs=1` before the check,
// which then complained about `'--tabs=1'` rather than what was typed.
for arg in ["-1", "-0"] {
new_ucmd!()
.args(&["-t", arg])
.fails()
.stderr_contains(format!("tab size contains invalid character(s): '{arg}'"));
}

// The obsolete spelling still works where an option is expected.
new_ucmd!().arg("-4").pipe_in("\tx\n").succeeds();
}
14 changes: 14 additions & 0 deletions tests/by-util/test_unexpand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,3 +544,17 @@ fn test_buffered_read_edgecase_behaviour() {
.stdout_only(String::from_utf8(expected).unwrap());
}
}

#[test]
fn test_tab_list_starting_with_hyphen() {
// See the matching expand test: `-1` after -t is a tab list, not the
// obsolete `--tabs=1` spelling.
for arg in ["-1", "-0"] {
new_ucmd!()
.args(&["-t", arg])
.fails()
.stderr_contains(format!("tab size contains invalid character(s): '{arg}'"));
}

new_ucmd!().arg("-4").pipe_in(" x\n").succeeds();
}
Loading