mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
echo: cleanup
This commit is contained in:
parent
68bc7bbd52
commit
f1436f3949
2 changed files with 131 additions and 96 deletions
|
@ -626,6 +626,7 @@ unused_qualifications = "warn"
|
||||||
all = { level = "warn", priority = -1 }
|
all = { level = "warn", priority = -1 }
|
||||||
cargo = { level = "warn", priority = -1 }
|
cargo = { level = "warn", priority = -1 }
|
||||||
pedantic = { level = "warn", priority = -1 }
|
pedantic = { level = "warn", priority = -1 }
|
||||||
|
match_bool = "allow" # 8310
|
||||||
cargo_common_metadata = "allow" # 3240
|
cargo_common_metadata = "allow" # 3240
|
||||||
multiple_crate_versions = "allow" # 2314
|
multiple_crate_versions = "allow" # 2314
|
||||||
missing_errors_doc = "allow" # 1504
|
missing_errors_doc = "allow" # 1504
|
||||||
|
|
|
@ -21,109 +21,149 @@ mod options {
|
||||||
pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
|
pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Holds the options for echo command:
|
/// Options for the echo command.
|
||||||
/// -n (disable newline)
|
#[derive(Debug, Clone, Copy)]
|
||||||
/// -e/-E (escape handling),
|
struct Options {
|
||||||
struct EchoOptions {
|
/// Whether the output should have a trailing newline.
|
||||||
/// -n flag option: if true, output a trailing newline (-n disables it)
|
///
|
||||||
/// Default: true
|
/// True by default. `-n` disables it.
|
||||||
pub trailing_newline: bool,
|
pub trailing_newline: bool,
|
||||||
|
|
||||||
/// -e enables escape interpretation, -E disables it
|
/// Whether given string literals should be parsed for
|
||||||
/// Default: false (escape interpretation disabled)
|
/// escape characters.
|
||||||
|
///
|
||||||
|
/// False by default, can be enabled with `-e`. Always true if
|
||||||
|
/// `POSIXLY_CORRECT` (cannot be disabled with `-E`).
|
||||||
pub escape: bool,
|
pub escape: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if an argument is a valid echo flag
|
impl Default for Options {
|
||||||
/// Returns true if valid echo flag found
|
fn default() -> Self {
|
||||||
fn is_echo_flag(arg: &OsString, echo_options: &mut EchoOptions) -> bool {
|
Self {
|
||||||
let bytes = arg.as_encoded_bytes();
|
trailing_newline: true,
|
||||||
if bytes.first() == Some(&b'-') && arg != "-" {
|
escape: false,
|
||||||
// we initialize our local variables to the "current" options so we don't override
|
}
|
||||||
// previous found flags
|
}
|
||||||
let mut escape = echo_options.escape;
|
}
|
||||||
let mut trailing_newline = echo_options.trailing_newline;
|
|
||||||
|
|
||||||
// Process characters after the '-'
|
impl Options {
|
||||||
for c in &bytes[1..] {
|
fn posixly_correct_default() -> Self {
|
||||||
|
Self {
|
||||||
|
trailing_newline: true,
|
||||||
|
escape: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if an argument is a valid echo flag, and if
|
||||||
|
/// it is records the changes in [`Options`].
|
||||||
|
fn is_flag(arg: &OsStr, options: &mut Options) -> bool {
|
||||||
|
let arg = arg.as_encoded_bytes();
|
||||||
|
|
||||||
|
if arg.first() != Some(&b'-') || arg == b"-" {
|
||||||
|
// Argument doesn't start with '-' or is '-' => not a flag.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't modify the given options until after
|
||||||
|
// the loop because there is a chance the flag isn't
|
||||||
|
// valid after all & shouldn't affect the options.
|
||||||
|
let mut options_: Options = *options;
|
||||||
|
|
||||||
|
// Skip the '-' when processing characters.
|
||||||
|
for c in &arg[1..] {
|
||||||
match c {
|
match c {
|
||||||
b'e' => escape = true,
|
b'e' => options_.escape = true,
|
||||||
b'E' => escape = false,
|
b'E' => options_.escape = false,
|
||||||
b'n' => trailing_newline = false,
|
b'n' => options_.trailing_newline = false,
|
||||||
// if there is any char in an argument starting with '-' that doesn't match e/E/n
|
|
||||||
// present means that this argument is not a flag
|
// If there is any character in an supposed flag
|
||||||
|
// that is not a valid flag character, it is not
|
||||||
|
// a flag.
|
||||||
|
//
|
||||||
|
// "-eeEnEe" => is a flag.
|
||||||
|
// "-eeBne" => not a flag, short circuit at the B.
|
||||||
_ => return false,
|
_ => return false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we only override the options with flags being found once we parsed the whole argument
|
// We are now sure that the argument is a
|
||||||
echo_options.escape = escape;
|
// flag, and can apply the modified options.
|
||||||
echo_options.trailing_newline = trailing_newline;
|
*options = options_;
|
||||||
return true;
|
true
|
||||||
}
|
|
||||||
|
|
||||||
// argument doesn't start with '-' or is "-" => no flag
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes command line arguments, separating flags from normal arguments
|
/// Processes command line arguments, separating flags from normal arguments.
|
||||||
/// Returns:
|
///
|
||||||
/// - Vector of non-flag arguments
|
/// # Returns
|
||||||
/// - `trailing_newline`: whether to print a trailing newline
|
///
|
||||||
/// - `escape`: whether to process escape sequences
|
/// - Vector of non-flag arguments.
|
||||||
fn filter_echo_flags(args: impl uucore::Args) -> (Vec<OsString>, bool, bool) {
|
/// - [`Options`], describing how teh arguments should be interpreted.
|
||||||
let mut result = Vec::new();
|
fn filter_flags(mut args: impl uucore::Args) -> (Vec<OsString>, Options) {
|
||||||
let mut echo_options = EchoOptions {
|
let mut arguments = Vec::with_capacity(args.size_hint().0);
|
||||||
trailing_newline: true,
|
let mut options = Options::default();
|
||||||
escape: false,
|
|
||||||
};
|
|
||||||
let mut args_iter = args.into_iter();
|
|
||||||
|
|
||||||
// Process arguments until first non-flag is found
|
// Process arguments until first non-flag is found.
|
||||||
for arg in &mut args_iter {
|
for arg in &mut args {
|
||||||
// we parse flags and store options found in "echo_option". First is_echo_flag
|
// We parse flags and aggregate the options in `options`.
|
||||||
// call to return false will break the loop and we will collect the remaining arguments
|
// First call to `is_echo_flag` to return false will break the loop.
|
||||||
if !is_echo_flag(&arg, &mut echo_options) {
|
if !is_flag(&arg, &mut options) {
|
||||||
// First non-flag argument stops flag processing
|
// Not a flag. Can break out of flag-processing loop.
|
||||||
result.push(arg);
|
// Don't forget to push it to the arguments too.
|
||||||
|
arguments.push(arg);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Collect remaining arguments
|
|
||||||
for arg in args_iter {
|
// Collect remaining non-flag arguments.
|
||||||
result.push(arg);
|
arguments.extend(args);
|
||||||
}
|
|
||||||
(result, echo_options.trailing_newline, echo_options.escape)
|
(arguments, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
// args[0] is the name of the binary.
|
||||||
|
let mut args = args.skip(1).peekable();
|
||||||
|
|
||||||
// Check POSIX compatibility mode
|
// Check POSIX compatibility mode
|
||||||
|
//
|
||||||
|
// From the GNU manual, on what it should do:
|
||||||
|
//
|
||||||
|
// > 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. For example, echo -ne hello
|
||||||
|
// > outputs ‘-ne hello’ instead of plain ‘hello’. Also backslash
|
||||||
|
// > escapes are always enabled. To echo the string ‘-n’, one of the
|
||||||
|
// > characters can be escaped in either octal or hexadecimal
|
||||||
|
// > representation. For example, echo -e '\x2dn'.
|
||||||
let is_posixly_correct = env::var_os("POSIXLY_CORRECT").is_some();
|
let is_posixly_correct = env::var_os("POSIXLY_CORRECT").is_some();
|
||||||
|
|
||||||
let args_iter = args.skip(1);
|
let (args, options) = match is_posixly_correct {
|
||||||
let (args, trailing_newline, escaped) = if is_posixly_correct {
|
|
||||||
let mut args_iter = args_iter.peekable();
|
|
||||||
|
|
||||||
if args_iter.peek() == Some(&OsString::from("-n")) {
|
|
||||||
// if POSIXLY_CORRECT is set and the first argument is the "-n" flag
|
|
||||||
// we filter flags normally but 'escaped' is activated nonetheless
|
|
||||||
let (args, _, _) = filter_echo_flags(args_iter);
|
|
||||||
(args, false, true)
|
|
||||||
} else {
|
|
||||||
// if POSIXLY_CORRECT is set and the first argument is not the "-n" flag
|
|
||||||
// we just collect all arguments as every argument is considered an argument
|
|
||||||
let args: Vec<OsString> = args_iter.collect();
|
|
||||||
(args, true, true)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if POSIXLY_CORRECT is not set we filter the flags normally
|
// if POSIXLY_CORRECT is not set we filter the flags normally
|
||||||
let (args, trailing_newline, escaped) = filter_echo_flags(args_iter);
|
false => filter_flags(args),
|
||||||
(args, trailing_newline, escaped)
|
|
||||||
|
true if args.peek().is_some_and(|arg| arg == "-n") => {
|
||||||
|
// if POSIXLY_CORRECT is set and the first argument is the "-n" flag
|
||||||
|
// we filter flags normally but 'escaped' is activated nonetheless.
|
||||||
|
let (args, _) = filter_flags(args);
|
||||||
|
(
|
||||||
|
args,
|
||||||
|
Options {
|
||||||
|
trailing_newline: false,
|
||||||
|
..Options::posixly_correct_default()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
true => {
|
||||||
|
// if POSIXLY_CORRECT is set and the first argument is not the "-n" flag
|
||||||
|
// we just collect all arguments as every argument is considered an argument.
|
||||||
|
(args.collect(), Options::posixly_correct_default())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut stdout_lock = io::stdout().lock();
|
execute(&mut io::stdout().lock(), args, options)?;
|
||||||
execute(&mut stdout_lock, args, trailing_newline, escaped)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -169,34 +209,29 @@ pub fn uu_app() -> Command {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(
|
fn execute(stdout: &mut StdoutLock, args: Vec<OsString>, options: Options) -> UResult<()> {
|
||||||
stdout_lock: &mut StdoutLock,
|
for (i, arg) in args.into_iter().enumerate() {
|
||||||
arguments_after_options: Vec<OsString>,
|
let Some(bytes) = bytes_from_os_string(arg.as_os_str()) else {
|
||||||
trailing_newline: bool,
|
|
||||||
escaped: bool,
|
|
||||||
) -> UResult<()> {
|
|
||||||
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, get_message("echo-error-non-utf8")));
|
return Err(USimpleError::new(1, get_message("echo-error-non-utf8")));
|
||||||
};
|
};
|
||||||
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
stdout_lock.write_all(b" ")?;
|
stdout.write_all(b" ")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if escaped {
|
if options.escape {
|
||||||
for item in parse_escape_only(bytes, OctalParsing::ThreeDigits) {
|
for item in parse_escape_only(bytes, OctalParsing::ThreeDigits) {
|
||||||
if item.write(&mut *stdout_lock)?.is_break() {
|
if item.write(&mut *stdout)?.is_break() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
stdout_lock.write_all(bytes)?;
|
stdout.write_all(bytes)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if trailing_newline {
|
if options.trailing_newline {
|
||||||
stdout_lock.write_all(b"\n")?;
|
stdout.write_all(b"\n")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -212,8 +247,7 @@ fn bytes_from_os_string(input: &OsStr) -> Option<&[u8]> {
|
||||||
|
|
||||||
#[cfg(not(target_family = "unix"))]
|
#[cfg(not(target_family = "unix"))]
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO: Verify that this works correctly on these platforms
|
||||||
// Verify that this works correctly on these platforms
|
|
||||||
input.to_str().map(|st| st.as_bytes())
|
input.to_str().map(|st| st.as_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue