use clap::builder::ValueParser;
use clap::{Arg, ArgAction, Command};
use std::env;
use std::ffi::{OsStr, OsString};
use std::io::{StdoutLock, Write, stdout};
use uucore::error::UResult;
use uucore::format::{FormatChar, OctalParsing, parse_escape_only};
use uucore::{crate_version, format_usage, os_str_as_bytes};
use uucore::translate;
mod options {
pub const STRING: &str = "STRING";
pub const NO_NEWLINE: &str = "no_newline";
pub const ENABLE_BACKSLASH_ESCAPE: &str = "enable_backslash_escape";
pub const DISABLE_BACKSLASH_ESCAPE: &str = "disable_backslash_escape";
}
#[derive(Debug, Clone, Copy)]
struct Options {
pub trailing_newline: bool,
pub escape: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
trailing_newline: true,
escape: false,
}
}
}
impl Options {
fn posixly_correct_default() -> Self {
Self {
trailing_newline: true,
escape: true,
}
}
}
fn is_flag(arg: &OsStr, options: &mut Options) -> bool {
let arg = arg.as_encoded_bytes();
if arg.first() != Some(&b'-') || arg == b"-" {
return false;
}
let mut options_: Options = *options;
for c in &arg[1..] {
match c {
b'e' => options_.escape = true,
b'E' => options_.escape = false,
b'n' => options_.trailing_newline = false,
_ => return false,
}
}
*options = options_;
true
}
fn filter_flags(args: impl Iterator<Item = OsString>) -> (impl Iterator<Item = OsString>, Options) {
let mut options = Options::default();
let mut args = args.peekable();
while let Some(arg) = args.peek() {
if is_flag(arg, &mut options) {
args.next();
} else {
break;
}
}
(args, options)
}
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let mut args = args.skip(1).peekable();
let is_posixly_correct = env::var_os("POSIXLY_CORRECT").is_some();
let (args, options): (Box<dyn Iterator<Item = OsString>>, Options) = if is_posixly_correct {
if args.peek().is_some_and(|arg| arg == "-n") {
let (args, _) = filter_flags(args);
(
Box::new(args),
Options {
trailing_newline: false,
..Options::posixly_correct_default()
},
)
} else {
(Box::new(args), Options::posixly_correct_default())
}
} else if let Some(first_arg) = args.next() {
if first_arg == "--help" && args.peek().is_none() {
uu_app().print_help()?;
return Ok(());
} else if first_arg == "--version" && args.peek().is_none() {
writeln!(stdout(), "echo {}", crate_version!())?;
return Ok(());
}
let (args, options) = filter_flags(std::iter::once(first_arg).chain(args));
(Box::new(args), options)
} else {
(Box::new(args), Options::default())
};
execute(&mut stdout().lock(), args, options)?;
Ok(())
}
pub fn uu_app() -> Command {
Command::new("echo")
.trailing_var_arg(true)
.allow_hyphen_values(true)
.version(crate_version!())
.about(translate!("echo-about"))
.after_help(translate!("echo-after-help"))
.override_usage(format_usage(&translate!("echo-usage")))
.help_template(uucore::localized_help_template("echo"))
.arg(
Arg::new(options::NO_NEWLINE)
.short('n')
.help(translate!("echo-help-no-newline"))
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::ENABLE_BACKSLASH_ESCAPE)
.short('e')
.help(translate!("echo-help-enable-escapes"))
.action(ArgAction::SetTrue)
.overrides_with(options::DISABLE_BACKSLASH_ESCAPE),
)
.arg(
Arg::new(options::DISABLE_BACKSLASH_ESCAPE)
.short('E')
.help(translate!("echo-help-disable-escapes"))
.action(ArgAction::SetTrue)
.overrides_with(options::ENABLE_BACKSLASH_ESCAPE),
)
.arg(
Arg::new(options::STRING)
.action(ArgAction::Append)
.value_parser(ValueParser::os_string()),
)
}
fn execute(
stdout: &mut StdoutLock,
args: impl Iterator<Item = OsString>,
options: Options,
) -> UResult<()> {
for (i, arg) in args.into_iter().enumerate() {
let bytes = os_str_as_bytes(&arg)?;
if i > 0 {
stdout.write_all(b" ")?;
}
if options.escape {
for item in parse_escape_only(bytes, OctalParsing::ThreeDigits) {
if item.write(&mut *stdout)?.is_break() {
return Ok(());
}
}
} else {
stdout.write_all(bytes)?;
}
}
if options.trailing_newline {
stdout.write_all(b"\n")?;
}
Ok(())
}