1use thiserror::*;
3
4#[derive(Clone, Error, Debug)]
5pub enum ConfError {
6 #[error("Syntax Error Parsing String")]
7 Syntax,
8 #[error("Environment variable not found")]
9 VarNotFound,
10 #[error("Could not load file {}", 0)]
11 LoadError(String),
12 #[error("{}", _0)]
13 Mess(&'static str),
14 #[error("{}", _0)]
15 Message(String),
16}
17
18impl ConfError {
19 pub fn with_info(self, s: &str) -> Self {
20 ConfError::Message(format!("{} - {}", self, s))
21 }
22}
23
24impl From<&'static str> for ConfError {
25 fn from(s: &'static str) -> Self {
26 ConfError::Mess(s)
27 }
28}
29
30impl From<std::io::Error> for ConfError {
31 fn from(e: std::io::Error) -> Self {
32 ConfError::LoadError(e.to_string())
33 }
34}
35
36impl From<std::env::VarError> for ConfError {
37 fn from(_: std::env::VarError) -> Self {
38 ConfError::VarNotFound
39 }
40}
41impl From<toml::de::Error> for ConfError {
42 fn from(_: toml::de::Error) -> Self {
43 ConfError::Syntax
44 }
45}
46
47type Job<E> = dyn Fn(&str) -> Result<String, E>;
48
49fn _replace<IT, E>(it: &mut IT, f: &Job<E>, depth: u8) -> Result<String, ConfError>
50where
51 IT: Iterator<Item = char>,
52 ConfError: From<E>,
54{
55 let mut res = String::new();
56 while let Some(c) = it.next() {
57 match c {
58 '\\' => res.push(it.next().ok_or(ConfError::Syntax)?),
59 '{' => {
60 let s = _replace(it, f, depth + 1)?;
61 res.push_str(&f(&s)?);
62 }
63 '}' => {
64 if depth == 0 {
65 return Err(ConfError::Syntax);
66 }
67 return Ok(res);
68 }
69 c => res.push(c),
70 }
71 }
72 if depth > 0 {
73 return Err(ConfError::Syntax);
74 }
75 Ok(res)
76}
77
78pub fn replace<E>(s: &str, f: &Job<E>) -> Result<String, ConfError>
79where
80 ConfError: From<E>,
81 {
83 _replace(&mut s.chars(), f, 0)
84}
85
86pub fn replace_simple<F: 'static + Fn(&str) -> String>(s: &str, f: F) -> Result<String, ConfError> {
87 replace::<ConfError>(s, &move |s| Ok(f(s)))
88}
89
90pub fn replace_env(s: &str) -> Result<String, ConfError> {
91 replace(s, &|v| std::env::var(v))
92}
93
94#[cfg(test)]
95mod test {
96 use super::*;
97 fn mini_rep(s: &str) -> String {
98 s.to_lowercase()
99 }
100 #[test]
101 pub fn rep_test() {
102 let s2 = replace_simple("HELLO{WORLD}", &mini_rep).unwrap();
103 assert_eq!(&s2, "HELLOworld");
104 }
105}