#![deny(missing_docs)]
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<String>, bool),
}
#[derive(Debug)]
struct Config {
mode: Mode,
}
fn parse_args() -> Result<Config> {
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<String> = 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::<String>("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 '{}' specified", other)),
}
}
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<String> {
vec![format!(
"Features: u8loc={} query-env=0.1 query-preferred=0.1 run=0.1",
env!("CARGO_PKG_VERSION")
)]
}
fn get_env(preferred: bool) -> Result<HashMap<String, String>> {
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<Vec<String>> {
let env = get_env(preferred)?;
let value = env
.get(name)
.with_context(|| format!("Internal error: {:?} should be present in {:?}", name, env))?;
Ok(vec![format!("{}", value)])
}
fn query_list() -> Vec<String> {
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<Vec<String>> {
LanguagesDetect::new()
.detect()
.context("Could not determine the list of preferred languages")
}
fn run_program(prog: &[String], preferred: bool) -> Result<Vec<String>> {
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 {} program", prog_name)))
}
fn run() -> Result<Vec<String>> {
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(())
}