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
16 changes: 3 additions & 13 deletions src/uu/mktemp/src/mktemp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
use clap::builder::{TypedValueParser, ValueParserFactory};
use clap::{Arg, ArgAction, ArgMatches, Command};
use uucore::display::{Quotable, println_verbatim};
use uucore::error::{FromIo, UError, UResult, UUsageError};
use uucore::error::{FromIo, UError, UResult};
use uucore::format_usage;
use uucore::translate;

Expand Down Expand Up @@ -381,7 +381,7 @@ impl ValueParserFactory for OptionalPathBufParser {

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args: Vec<_> = args.collect();
let args = uucore::clap_localization::prepare_args(&uu_app(), args);
let matches = uu_app().try_get_matches_from(&args).map_err(|e| {
use clap::error::{ContextKind, ContextValue, ErrorKind};
use uucore::clap_localization::handle_clap_error_with_exit_code;
Expand All @@ -393,7 +393,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
k == ContextKind::InvalidArg && v == &ContextValue::String("[template]".into())
}) =>
{
UUsageError::new(1, translate!("mktemp-error-too-many-templates"))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why ?

Box::new(MkTempError::TooManyTemplates) as Box<dyn UError>
}
_ => e.into(),
}
Expand All @@ -403,16 +403,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// application logic.
let options = Options::from(&matches);

if env::var_os("POSIXLY_CORRECT").is_some() {
// If POSIXLY_CORRECT was set, template MUST be the last argument.
if matches.contains_id(ARG_TEMPLATE) {
// Template argument was provided, check if was the last one.
if args.last().unwrap() != &options.template {
return Err(Box::new(MkTempError::TooManyTemplates));
}
}
}

