1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Similar to [std::env::*](mod@std::env), but optimized for better error messages.
//!
//! | Prefix        | Set           | Unset     | Set (Invalid Unicode) |
//! | ------------- | ------------- | --------- | --------------------- |
//! | var_str       | Ok(value)     | Err       | Err
//! | var_lossy     | Ok(value)     | Err       | Ok(**lossy**)
//! | var_os        | Ok(value)     | Err       | Ok(value)
//! | var_path      | Ok(value)     | Err       | Ok(value)
//! | req_var_str   | value         | <span style="color: red; font-weight: bold">exit</span>      | <span style="color: red; font-weight: bold">exit</span>
//! | req_var_lossy | value         | <span style="color: red; font-weight: bold">exit</span>      | **lossy**
//! | req_var_os    | value         | <span style="color: red; font-weight: bold">exit</span>      | value
//! | req_var_path  | value         | <span style="color: red; font-weight: bold">exit</span>      | value
//! | opt_var_str   | Some(value)   | None      | <span style="color: red; font-weight: bold">exit</span>
//! | opt_var_lossy | Some(value)   | None      | Some(**lossy**)
//! | opt_var_os    | Some(value)   | None      | Some(value)
//! | opt_var_path  | Some(value)   | None      | Some(value)

use crate::*;

use std::ffi::{OsStr, OsString};
use std::fmt::{self, Display, Formatter};
use std::path::{Path, PathBuf};



pub type Result<T> = std::result::Result<T, Error>;

/// Contextual env var error.  Examples:
/// <code style="display: block; padding: 0.25em; margin: 0.5em 0;">%NONEXISTANT% is not set   <span style="color: #888">(windows)</span>
/// ${NONEXISTANT} is not set  <span style="color: #888">(linux)</span></code>

#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum Error {
    NotSet(OsString),
    InvalidUnicode(OsString),
}

impl Display for Error {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        fn display<'a>(var: &'a OsStr) -> impl Display + 'a { Path::new(var).display() }

        if cfg!(windows) {
            match self {
                Error::NotSet(var)          => write!(fmt, "%{}% is not set",               display(var)),
                Error::InvalidUnicode(var)  => write!(fmt, "%{}% contains invalid unicode", display(var)),
            }
        } else {
            match self {
                Error::NotSet(var)          => write!(fmt, "${{{}}} is not set",               display(var)),
                Error::InvalidUnicode(var)  => write!(fmt, "${{{}}} contains invalid unicode", display(var)),
            }
        }
    }
}



pub fn has_var(name: impl AsRef<OsStr> + Into<OsString>) -> bool {
    std::env::var_os(name).is_some()
}

pub fn var_str(name: impl AsRef<OsStr> + Into<OsString>) -> Result<String> {
    match std::env::var(name.as_ref()) {
        Ok(v) => Ok(v),
        Err(std::env::VarError::NotPresent)     => Err(Error::NotSet(name.into())),
        Err(std::env::VarError::NotUnicode(_))  => Err(Error::InvalidUnicode(name.into())),
    }
}

pub fn var_lossy(name: impl AsRef<OsStr> + Into<OsString>) -> Result<String> {
    match std::env::var_os(name.as_ref()) {
        Some(v) => Ok(into_string_lossy(v)),
        None    => Err(Error::NotSet(name.into())),
    }
}

pub fn var_os(name: impl AsRef<OsStr> + Into<OsString>) -> Result<OsString> {
    match std::env::var_os(name.as_ref()) {
        Some(v) => Ok(v),
        None    => Err(Error::NotSet(name.into())),
    }
}

pub fn var_path(name: impl AsRef<OsStr> + Into<OsString>) -> Result<PathBuf> {
    match std::env::var_os(name.as_ref()) {
        Some(v) => Ok(PathBuf::from(v)),
        None    => Err(Error::NotSet(name.into())),
    }
}



pub fn req_var_str  (name: impl AsRef<OsStr> + Into<OsString>) -> String    { var_str(name).or_die() }
pub fn req_var_lossy(name: impl AsRef<OsStr> + Into<OsString>) -> String    { var_lossy(name).or_die() }
pub fn req_var_os   (name: impl AsRef<OsStr> + Into<OsString>) -> OsString  { var_os(name).or_die() }
pub fn req_var_path (name: impl AsRef<OsStr> + Into<OsString>) -> PathBuf   { var_path(name).or_die() }



pub fn opt_var_str(name: impl AsRef<OsStr> + Into<OsString>) -> Result<Option<String>> {
    match std::env::var(name.as_ref()) {
        Ok(v) => Ok(Some(v)),
        Err(std::env::VarError::NotPresent)     => Ok(None),
        Err(std::env::VarError::NotUnicode(_))  => Err(Error::InvalidUnicode(name.into())),
    }
}

pub fn opt_var_lossy(name: impl AsRef<OsStr> + Into<OsString>) -> Option<String> {
    std::env::var_os(name.as_ref()).map(into_string_lossy)
}

pub fn opt_var_os(name: impl AsRef<OsStr> + Into<OsString>) -> Option<OsString> {
    std::env::var_os(name.as_ref())
}

pub fn opt_var_path(name: impl AsRef<OsStr> + Into<OsString>) -> Option<PathBuf> {
    std::env::var_os(name.as_ref()).map(PathBuf::from)
}



fn into_string_lossy(os: OsString) -> String {
    // Optimized for the common case where OsString is valid UTF8
    os.into_string().map_or_else(
        |os| os.to_string_lossy().into_owned(), // could be optimized to not revalidate early unicode
        |s| s,
    )
}