Skip to main content

winnow_args/
env.rs

1//! Environment variables as a source of values, after the command line and
2//! before defaults.
3
4use std::cell::RefCell;
5use std::ffi::{OsStr, OsString};
6
7use winnow::stream::BStr;
8
9use crate::error::Error;
10use crate::value::FromArg;
11
12thread_local! {
13    static OVERRIDE: RefCell<Option<Vec<(OsString, OsString)>>> = const { RefCell::new(None) };
14}
15
16/// Run `f` with parses on this thread reading `vars` instead of the process
17/// environment: for tests, which then need neither `unsafe` `set_var` nor to
18/// serialize against each other.
19pub fn with_env<R>(vars: &[(&str, &str)], f: impl FnOnce() -> R) -> R {
20    let vars = vars
21        .iter()
22        .map(|(k, v)| (OsString::from(k), OsString::from(v)))
23        .collect();
24    /// Puts the previous override back, even when `f` panics.
25    struct Restore(Option<Vec<(OsString, OsString)>>);
26    impl Drop for Restore {
27        fn drop(&mut self) {
28            OVERRIDE.with(|o| *o.borrow_mut() = self.0.take());
29        }
30    }
31    let _restore = Restore(OVERRIDE.with(|o| o.replace(Some(vars))));
32    f()
33}
34
35/// Whether [`with_env`] stands in for the environment on this thread: then
36/// the process's surroundings, the terminal included, are not consulted either.
37pub(crate) fn overridden() -> bool {
38    OVERRIDE.with(|o| o.borrow().is_some())
39}
40
41/// The variable `name`, from the override set by [`with_env`] if there is one.
42pub fn var(name: &str) -> Option<OsString> {
43    OVERRIDE.with(|o| match &*o.borrow() {
44        Some(vars) => vars
45            .iter()
46            .find(|(k, _)| k == OsStr::new(name))
47            .map(|(_, v)| v.clone()),
48        None => std::env::var_os(name),
49    })
50}
51
52/// A switch set from the environment: true unless empty, `0`, `false`, `no` or `off`.
53pub fn truthy(value: &OsStr) -> bool {
54    !matches!(
55        value.as_encoded_bytes(),
56        b"" | b"0" | b"false" | b"no" | b"off"
57    )
58}
59
60/// The variable `name` converted with [`FromArg`]; errors name it as `$name`.
61pub fn value<T: FromArg>(name: &str) -> Result<Option<T>, Error> {
62    let Some(raw) = var(name) else {
63        return Ok(None);
64    };
65    let bytes = BStr::new(raw.as_encoded_bytes());
66    T::from_arg(bytes)
67        .map(Some)
68        .map_err(|cause| Error::invalid_value(0, format!("${name}"), bytes, cause))
69}