#![deny(missing_docs)] /* * SPDX-FileCopyrightText: Peter Pentchev * SPDX-License-Identifier: BSD-2-Clause */ //! u8loc - run a command in a UTF-8-capable locale use std::collections::HashMap; use std::env; use std::os::unix::process::CommandExt; use std::process::Command; use anyhow::{bail, Context, Error as AnyError, Result}; use getopts::Options; use utf8_locale::{LanguagesDetect, Utf8Detect}; #[derive(Debug)] enum Mode { Features, QueryEnv(String, bool), QueryList, QueryPreferred, Run(Vec, bool), } #[derive(Debug)] struct Config { mode: Mode, } fn parse_args() -> Result { let mut parser = Options::new(); parser.optflag( "", "features", "display the features supported by the program and exit", ); parser.optflag( "p", "", "use a locale specified in the LANG and LC_* variables if appropriate", ); parser.optopt( "q", "", "output the value of an environment variable", "NAME", ); parser.optflag( "r", "", "run the specified program in a UTF-8-friendly environment", ); let args: Vec = env::args().collect(); let opts = parser .parse(args.split_first().context("Not even a program name?")?.1) .context("Could not parse the command-line options")?; let preferred = opts.opt_present("p"); match opts .opt_get::("q") .context("Could not obtain the argument of the -q command-line option")? { Some(query) => { if opts.opt_present("r") { bail!("Exactly one of the -q and -r options must be specified"); } match &*query { "list" => Ok(Config { mode: Mode::QueryList, }), "preferred" => Ok(Config { mode: Mode::QueryPreferred, }), var @ ("LC_ALL" | "LANGUAGE") => Ok(Config { mode: Mode::QueryEnv(var.to_owned(), preferred), }), other => bail!(format!("Invalid query name '{other}' specified")), } } None => { if opts.opt_present("r") { if opts.free.is_empty() { bail!("No program specified to run"); } Ok(Config { mode: Mode::Run(opts.free, preferred), }) } else if opts.opt_present("features") { Ok(Config { mode: Mode::Features, }) } else { bail!("Exactly one of the -q and -r options must be specified"); } } } } fn show_features() -> Vec { vec![format!( "Features: u8loc={ver} query-env=0.1 query-preferred=0.1 run=0.1", ver = env!("CARGO_PKG_VERSION") )] } fn get_env(preferred: bool) -> Result> { let det = if preferred { let langs = LanguagesDetect::new() .detect() .context("Could not determine the list of preferred languages")?; Utf8Detect::new().with_languages(langs) } else { Utf8Detect::new() }; Ok(det.detect().context("Could not detect a UTF-8 locale")?.env) } fn query_env(name: &str, preferred: bool) -> Result> { let mut env = get_env(preferred)?; let value = env .remove(name) .with_context(|| format!("Internal error: {name:?} should be present in {env:?}"))?; Ok(vec![value]) } fn query_list() -> Vec { vec![ "LANGUAGE - The LANGUAGE environment variable".to_owned(), "LC_ALL - The LC_ALL environment variable".to_owned(), "list - List the available query parameters".to_owned(), "preferred - List the preferred languages as per the locale variables".to_owned(), ] } fn query_preferred() -> Result> { LanguagesDetect::new() .detect() .context("Could not determine the list of preferred languages") } fn run_program(prog: &[String], preferred: bool) -> Result> { let env = get_env(preferred)?; let (prog_name, args) = prog.split_first().context("Not even a program name?")?; Err(AnyError::new( Command::new(prog_name) .args(args) .env_clear() .envs(env) .exec(), ) .context(format!("Could not execute the {prog_name} program"))) } fn run() -> Result> { let cfg = parse_args()?; match cfg.mode { Mode::Features => Ok(show_features()), Mode::QueryEnv(ref name, ref preferred) => query_env(name, *preferred), Mode::QueryList => Ok(query_list()), Mode::QueryPreferred => query_preferred(), Mode::Run(ref prog, ref preferred) => run_program(prog, *preferred), } } #[allow(clippy::print_stdout)] fn main() -> Result<()> { println!("{}", run()?.join("\n")); Ok(()) }