mirror of
https://github.com/uutils/coreutils.git
synced 2025-12-23 08:47:37 +00:00
Adding example of how the clap parser can be setup
This commit is contained in:
parent
aaa061052f
commit
e2baf1cceb
4 changed files with 185 additions and 43 deletions
|
|
@ -10,7 +10,8 @@ use uucore::display::Quotable;
|
|||
use uucore::error::{FromIo, UError, UResult};
|
||||
use uucore::fs::{make_path_relative_to, paths_refer_to_same_file};
|
||||
use uucore::translate;
|
||||
use uucore::{format_usage, prompt_yes, show_error};
|
||||
use uucore::{prompt_yes, show_error};
|
||||
use uucore::clap_localization::localize_command;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashSet;
|
||||
|
|
@ -91,7 +92,36 @@ static ARG_FILES: &str = "files";
|
|||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?;
|
||||
let cmd = uu_app();
|
||||
let matches = cmd.clone().try_get_matches_from(args);
|
||||
|
||||
let matches = match matches {
|
||||
Ok(m) => {
|
||||
if m.get_flag("help") {
|
||||
localize_command(cmd).print_help().unwrap();
|
||||
println!();
|
||||
return Ok(());
|
||||
}
|
||||
m
|
||||
}
|
||||
Err(e) => {
|
||||
use clap::error::ErrorKind;
|
||||
match e.kind() {
|
||||
ErrorKind::DisplayHelp => {
|
||||
localize_command(cmd).print_help().unwrap();
|
||||
println!();
|
||||
return Ok(());
|
||||
}
|
||||
ErrorKind::DisplayVersion => {
|
||||
print!("{}", e.render());
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
return Err(uucore::clap_localization::clap_error_to_uerror(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* the list of files */
|
||||
|
||||
|
|
@ -135,71 +165,65 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
|||
exec(&paths[..], &settings)
|
||||
}
|
||||
|
||||
/// Build the clap Command with translation keys (not translated strings).
|
||||
/// Translation only happens when help is actually displayed.
|
||||
pub fn uu_app() -> Command {
|
||||
let after_help = format!(
|
||||
"{}\n\n{}",
|
||||
translate!("ln-after-help"),
|
||||
backup_control::BACKUP_CONTROL_LONG_HELP
|
||||
);
|
||||
|
||||
Command::new(uucore::util_name())
|
||||
.version(uucore::crate_version!())
|
||||
.help_template(uucore::localized_help_template(uucore::util_name()))
|
||||
.about(translate!("ln-about"))
|
||||
.override_usage(format_usage(&translate!("ln-usage")))
|
||||
.disable_help_flag(true)
|
||||
.about("ln-about") // key, not translated
|
||||
.after_help("ln-after-help") // key
|
||||
.infer_long_args(true)
|
||||
.after_help(after_help)
|
||||
.arg(
|
||||
Arg::new("help")
|
||||
.short('h')
|
||||
.long("help")
|
||||
.help("help-help") // key
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(backup_control::arguments::backup())
|
||||
.arg(backup_control::arguments::backup_no_args())
|
||||
/*.arg(
|
||||
Arg::new(options::DIRECTORY)
|
||||
.short('d')
|
||||
.long(options::DIRECTORY)
|
||||
.help("allow users with appropriate privileges to attempt to make hard links to directories")
|
||||
)*/
|
||||
.arg(
|
||||
Arg::new(options::FORCE)
|
||||
.short('f')
|
||||
.long(options::FORCE)
|
||||
.help(translate!("ln-help-force"))
|
||||
.help("ln-help-force") // key
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::INTERACTIVE)
|
||||
.short('i')
|
||||
.long(options::INTERACTIVE)
|
||||
.help(translate!("ln-help-interactive"))
|
||||
.help("ln-help-interactive") // key
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::NO_DEREFERENCE)
|
||||
.short('n')
|
||||
.long(options::NO_DEREFERENCE)
|
||||
.help(translate!("ln-help-no-dereference"))
|
||||
.help("ln-help-no-dereference") // key
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::LOGICAL)
|
||||
.short('L')
|
||||
.long(options::LOGICAL)
|
||||
.help(translate!("ln-help-logical"))
|
||||
.help("ln-help-logical") // key
|
||||
.overrides_with(options::PHYSICAL)
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
// Not implemented yet
|
||||
Arg::new(options::PHYSICAL)
|
||||
.short('P')
|
||||
.long(options::PHYSICAL)
|
||||
.help(translate!("ln-help-physical"))
|
||||
.help("ln-help-physical") // key
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::SYMBOLIC)
|
||||
.short('s')
|
||||
.long(options::SYMBOLIC)
|
||||
.help(translate!("ln-help-symbolic"))
|
||||
// override added for https://github.com/uutils/coreutils/issues/2359
|
||||
.help("ln-help-symbolic") // key
|
||||
.overrides_with(options::SYMBOLIC)
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
|
|
@ -208,7 +232,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::TARGET_DIRECTORY)
|
||||
.short('t')
|
||||
.long(options::TARGET_DIRECTORY)
|
||||
.help(translate!("ln-help-target-directory"))
|
||||
.help("ln-help-target-directory") // key
|
||||
.value_name("DIRECTORY")
|
||||
.value_hint(clap::ValueHint::DirPath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
|
|
@ -218,14 +242,14 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::NO_TARGET_DIRECTORY)
|
||||
.short('T')
|
||||
.long(options::NO_TARGET_DIRECTORY)
|
||||
.help(translate!("ln-help-no-target-directory"))
|
||||
.help("ln-help-no-target-directory") // key
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::RELATIVE)
|
||||
.short('r')
|
||||
.long(options::RELATIVE)
|
||||
.help(translate!("ln-help-relative"))
|
||||
.help("ln-help-relative") // key
|
||||
.requires(options::SYMBOLIC)
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
|
|
@ -233,7 +257,7 @@ pub fn uu_app() -> Command {
|
|||
Arg::new(options::VERBOSE)
|
||||
.short('v')
|
||||
.long(options::VERBOSE)
|
||||
.help(translate!("ln-help-verbose"))
|
||||
.help("ln-help-verbose") // key
|
||||
.action(ArgAction::SetTrue),
|
||||
)
|
||||
.arg(
|
||||
|
|
@ -241,7 +265,7 @@ pub fn uu_app() -> Command {
|
|||
.action(ArgAction::Append)
|
||||
.value_hint(clap::ValueHint::AnyPath)
|
||||
.value_parser(clap::value_parser!(OsString))
|
||||
.required(true)
|
||||
.required_unless_present("help")
|
||||
.num_args(1..),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -190,20 +190,13 @@ macro_rules! bin {
|
|||
($util:ident) => {
|
||||
pub fn main() {
|
||||
use std::io::Write;
|
||||
use uucore::locale;
|
||||
// suppress extraneous error output for SIGPIPE failures/panics
|
||||
uucore::panic::mute_sigpipe_panic();
|
||||
locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
|
||||
.unwrap_or_else(|err| {
|
||||
match err {
|
||||
uucore::locale::LocalizationError::ParseResource {
|
||||
error: err_msg,
|
||||
snippet,
|
||||
} => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
|
||||
other => eprintln!("Could not init the localization system: {other}"),
|
||||
}
|
||||
std::process::exit(99)
|
||||
});
|
||||
|
||||
// Store util name for lazy localization initialization
|
||||
uucore::locale::set_util_name_for_lazy_init(
|
||||
uucore::get_canonical_util_name(stringify!($util)),
|
||||
);
|
||||
|
||||
// execute utility code
|
||||
let code = $util::uumain(uucore::args_os());
|
||||
|
|
|
|||
|
|
@ -561,6 +561,109 @@ pub fn configure_localized_command(mut cmd: Command) -> Command {
|
|||
cmd
|
||||
}
|
||||
|
||||
/// Localizes a clap `Command` by translating all keys to actual strings.
|
||||
///
|
||||
/// This function is the "layer between clap and translations". It takes a Command
|
||||
/// that was built with translation keys (not translated strings) and converts
|
||||
/// those keys to actual translated strings just before displaying help.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `cmd` - The clap `Command` with translation keys in about/help fields
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new `Command` with all keys translated and proper formatting applied.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// use clap::{Arg, ArgAction, Command};
|
||||
/// use uucore::clap_localization::localize_command;
|
||||
///
|
||||
/// // Build command with keys (not translated strings)
|
||||
/// let cmd = Command::new("myutil")
|
||||
/// .about("myutil-about") // key, not translated
|
||||
/// .arg(Arg::new("verbose")
|
||||
/// .short('v')
|
||||
/// .help("myutil-help-verbose")); // key
|
||||
///
|
||||
/// // Only translate when actually displaying help
|
||||
/// if user_requested_help {
|
||||
/// localize_command(cmd).print_help().unwrap();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn localize_command(mut cmd: Command) -> Command {
|
||||
let util_name = crate::util_name();
|
||||
|
||||
// Translate about text
|
||||
if let Some(about) = cmd.get_about().map(|s| s.to_string()) {
|
||||
cmd = cmd.about(translate!(&about));
|
||||
}
|
||||
|
||||
// Translate after_help text
|
||||
if let Some(after_help) = cmd.get_after_help().map(|s| s.to_string()) {
|
||||
cmd = cmd.after_help(translate!(&after_help));
|
||||
}
|
||||
|
||||
// Set localized usage
|
||||
let usage_key = format!("{util_name}-usage");
|
||||
cmd = cmd.override_usage(crate::format_usage(&translate!(&usage_key)));
|
||||
|
||||
// Translate arg help texts
|
||||
let arg_ids: Vec<_> = cmd.get_arguments().map(|a| a.get_id().clone()).collect();
|
||||
for id in arg_ids {
|
||||
cmd = cmd.mut_arg(&id, |arg| {
|
||||
let mut arg = arg;
|
||||
if let Some(help_key) = arg.get_help().map(|s| s.to_string()) {
|
||||
arg = arg.help(translate!(&help_key));
|
||||
}
|
||||
if let Some(long_help_key) = arg.get_long_help().map(|s| s.to_string()) {
|
||||
arg = arg.long_help(translate!(&long_help_key));
|
||||
}
|
||||
arg
|
||||
});
|
||||
}
|
||||
|
||||
// Apply color and help template
|
||||
configure_localized_command(cmd)
|
||||
}
|
||||
|
||||
/// Converts a clap Error to a UError for use in UResult return types.
|
||||
///
|
||||
/// This is useful when manually handling clap parsing and needing to
|
||||
/// return errors through the UResult system.
|
||||
pub fn clap_error_to_uerror(err: Error) -> Box<dyn crate::error::UError> {
|
||||
Box::new(ClapErrorWrapper(err))
|
||||
}
|
||||
|
||||
/// Wrapper to convert clap::Error into UError
|
||||
struct ClapErrorWrapper(Error);
|
||||
|
||||
impl std::fmt::Display for ClapErrorWrapper {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ClapErrorWrapper {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for ClapErrorWrapper {}
|
||||
|
||||
impl crate::error::UError for ClapErrorWrapper {
|
||||
fn code(&self) -> i32 {
|
||||
if self.0.exit_code() == 0 { 0 } else { 1 }
|
||||
}
|
||||
|
||||
fn usage(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/* spell-checker: disable */
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,27 @@ thread_local! {
|
|||
static LOCALIZER: OnceLock<Localizer> = const { OnceLock::new() };
|
||||
}
|
||||
|
||||
// Store util name for lazy initialization
|
||||
static UTIL_NAME_FOR_LAZY_INIT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
|
||||
|
||||
/// Store the utility name for lazy localization initialization.
|
||||
/// Called from bin! macro before uumain.
|
||||
pub fn set_util_name_for_lazy_init(name: &str) {
|
||||
let _ = UTIL_NAME_FOR_LAZY_INIT.set(name.to_string());
|
||||
}
|
||||
|
||||
/// Ensure localization is initialized (lazy initialization).
|
||||
/// Called automatically by translate! macro when needed.
|
||||
fn ensure_initialized() {
|
||||
LOCALIZER.with(|lock| {
|
||||
if lock.get().is_none() {
|
||||
if let Some(util_name) = UTIL_NAME_FOR_LAZY_INIT.get() {
|
||||
let _ = setup_localization(util_name);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Helper function to find the uucore locales directory from a utility's locales directory
|
||||
fn find_uucore_locales_dir(utility_locales_dir: &Path) -> Option<PathBuf> {
|
||||
// Normalize the path to get absolute path
|
||||
|
|
@ -262,6 +283,7 @@ fn create_english_bundle_from_embedded(
|
|||
}
|
||||
|
||||
fn get_message_internal(id: &str, args: Option<FluentArgs>) -> String {
|
||||
ensure_initialized();
|
||||
LOCALIZER.with(|lock| {
|
||||
lock.get()
|
||||
.map(|loc| loc.format(id, args.as_ref()))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue