u8loc 1.1.2

Run child processes in a UTF-8-capable locale
#![deny(missing_docs)]
// SPDX-FileCopyrightText: Peter Pentchev <roam@ringlet.net>
// 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 as _;
use std::process::Command;

use anyhow::{Context as _, Result, anyhow};
use utf8_locale::{LanguagesDetect, Utf8Detect};

use cli::Mode;

mod cli;

const FEATURES: [(&str, &str); 4] = [
    ("u8loc", env!("CARGO_PKG_VERSION")),
    ("query-env", "0.1"),
    ("query-preferred", "0.1"),
    ("run", "0.1"),
];

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 value = {
        let mut env = get_env(preferred)?;
        env.remove(name)
            .ok_or_else(|| anyhow!("Internal error: {name:?} should be present in {env:?}"))?
    };
    Ok(vec![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()
        .ok_or_else(|| anyhow!("Not even a program name?"))?;
    Err(Command::new(prog_name)
        .args(args)
        .env_clear()
        .envs(env)
        .exec())
    .context(format!("Could not execute the {prog_name} program"))
}

fn run() -> Result<Option<Vec<String>>> {
    match cli::parse_args()? {
        Mode::Handled => Ok(None),
        Mode::QueryEnv(ref name, ref preferred) => query_env(name, *preferred).map(Some),
        Mode::QueryList => Ok(Some(query_list())),
        Mode::QueryPreferred => query_preferred().map(Some),
        Mode::Run(ref prog, ref preferred) => run_program(prog, *preferred).map(Some),
    }
}

#[expect(clippy::print_stdout, reason = "this is the whole point")]
fn main() -> Result<()> {
    if let Some(res) = run()? {
        println!("{res}", res = res.join("\n"));
    }
    Ok(())
}