let dry_run = options.dry_run;
let suppress_file_err = options.quiet;
let make_dir = options.directory;
Expand Down
2 changes: 1 addition & 1 deletion src/uu/rm/src/rm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ static ARG_FILES: &str = "files";

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let args: Vec<OsString> = args.collect();
let args: Vec<OsString> = uucore::clap_localization::prepare_args(&uu_app(), args);
let matches = uu_app()
.try_get_matches_from(args.iter())
.map_err(|e| handle_parse_error(e, &args))?;
Expand Down
3 changes: 2 additions & 1 deletion src/uu/tail/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,8 @@ fn parse_num(src: &str) -> Result<Signum, ParseSizeError> {

pub fn parse_args(args: impl uucore::Args) -> UResult<Settings> {
let args_vec: Vec<OsString> = args.collect();
let clap_args = uu_app().try_get_matches_from(args_vec.clone());
let prepared_args = uucore::clap_localization::prepare_args(&uu_app(), args_vec.clone());
let clap_args = uu_app().try_get_matches_from(prepared_args);
let clap_result = match clap_args {
// Kept for the caret in size diagnostics, which needs the value as
// typed.
Expand Down
1 change: 1 addition & 0 deletions src/uu/uniq/src/uniq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,7 @@ fn map_clap_errors(clap_error: Error) -> Box<dyn UError> {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let (args, skip_fields_old, skip_chars_old) = handle_obsolete(args);
let args = uucore::clap_localization::prepare_args(&uu_app(), args);

let matches = match uu_app().try_get_matches_from(args) {
Ok(matches) => matches,
Expand Down
237 changes: 236 additions & 1 deletion src/uucore/src/lib/mods/clap_localization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,8 @@ where
I: IntoIterator<Item = T>,
T: Into<OsString> + Clone,
{
cmd.try_get_matches_from(itr).map_err(|e| {
let args = prepare_args(&cmd, itr);
cmd.try_get_matches_from(args).map_err(|e| {
if e.exit_code() == 0 {
e.into() // Preserve help/version
} else {
Expand All @@ -484,6 +485,135 @@ where
})
}

fn opt_takes_value(arg: &clap::Arg) -> bool {
if !arg.get_action().takes_values() {
return false;
}
if let Some(num_args) = arg.get_num_args()
&& num_args.min_values() == 0
{
return false;
}
true
}

fn find_long_opt<'a>(cmd: &'a Command, name: &str) -> Option<&'a clap::Arg> {
for arg in cmd.get_arguments() {
if arg.get_long() == Some(name) {
return Some(arg);
}
if let Some(aliases) = arg.get_all_aliases()
&& aliases.contains(&name)
{
return Some(arg);
}
}
let matches: Vec<_> = cmd
.get_arguments()
.filter(|a| {
a.get_long().is_some_and(|l| l.starts_with(name))
|| a.get_all_aliases()
.is_some_and(|aliases| aliases.iter().any(|l| l.starts_with(name)))
})
.collect();
if matches.len() == 1 {
return Some(matches[0]);
}
None
}

fn find_short_opt(cmd: &Command, c: char) -> Option<&clap::Arg> {
for arg in cmd.get_arguments() {
if arg.get_short() == Some(c) {
return Some(arg);
}
if let Some(aliases) = arg.get_short_and_visible_aliases()
&& aliases.contains(&c)
{
return Some(arg);
}
if let Some(aliases) = arg.get_all_short_aliases()
&& aliases.contains(&c)
{
return Some(arg);
}
}
None
}

pub fn prepare_args<I, T>(cmd: &Command, itr: I) -> Vec<OsString>
where
I: IntoIterator<Item = T>,
T: Into<OsString>,
{
let mut args: Vec<OsString> = itr.into_iter().map(Into::into).collect();
if std::env::var_os("POSIXLY_CORRECT").is_none() {
return args;
}
let cmd_name = cmd.get_name();
if cmd_name == "join" || cmd_name == "pr" {
return args;
}
if args.len() <= 1 {
return args;
}

let mut i = 1;
while i < args.len() {
let Ok(arg_bytes) = crate::os_str_as_bytes(args[i].as_os_str()) else {
args.insert(i, OsString::from("--"));
return args;
};

if arg_bytes == b"--" {
return args;
}

if arg_bytes == b"-" || !arg_bytes.starts_with(b"-") {
args.insert(i, OsString::from("--"));
return args;
}

if arg_bytes.starts_with(b"--") {
let opt_bytes = &arg_bytes[2..];
if opt_bytes.contains(&b'=') {
i += 1;
} else if let Ok(opt_str) = std::str::from_utf8(opt_bytes) {
let takes_val = find_long_opt(cmd, opt_str).is_some_and(opt_takes_value);
if takes_val && i + 1 < args.len() {
i += 2;
} else {
i += 1;
}
} else {
i += 1;
}
} else {
let short_bytes = &arg_bytes[1..];
let mut consumed_next = false;
if let Ok(short_str) = std::str::from_utf8(short_bytes) {
let chars: Vec<char> = short_str.chars().collect();
for (idx, &c) in chars.iter().enumerate() {
if let Some(arg) = find_short_opt(cmd, c)
&& opt_takes_value(arg)
{
if idx + 1 == chars.len() && i + 1 < args.len() {
consumed_next = true;
}
break;
}
}
}
if consumed_next {
i += 2;
} else {
i += 1;
}
}
}
args
}

/// Handles a clap error directly with a custom exit code.
///
/// This function processes a clap error and exits the program with the specified
Expand Down Expand Up @@ -735,5 +865,110 @@ mod tests {
}
}
}

#[test]
fn test_prepare_args_posixly_correct() {
use std::env;
let cmd = Command::new("test")
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.action(clap::ArgAction::SetTrue),
)
.arg(Arg::new("width").short('w').long("width").value_name("NUM"))
.arg(
Arg::new("files")
.action(clap::ArgAction::Append)
.num_args(1..),
);

unsafe {
env::remove_var("POSIXLY_CORRECT");
}
let args = vec!["test", "file", "-v"];
let prepared = prepare_args(&cmd, args.clone());
assert_eq!(
prepared,
args.iter().map(OsString::from).collect::<Vec<_>>()
);

unsafe {
env::set_var("POSIXLY_CORRECT", "1");
}
let prepared = prepare_args(&cmd, vec!["test", "file", "-v"]);
assert_eq!(
prepared,
vec![
OsString::from("test"),
OsString::from("--"),
OsString::from("file"),
OsString::from("-v")
]
);

let prepared = prepare_args(&cmd, vec!["test", "-w", "80", "file", "-v"]);
assert_eq!(
prepared,
vec![
OsString::from("test"),
OsString::from("-w"),
OsString::from("80"),
OsString::from("--"),
OsString::from("file"),
OsString::from("-v")
]
);

let prepared = prepare_args(&cmd, vec!["test", "-w80", "file", "-v"]);
assert_eq!(
prepared,
vec![
OsString::from("test"),
OsString::from("-w80"),
OsString::from("--"),
OsString::from("file"),
OsString::from("-v")
]
);

let prepared = prepare_args(&cmd, vec!["test", "--width=80", "file", "-v"]);
assert_eq!(
prepared,
vec![
OsString::from("test"),
OsString::from("--width=80"),
OsString::from("--"),
OsString::from("file"),
OsString::from("-v")
]
);

let prepared = prepare_args(&cmd, vec!["test", "--", "file", "-v"]);
assert_eq!(
prepared,
vec![
OsString::from("test"),
OsString::from("--"),
OsString::from("file"),
OsString::from("-v")
]
);

let join_cmd = Command::new("join");
let prepared_join = prepare_args(&join_cmd, vec!["join", "file", "-v"]);
assert_eq!(
prepared_join,
vec![
OsString::from("join"),
OsString::from("file"),
OsString::from("-v")
]
);

unsafe {
env::remove_var("POSIXLY_CORRECT");
}
}
}
/* spell-checker: enable */
14 changes: 14 additions & 0 deletions tests/by-util/test_cat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -957,3 +957,17 @@ fn test_cat_eintr_handling() {
// Verify that the interruption was encountered and handled
assert_eq!(*interrupt_count.lock().unwrap(), 1);
}

#[test]
fn test_posixly_correct_options_after_operands() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("data.txt", "hello\n");

ucmd.env("POSIXLY_CORRECT", "1")
.arg("data.txt")
.arg("-n")
.fails()
.code_is(1)
.stdout_is("hello\n")
.stderr_contains("-n");
}
13 changes: 13 additions & 0 deletions tests/by-util/test_du.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2913,4 +2913,17 @@ du: invalid suffix in --block-size argument '1fb'
.fails_with_code(1)
.stderr_is("du: invalid suffix in --block-size argument '1fb'\n");
}

#[test]
fn test_posixly_correct_options_after_operands() {
let (at, mut ucmd) = uutests::at_and_ucmd!();
at.mkdir("dir");

ucmd.env("POSIXLY_CORRECT", "1")
.arg("dir")
.arg("-s")
.fails()
.code_is(1)
.stderr_contains("-s");
}
}
14 changes: 14 additions & 0 deletions tests/by-util/test_ls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8064,4 +8064,18 @@ ls: invalid --block-size argument '1fb'
.fails_with_code(2)
.stderr_is("ls: invalid --block-size argument '1fb'\n");
}

#[test]
fn test_posixly_correct_options_after_operands() {
let (at, mut ucmd) = uutests::at_and_ucmd!();
at.touch("file");

ucmd.env("POSIXLY_CORRECT", "1")
.arg("file")
.arg("-l")
.fails()
.code_is(2)
.stdout_is("file\n")
.stderr_contains("-l");
}
}
Loading
Loading