From a847832d07521cf15302c689dd5fdcf1499c333e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Thu, 24 Sep 2026 18:27:07 +0200 Subject: [PATCH 1/2] printenv: Honor QUOTING_STYLE --- src/uu/env/src/env.rs | 4 +- src/uu/printenv/Cargo.toml | 2 +- src/uu/printenv/locales/en-US.ftl | 1 + src/uu/printenv/locales/fr-FR.ftl | 1 + src/uu/printenv/src/printenv.rs | 56 ++++++- .../src/lib/features/quoting_style/mod.rs | 4 +- src/uucore/src/lib/mods/display.rs | 36 ++++- tests/by-util/test_printenv.rs | 150 ++++++++++++++++++ 8 files changed, 238 insertions(+), 16 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 1ba7aa9b71f..8545d256296 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -33,7 +33,6 @@ use std::env; #[cfg(unix)] use std::ffi::CString; use std::ffi::{OsStr, OsString}; -#[cfg(not(unix))] use std::io; use std::io::Write as _; use std::io::stderr; @@ -825,8 +824,7 @@ impl EnvAppData { apply_change_directory(&opts)?; if opts.program.is_empty() { - // no program provided, so just dump all env vars to stdout - print_all_env_vars(opts.line_ending)?; + print_all_env_vars(io::stdout(), opts.line_ending)?; } else { return self.run_program(&opts, self.do_debug_printing); } diff --git a/src/uu/printenv/Cargo.toml b/src/uu/printenv/Cargo.toml index a60b5bda9b2..e1eeb0ba8d8 100644 --- a/src/uu/printenv/Cargo.toml +++ b/src/uu/printenv/Cargo.toml @@ -19,7 +19,7 @@ doctest = false [dependencies] clap = { workspace = true } fluent = { workspace = true } -uucore = { workspace = true } +uucore = { workspace = true, features = ["quoting-style", "parser"] } [lints] workspace = true diff --git a/src/uu/printenv/locales/en-US.ftl b/src/uu/printenv/locales/en-US.ftl index 20c1a81f8dc..3b8be3bbff5 100644 --- a/src/uu/printenv/locales/en-US.ftl +++ b/src/uu/printenv/locales/en-US.ftl @@ -2,3 +2,4 @@ printenv-about = Display the values of the specified environment VARIABLE(s), or printenv-usage = printenv [OPTION]... [VARIABLE]... printenv-help-null = end each output line with 0 byte rather than newline +printenv-help-quoting-style = set quoting style diff --git a/src/uu/printenv/locales/fr-FR.ftl b/src/uu/printenv/locales/fr-FR.ftl index 3ed5b3904f9..a0f5e98498b 100644 --- a/src/uu/printenv/locales/fr-FR.ftl +++ b/src/uu/printenv/locales/fr-FR.ftl @@ -2,3 +2,4 @@ printenv-about = Afficher les valeurs des VARIABLE(s) d'environnement spécifié printenv-usage = printenv [OPTION]... [VARIABLE]... printenv-help-null = terminer chaque ligne de sortie avec un octet 0 plutôt qu'un saut de ligne +printenv-help-quoting-style = indiquer le style d'encapsulation à utiliser diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 0005577c159..9f8eec0dbd6 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -4,17 +4,21 @@ // file that was distributed with this source code. use std::env; -use std::io::Write; +use std::io::{IsTerminal, Write}; +use clap::builder::PossibleValue; use clap::{Arg, ArgAction, Command}; -use uucore::display::{OsWrite, print_all_env_vars}; +use uucore::display::{OsWrite, print_all_env_vars, print_all_env_vars_quoted}; use uucore::error::UResult; +use uucore::i18n::get_ctype_encoding; use uucore::line_ending::LineEnding; +use uucore::parser::shortcut_value_parser::ShortcutValueParser; +use uucore::quoting_style::{QuotingStyle, escape_name, quoting_style_from_env}; use uucore::{format_usage, translate}; static OPT_NULL: &str = "null"; - +static OPT_QUOTING_STYLE: &str = "quoting-style"; static ARG_VARIABLES: &str = "variables"; #[uucore::main(no_signals)] @@ -26,14 +30,34 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .map(|v| v.map(ToString::to_string).collect()) .unwrap_or_default(); + let maybe_quoting_style = matches + .get_one::(OPT_QUOTING_STYLE) + .and_then(QuotingStyle::parse) + .or_else(|| { + std::io::stdout() + .is_terminal() // quoting style from env var is only used in TTY + .then(quoting_style_from_env) + .flatten() + }); + let separator = LineEnding::from_zero_flag(matches.get_flag(OPT_NULL)); if variables.is_empty() { - print_all_env_vars(separator)?; + if let Some(quoting_style) = maybe_quoting_style { + print_all_env_vars_quoted( + std::io::stdout(), + separator, + quoting_style, + get_ctype_encoding(), + )?; + } else { + print_all_env_vars(std::io::stdout(), separator)?; + } return Ok(()); } let mut error_found = false; + let ctype = get_ctype_encoding(); for env_var in variables { // we silently ignore a=b as variable but we trigger an error if env_var.contains('=') { @@ -42,6 +66,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } if let Some(var) = env::var_os(env_var) { let mut stdout = std::io::stdout().lock(); + + let var = if let Some(qs) = maybe_quoting_style { + escape_name(&var, qs, ctype) + } else { + var + }; + stdout.write_all_os(&var)?; write!(stdout, "{separator}")?; } else { @@ -66,6 +97,23 @@ pub fn uu_app() -> Command { .help(translate!("printenv-help-null")) .action(ArgAction::SetTrue), ) + .arg( + Arg::new(OPT_QUOTING_STYLE) + .long(OPT_QUOTING_STYLE) + .help(translate!("printenv-help-quoting-style")) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("literal"), + PossibleValue::new("locale"), + PossibleValue::new("shell"), + PossibleValue::new("shell-escape"), + PossibleValue::new("shell-always"), + PossibleValue::new("shell-escape-always"), + PossibleValue::new("clocale"), + PossibleValue::new("c"), + PossibleValue::new("c-maybe"), + PossibleValue::new("escape"), + ])), + ) .arg( Arg::new(ARG_VARIABLES) .action(ArgAction::Append) diff --git a/src/uucore/src/lib/features/quoting_style/mod.rs b/src/uucore/src/lib/features/quoting_style/mod.rs index 4313a443575..5e1c8f26a00 100644 --- a/src/uucore/src/lib/features/quoting_style/mod.rs +++ b/src/uucore/src/lib/features/quoting_style/mod.rs @@ -157,8 +157,8 @@ impl QuotingStyle { /// /// Used for e.g., the `QUOTING_STYLE` environment variable and the /// `--quoting-style` option of `ls`. - pub fn parse(s: &str) -> Option { - Some(match s { + pub fn parse(s: impl AsRef) -> Option { + Some(match s.as_ref() { "literal" => Self::LITERAL, "shell" => Self::SHELL, "shell-always" => Self::SHELL_ALWAYS, diff --git a/src/uucore/src/lib/mods/display.rs b/src/uucore/src/lib/mods/display.rs index 50174af6f13..d457cd6dd3b 100644 --- a/src/uucore/src/lib/mods/display.rs +++ b/src/uucore/src/lib/mods/display.rs @@ -33,6 +33,12 @@ use std::{env, fmt}; // These used to be defined here, but they live in their own crate now. pub use os_display::{Quotable, Quoted}; +#[cfg(feature = "quoting-style")] +use crate::{ + i18n::UEncoding, + quoting_style::{QuotingStyle, escape_name}, +}; + /// Print a path (or `OsStr`-like object) directly to stdout, with a trailing newline, /// without losing any information if its encoding is invalid. /// @@ -119,13 +125,31 @@ impl OsWrite for Box { /// /// This function handles non-UTF-8 environment variable names and values correctly by using /// raw bytes on Unix systems. -pub fn print_all_env_vars(line_ending: T) -> io::Result<()> { - let mut stdout = io::stdout().lock(); +pub fn print_all_env_vars(mut w: W, line_ending: T) -> io::Result<()> { for (name, value) in env::vars_os() { - stdout.write_all_os(&name)?; - stdout.write_all(b"=")?; - stdout.write_all_os(&value)?; - write!(stdout, "{line_ending}")?; + w.write_all_os(&name)?; + w.write_all(b"=")?; + + w.write_all_os(&value)?; + write!(w, "{line_ending}")?; + } + Ok(()) +} + +/// Equivalent to [`print_all_env_vars`] that quotes +#[cfg(feature = "quoting-style")] +pub fn print_all_env_vars_quoted( + mut w: W, + line_ending: T, + quoting_style: QuotingStyle, + encoding: UEncoding, +) -> io::Result<()> { + for (name, value) in env::vars_os() { + w.write_all_os(&escape_name(&name, quoting_style, encoding))?; + w.write_all(b"=")?; + + w.write_all_os(&escape_name(&value, quoting_style, encoding))?; + write!(w, "{line_ending}")?; } Ok(()) } diff --git a/tests/by-util/test_printenv.rs b/tests/by-util/test_printenv.rs index 89b9140fc21..26605d7ace7 100644 --- a/tests/by-util/test_printenv.rs +++ b/tests/by-util/test_printenv.rs @@ -133,3 +133,153 @@ fn test_non_utf8_env_vars() { .succeeds() .stdout_contains_bytes(b"NON_UTF8_VAR=hello\x80world"); } + +mod quoting_style { + use uutests::new_ucmd; + + use rstest::rstest; + use rstest_reuse::{apply, template}; + + #[template] + #[rstest] + #[case::literal("literal", "a b", "c d")] + #[case::escape("escape", "a\\ b", "c\\ d")] + #[case::shell("shell", "'a b'", "'c d'")] + #[case::c("c", "\"a b\"", "\"c d\"")] + fn test_qs(#[case] qs: &str, #[case] key: &str, #[case] val: &str) {} + + #[apply(test_qs)] + fn test_qs_arg(qs: &str, key: &str, val: &str) { + new_ucmd!() + .env("a b", "c d") + .args(&["--quoting-style", qs]) + .succeeds() + .stdout_contains(format!("{key}={val}")); + + new_ucmd!() + .env("a b", "c d") + .arg("a b") + .args(&["--quoting-style", qs]) + .succeeds() + .stdout_is(format!("{val}\n")); + } + + #[cfg(unix)] + #[apply(test_qs)] + fn test_qs_env(qs: &str, key: &str, val: &str) { + new_ucmd!() + .env("QUOTING_STYLE", qs) + .env("a b", "c d") + .terminal_simulation(true) + .succeeds() + .stdout_contains(format!("{key}={val}")); + + new_ucmd!() + .env("QUOTING_STYLE", qs) + .env("a b", "c d") + .arg("a b") + .terminal_simulation(true) + .succeeds() + .stdout_is(format!("{val}\r\n")); + } + + #[template] + #[rstest] + #[case::locale_c("C", "locale", "'a b'", "'c d'")] + #[case::clocale_c("C", "clocale", "\"a b\"", "\"c d\"")] + #[case::locale_utf8("en_US.UTF-8", "locale", "\u{2018}a b\u{2019}", "\u{2018}c d\u{2019}")] + #[case::clocale_utf8("en_US.UTF-8", "clocale", "\u{2018}a b\u{2019}", "\u{2018}c d\u{2019}")] + fn test_qs_locale( + #[case] locale: &str, + #[case] qs: &str, + #[case] key: &str, + #[case] val: &str, + ) { + } + + #[apply(test_qs_locale)] + fn test_qs_locale_arg(locale: &str, qs: &str, key: &str, val: &str) { + new_ucmd!() + .env("LC_ALL", locale) + .env("a b", "c d") + .args(&["--quoting-style", qs]) + .succeeds() + .stdout_contains(format!("{key}={val}")); + + new_ucmd!() + .env("LC_ALL", locale) + .env("a b", "c d") + .arg("a b") + .args(&["--quoting-style", qs]) + .succeeds() + .stdout_is(format!("{val}\n")); + } + + #[cfg(unix)] + #[apply(test_qs_locale)] + fn test_qs_locale_env(locale: &str, qs: &str, key: &str, val: &str) { + new_ucmd!() + .env("QUOTING_STYLE", qs) + .env("LC_ALL", locale) + .env("a b", "c d") + .terminal_simulation(true) + .succeeds() + .stdout_contains(format!("{key}={val}")); + + new_ucmd!() + .env("QUOTING_STYLE", qs) + .env("LC_ALL", locale) + .env("a b", "c d") + .arg("a b") + .terminal_simulation(true) + .succeeds() + .stdout_is(format!("{val}\r\n")); + } + + // Check that the QUOTING_STYLE environment variable is ignored when + // stdout is not a terminal (default in test environment) + #[test] + fn test_qs_env_no_effect_without_tty() { + new_ucmd!() + .env("QUOTING_STYLE", "c") + .env("a b", "c d") + .arg("a b") + .succeeds() + .stdout_is("c d\n"); + } + + // Check that --quoting-style option takes precedence + // over the QUOTING_STYLE environment variable + #[cfg(unix)] + #[test] + fn test_qs_arg_precedence() { + new_ucmd!() + .env("QUOTING_STYLE", "c") + .env("a b", "c d") + .arg("a b") + .arg("--quoting-style=escape") + .terminal_simulation(true) + .succeeds() + .stdout_is("c\\ d\r\n"); + } + + #[test] + fn test_qs_arg_invalid() { + new_ucmd!() + .arg("--quoting-style=invalid") + .fails_with_code(1) + .stderr_contains("error: invalid value 'invalid' for '--quoting-style"); + } + + #[cfg(unix)] + #[test] + fn test_qs_env_invalid() { + new_ucmd!() + .env("QUOTING_STYLE", "invalid") + .terminal_simulation(true) + .succeeds() + .stderr_is( + "printenv: ignoring invalid value of environment variable QUOTING_STYLE: 'invalid'\r\n", + ); + } +} From eee0b7085597508e9bd06a4c0d1de877a3570c94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20P=C3=A9ron?= Date: Mon, 2 Nov 2026 23:10:26 +0100 Subject: [PATCH 2/2] printenv,ls: Factorize --quoting-style arg --- src/uu/ls/locales/en-US.ftl | 1 - src/uu/ls/locales/fr-FR.ftl | 1 - src/uu/ls/src/config.rs | 4 ++- src/uu/ls/src/ls.rs | 29 +++++-------------- src/uu/printenv/locales/en-US.ftl | 1 - src/uu/printenv/locales/fr-FR.ftl | 1 - src/uu/printenv/src/printenv.rs | 27 ++++------------- src/uucore/locales/en-US.ftl | 2 ++ src/uucore/locales/fr-FR.ftl | 2 ++ .../src/lib/features/quoting_style/mod.rs | 29 +++++++++++++++++++ 10 files changed, 48 insertions(+), 49 deletions(-) diff --git a/src/uu/ls/locales/en-US.ftl b/src/uu/ls/locales/en-US.ftl index 075ec37bf86..822c9d145a8 100644 --- a/src/uu/ls/locales/en-US.ftl +++ b/src/uu/ls/locales/en-US.ftl @@ -51,7 +51,6 @@ ls-help-long-format-no-group = Long format without group information. Identical to --format=long with --no-group. ls-help-long-no-owner = Long format without owner information. ls-help-long-numeric-uid-gid = -l with numeric UIDs and GIDs. -ls-help-set-quoting-style = Set quoting style. ls-help-literal-quoting-style = Use literal quoting style. Equivalent to `--quoting-style=literal` ls-help-escape-quoting-style = Use escape quoting style. Equivalent to `--quoting-style=escape` ls-help-c-quoting-style = Use C quoting style. Equivalent to `--quoting-style=c` diff --git a/src/uu/ls/locales/fr-FR.ftl b/src/uu/ls/locales/fr-FR.ftl index 06a1a6aa6d4..70907a6dbb5 100644 --- a/src/uu/ls/locales/fr-FR.ftl +++ b/src/uu/ls/locales/fr-FR.ftl @@ -49,7 +49,6 @@ ls-help-long-format-no-group = Format long sans informations de groupe. Identique à --format=long avec --no-group. ls-help-long-no-owner = Format long sans informations de propriétaire. ls-help-long-numeric-uid-gid = -l avec des UID et GID numériques. -ls-help-set-quoting-style = Définir le style de citation. ls-help-literal-quoting-style = Utiliser le style de citation littéral. Équivalent à `--quoting-style=literal` ls-help-escape-quoting-style = Utiliser le style de citation d'échappement. Équivalent à `--quoting-style=escape` ls-help-c-quoting-style = Utiliser le style de citation C. Équivalent à `--quoting-style=c` diff --git a/src/uu/ls/src/config.rs b/src/uu/ls/src/config.rs index 29f4d9415af..6821a5f26b6 100644 --- a/src/uu/ls/src/config.rs +++ b/src/uu/ls/src/config.rs @@ -39,6 +39,8 @@ use crate::{ }; pub mod options { + use uucore::quoting_style; + pub mod format { pub static ONE_LINE: &str = "1"; pub static LONG: &str = "long"; @@ -97,7 +99,7 @@ pub mod options { } pub static HELP: &str = "help"; - pub static QUOTING_STYLE: &str = "quoting-style"; + pub use quoting_style::parser::QUOTING_STYLE; pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars"; pub static SHOW_CONTROL_CHARS: &str = "show-control-chars"; pub static WIDTH: &str = "width"; diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index c7e6fd48804..3626337909e 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -38,7 +38,7 @@ use uucore::{ fsext::metadata_get_time, os_str_as_bytes_lossy, parser::shortcut_value_parser::ShortcutValueParser, - show, translate, + quoting_style, show, translate, version_cmp::version_cmp, }; @@ -297,27 +297,12 @@ pub fn uu_app() -> Command { ) // Quoting style .arg( - Arg::new(QUOTING_STYLE) - .long(QUOTING_STYLE) - .help(translate!("ls-help-set-quoting-style")) - .value_parser(ShortcutValueParser::new([ - PossibleValue::new("literal"), - PossibleValue::new("locale"), - PossibleValue::new("shell"), - PossibleValue::new("shell-escape"), - PossibleValue::new("shell-always"), - PossibleValue::new("shell-escape-always"), - PossibleValue::new("clocale"), - PossibleValue::new("c"), - PossibleValue::new("c-maybe"), - PossibleValue::new("escape"), - ])) - .overrides_with_all([ - QUOTING_STYLE, - options::quoting::LITERAL, - options::quoting::ESCAPE, - options::quoting::C, - ]), + quoting_style::parser::clap_quoting_argument().overrides_with_all([ + QUOTING_STYLE, + options::quoting::LITERAL, + options::quoting::ESCAPE, + options::quoting::C, + ]), ) .arg( Arg::new(options::quoting::LITERAL) diff --git a/src/uu/printenv/locales/en-US.ftl b/src/uu/printenv/locales/en-US.ftl index 3b8be3bbff5..20c1a81f8dc 100644 --- a/src/uu/printenv/locales/en-US.ftl +++ b/src/uu/printenv/locales/en-US.ftl @@ -2,4 +2,3 @@ printenv-about = Display the values of the specified environment VARIABLE(s), or printenv-usage = printenv [OPTION]... [VARIABLE]... printenv-help-null = end each output line with 0 byte rather than newline -printenv-help-quoting-style = set quoting style diff --git a/src/uu/printenv/locales/fr-FR.ftl b/src/uu/printenv/locales/fr-FR.ftl index a0f5e98498b..3ed5b3904f9 100644 --- a/src/uu/printenv/locales/fr-FR.ftl +++ b/src/uu/printenv/locales/fr-FR.ftl @@ -2,4 +2,3 @@ printenv-about = Afficher les valeurs des VARIABLE(s) d'environnement spécifié printenv-usage = printenv [OPTION]... [VARIABLE]... printenv-help-null = terminer chaque ligne de sortie avec un octet 0 plutôt qu'un saut de ligne -printenv-help-quoting-style = indiquer le style d'encapsulation à utiliser diff --git a/src/uu/printenv/src/printenv.rs b/src/uu/printenv/src/printenv.rs index 9f8eec0dbd6..f9cb3fc63b9 100644 --- a/src/uu/printenv/src/printenv.rs +++ b/src/uu/printenv/src/printenv.rs @@ -6,19 +6,18 @@ use std::env; use std::io::{IsTerminal, Write}; -use clap::builder::PossibleValue; use clap::{Arg, ArgAction, Command}; use uucore::display::{OsWrite, print_all_env_vars, print_all_env_vars_quoted}; use uucore::error::UResult; use uucore::i18n::get_ctype_encoding; use uucore::line_ending::LineEnding; -use uucore::parser::shortcut_value_parser::ShortcutValueParser; -use uucore::quoting_style::{QuotingStyle, escape_name, quoting_style_from_env}; +use uucore::quoting_style::{ + QuotingStyle, escape_name, parser as qs_parser, quoting_style_from_env, +}; use uucore::{format_usage, translate}; static OPT_NULL: &str = "null"; -static OPT_QUOTING_STYLE: &str = "quoting-style"; static ARG_VARIABLES: &str = "variables"; #[uucore::main(no_signals)] @@ -31,7 +30,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap_or_default(); let maybe_quoting_style = matches - .get_one::(OPT_QUOTING_STYLE) + .get_one::(qs_parser::QUOTING_STYLE) .and_then(QuotingStyle::parse) .or_else(|| { std::io::stdout() @@ -97,23 +96,7 @@ pub fn uu_app() -> Command { .help(translate!("printenv-help-null")) .action(ArgAction::SetTrue), ) - .arg( - Arg::new(OPT_QUOTING_STYLE) - .long(OPT_QUOTING_STYLE) - .help(translate!("printenv-help-quoting-style")) - .value_parser(ShortcutValueParser::new([ - PossibleValue::new("literal"), - PossibleValue::new("locale"), - PossibleValue::new("shell"), - PossibleValue::new("shell-escape"), - PossibleValue::new("shell-always"), - PossibleValue::new("shell-escape-always"), - PossibleValue::new("clocale"), - PossibleValue::new("c"), - PossibleValue::new("c-maybe"), - PossibleValue::new("escape"), - ])), - ) + .arg(qs_parser::clap_quoting_argument()) .arg( Arg::new(ARG_VARIABLES) .action(ArgAction::Append) diff --git a/src/uucore/locales/en-US.ftl b/src/uucore/locales/en-US.ftl index 9d786e42b16..fd4521a48d6 100644 --- a/src/uucore/locales/en-US.ftl +++ b/src/uucore/locales/en-US.ftl @@ -26,6 +26,8 @@ common-help-suggestion = For more information, try '--help'. help-flag-help = Print help information help-flag-version = Print version information +help-set-quoting-style = Set quoting style + # Common error contexts error-io = I/O error error-permission-denied = Permission denied diff --git a/src/uucore/locales/fr-FR.ftl b/src/uucore/locales/fr-FR.ftl index c3b0e82d709..70be9f1cb31 100644 --- a/src/uucore/locales/fr-FR.ftl +++ b/src/uucore/locales/fr-FR.ftl @@ -25,6 +25,8 @@ common-help-suggestion = Pour plus d'informations, essayez '--help'. help-flag-help = Afficher les informations d'aide help-flag-version = Afficher les informations de version +help-set-quoting-style = Définir le style de citation. + # Contextes d'erreur communs error-io = Erreur E/S error-permission-denied = Permission refusée diff --git a/src/uucore/src/lib/features/quoting_style/mod.rs b/src/uucore/src/lib/features/quoting_style/mod.rs index 5e1c8f26a00..33394855b0b 100644 --- a/src/uucore/src/lib/features/quoting_style/mod.rs +++ b/src/uucore/src/lib/features/quoting_style/mod.rs @@ -381,6 +381,35 @@ pub fn locale_aware_escape_dir_name(name: &OsStr, style: QuotingStyle) -> OsStri escape_dir_name(name, style, i18n::get_ctype_encoding()) } +#[cfg(any(feature = "parser", feature = "parser-num"))] +pub mod parser { + use clap::Arg; + use clap::builder::PossibleValue; + + use crate::parser::shortcut_value_parser::ShortcutValueParser; + use crate::translate; + + pub const QUOTING_STYLE: &str = "quoting-style"; + + pub fn clap_quoting_argument() -> Arg { + Arg::new(QUOTING_STYLE) + .long(QUOTING_STYLE) + .help(translate!("help-set-quoting-style")) + .value_parser(ShortcutValueParser::new([ + PossibleValue::new("literal"), + PossibleValue::new("locale"), + PossibleValue::new("shell"), + PossibleValue::new("shell-escape"), + PossibleValue::new("shell-always"), + PossibleValue::new("shell-escape-always"), + PossibleValue::new("clocale"), + PossibleValue::new("c"), + PossibleValue::new("c-maybe"), + PossibleValue::new("escape"), + ])) + } +} + #[cfg(test)] mod tests { use crate::i18n::UEncoding;