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 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
/*!
A simple ***.env*** file loader
# Description
Giving a sequence of env files from most general to most specific.
# Operation
Parse each file for key val remove any comments blank lines and extra whitespace.
# Syntax
```ignore
TEST_DATA=bar # spaces are optional
## this is a comment
TEST_BAZ = "baz" # double quotes are removed
TEST_BAR = 'bar' # single quotes are removed
## above line was left intentionally blank
```
will produce:
|Key|Value|
|---|---|
`TEST_DATA`|`bar`
`TEST_BAZ`|`baz`
`TEST_BAR`|`bar`
*/
/// Tries to load the env. vars from these paths
///
/// ```rust
/// // this will add envs it finds from the first to the last
/// // so important (read: secret/user) ends should be at the end of the iterator
/// simple_env_load::load_env_from(&["./env", "~/.config/.env"]);
/// ```
pub fn load_env_from<I, T>(paths: I)
where
I: IntoIterator<Item = T>,
T: AsRef<std::path::Path>,
{
paths
.into_iter()
.map(std::fs::read_to_string)
.flatten()
.for_each(|data| parse_and_set(&data, |k, v| std::env::set_var(&k, &v)))
}
/// Parse an env string and calls a function for each key=value pair
///
/// This is useful for mocking and testing
///
/// ```rust
/// let data = r#"
/// # this is a comment
/// TEST_DATA=bar # spaces are optional
/// TEST_BAZ = "baz" # double quotes are removed
/// TEST_QUX = 'qux' # single quotes are removed
/// TEST_FOO = "'nested'" # nested quotes are preserved
/// TEST_BAR = '"nested"' # nested quotes are preserved
///
/// # above line was left intentionally blank
/// "#;
/// # for key in ["TEST_DATA", "TEST_baz", "TEST_qux","TEST_FOO", "TEST_BAR"] {
/// # assert!(std::env::var(key).is_err());
/// # }
/// // just set the env. vars, but this can be any fn(&str, &str)
/// simple_env_load::parse_and_set(&data, |k, v| std::env::set_var(k, v));
/// assert_eq!(std::env::var("TEST_DATA").unwrap(), "bar");
/// assert_eq!(std::env::var("TEST_BAZ").unwrap(), "baz");
/// assert_eq!(std::env::var("TEST_QUX").unwrap(), "qux");;
/// assert_eq!(std::env::var("TEST_FOO").unwrap(), "'nested'");
/// assert_eq!(std::env::var("TEST_BAR").unwrap(), "\"nested\"");
/// ```
pub fn parse_and_set(data: &str, set: fn(k: &str, v: &str)) {
parse(data).for_each(|(k, v)| set(k, v))
}
fn parse(data: &str) -> impl Iterator<Item = (&str, &str)> + '_ {
data.lines().map(<str>::trim).filter_map(|s| {
if s.starts_with('#') {
return None;
}
let mut iter = s.splitn(2, '=').map(<str>::trim).map(parse_str);
let (head, tail) = (iter.next()??, iter.next()??);
Some((head, tail))
})
}
fn parse_str(input: &str) -> Option<&str> {
if !input.contains(|c| matches!(c, '"' | '\'')) {
return input.splitn(2, '#').map(<str>::trim).next();
}
#[derive(Debug)]
enum Flavor {
Single,
Double,
Unknown,
}
let mut flavor = Flavor::Unknown;
let (mut start, mut end) = (None, None);
for (i, c) in input.char_indices() {
if start.is_some() && end.is_some() {
break;
}
if matches!(flavor, Flavor::Unknown) {
flavor = match c {
'\'' => Flavor::Single,
'"' => Flavor::Double,
_ => continue,
};
}
if match flavor {
Flavor::Single => '\'',
Flavor::Double => '"',
Flavor::Unknown => unreachable!(),
} != c
{
continue;
}
match (start, end) {
(None, ..) => {
start.get_or_insert(i + 1);
}
(Some(_), None) => {
end.get_or_insert(i - 1);
}
_ => {}
};
}
let (start, end) = (start?, end?);
input.get(start..start + end)
}
#[test]
fn parse_octos_in_strings() {
macro_rules! val {
($k:expr => $v:expr) => {
&[($k, $v)]
};
}
#[rustfmt::skip]
let tests: &[(&str, &[(&str, &str)])] = &[
(r##"FOO="#bar""##, val!("FOO" => "#bar")),
(r"'asdf'='fdsa'", val!("asdf" => "fdsa")),
(r##"#FOO="bar""##, &[]),
];
for (input, expected) in tests {
assert_eq!(parse(input).collect::<Vec<_>>(), *expected);
}
}