From d8d635d01429afaae8cf2998289b0bbf7c5416cd Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 6 Jan 2025 09:51:41 +0100 Subject: [PATCH] echo: add support for POSIXLY_CORRECT --- src/uu/echo/src/echo.rs | 56 ++++++++++++++++++++------------------ tests/by-util/test_echo.rs | 52 +++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/uu/echo/src/echo.rs b/src/uu/echo/src/echo.rs index 097e4f2e9..228b5a0c1 100644 --- a/src/uu/echo/src/echo.rs +++ b/src/uu/echo/src/echo.rs @@ -4,8 +4,8 @@ // file that was distributed with this source code. use clap::builder::ValueParser; -use clap::parser::ValuesRef; -use clap::{crate_version, Arg, ArgAction, Command}; +use clap::{crate_version, Arg, ArgAction, ArgMatches, Command}; +use std::env; use std::ffi::{OsStr, OsString}; use std::io::{self, StdoutLock, Write}; use std::iter::Peekable; @@ -272,35 +272,37 @@ fn handle_double_hyphens(args: impl uucore::Args) -> impl uucore::Args { result.into_iter() } +fn collect_args(matches: &ArgMatches) -> Vec { + matches + .get_many::(options::STRING) + .map_or_else(Vec::new, |values| values.cloned().collect()) +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let matches = uu_app().get_matches_from(handle_double_hyphens(args)); + let is_posixly_correct = env::var("POSIXLY_CORRECT").is_ok(); - // TODO - // "If the POSIXLY_CORRECT environment variable is set, then when echo’s first argument is not -n it outputs option-like arguments instead of treating them as options." - // https://www.gnu.org/software/coreutils/manual/html_node/echo-invocation.html + let (args, trailing_newline, escaped) = if is_posixly_correct { + let mut args_iter = args.skip(1).peekable(); - let trailing_newline = !matches.get_flag(options::NO_NEWLINE); - let escaped = matches.get_flag(options::ENABLE_BACKSLASH_ESCAPE); + if args_iter.peek() == Some(&OsString::from("-n")) { + let matches = uu_app().get_matches_from(handle_double_hyphens(args_iter)); + let args = collect_args(&matches); + (args, false, true) + } else { + let args: Vec<_> = args_iter.collect(); + (args, true, true) + } + } else { + let matches = uu_app().get_matches_from(handle_double_hyphens(args.into_iter())); + let trailing_newline = !matches.get_flag(options::NO_NEWLINE); + let escaped = matches.get_flag(options::ENABLE_BACKSLASH_ESCAPE); + let args = collect_args(&matches); + (args, trailing_newline, escaped) + }; let mut stdout_lock = io::stdout().lock(); - - match matches.get_many::(options::STRING) { - Some(arguments_after_options) => { - execute( - &mut stdout_lock, - trailing_newline, - escaped, - arguments_after_options, - )?; - } - None => { - // No strings to print, so just handle newline setting - if trailing_newline { - stdout_lock.write_all(b"\n")?; - } - } - } + execute(&mut stdout_lock, args, trailing_newline, escaped)?; Ok(()) } @@ -348,11 +350,11 @@ pub fn uu_app() -> Command { fn execute( stdout_lock: &mut StdoutLock, + arguments_after_options: Vec, trailing_newline: bool, escaped: bool, - arguments_after_options: ValuesRef<'_, OsString>, ) -> UResult<()> { - for (i, input) in arguments_after_options.enumerate() { + for (i, input) in arguments_after_options.into_iter().enumerate() { let Some(bytes) = bytes_from_os_string(input.as_os_str()) else { return Err(USimpleError::new( 1, diff --git a/tests/by-util/test_echo.rs b/tests/by-util/test_echo.rs index dd6b412a4..d4430d056 100644 --- a/tests/by-util/test_echo.rs +++ b/tests/by-util/test_echo.rs @@ -390,3 +390,55 @@ fn slash_eight_off_by_one() { .succeeds() .stdout_only(r"\8"); } + +mod posixly_correct { + use super::*; + + #[test] + fn ignore_options() { + for arg in ["--help", "--version", "-E -n 'foo'", "-nE 'foo'"] { + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg(arg) + .succeeds() + .stdout_only(format!("{arg}\n")); + } + } + + #[test] + fn process_n_option() { + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .args(&["-n", "foo"]) + .succeeds() + .stdout_only("foo"); + + // ignore -E & process escapes + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .args(&["-n", "-E", "foo\\cbar"]) + .succeeds() + .stdout_only("foo"); + } + + #[test] + fn process_escapes() { + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg("foo\\n") + .succeeds() + .stdout_only("foo\n\n"); + + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg("foo\\tbar") + .succeeds() + .stdout_only("foo\tbar\n"); + + new_ucmd!() + .env("POSIXLY_CORRECT", "1") + .arg("foo\\ctbar") + .succeeds() + .stdout_only("foo"); + } +}