functora_cfg/
lib.rs

1#![doc = include_str!("../README.md")]
2use clap::{Args, FromArgMatches, Subcommand};
3pub use config::ConfigError;
4use config::{Config, Environment, File, Value};
5use serde::{Deserialize, Serialize, de::DeserializeOwned};
6use std::{collections::HashMap, path::Path};
7use toml::Value as TomlValue;
8
9pub struct Cfg<'a, Src: Serialize> {
10    pub default: &'a Src,
11    pub file_path: fn(&Src) -> Option<&str>,
12    pub env_prefix: &'a str,
13    pub command_line: &'a Src,
14}
15
16impl<'a, Src: Serialize> Cfg<'a, Src> {
17    pub fn eval<Dst: Serialize + DeserializeOwned>(
18        &self,
19    ) -> Result<Dst, ConfigError> {
20        eval(self)
21    }
22}
23
24pub fn eval<Src, Dst>(
25    cfg: &Cfg<Src>,
26) -> Result<Dst, ConfigError>
27where
28    Src: Serialize,
29    Dst: Serialize + DeserializeOwned,
30{
31    let mut builder = Config::builder();
32
33    builder =
34        builder.add_source(term_to_config(cfg.default)?);
35
36    let getter = cfg.file_path;
37    if let Some(path) =
38        getter(cfg.command_line).or(getter(cfg.default))
39    {
40        let path = Path::new(&path);
41        if path.exists() && path.is_file() {
42            builder = builder.add_source(File::from(path));
43        } else {
44            return Err(ConfigError::Message(
45                "Config file path does not exist!".into(),
46            ));
47        }
48    }
49
50    builder = builder.add_source(
51        Environment::with_prefix(
52            &cfg.env_prefix.to_uppercase(),
53        )
54        .prefix_separator("__")
55        .separator("__"),
56    );
57
58    builder = builder
59        .add_source(term_to_config(cfg.command_line)?);
60
61    builder.build()?.try_deserialize()
62}
63
64fn term_to_config<T: Serialize>(
65    value: &T,
66) -> Result<Config, ConfigError> {
67    let toml = toml::Value::try_from(value)
68        .map_err(|e| ConfigError::Message(e.to_string()))?;
69
70    if let TomlValue::Table(kv) = toml {
71        kv.into_iter()
72            .try_fold(Config::builder(), |acc, (k, v)| {
73                acc.set_override(k, toml_to_config(v))
74            })?
75            .build()
76    } else {
77        Err(ConfigError::Message(
78            "Expected table at root".into(),
79        ))
80    }
81}
82
83fn toml_to_config(toml: TomlValue) -> Value {
84    match toml {
85        TomlValue::String(x) => Value::from(x),
86        TomlValue::Integer(x) => Value::from(x),
87        TomlValue::Float(x) => Value::from(x),
88        TomlValue::Boolean(x) => Value::from(x),
89        TomlValue::Datetime(x) => {
90            Value::from(x.to_string())
91        }
92        TomlValue::Array(xs) => Value::from(
93            xs.into_iter()
94                .map(toml_to_config)
95                .collect::<Vec<_>>(),
96        ),
97        TomlValue::Table(kv) => Value::from(
98            kv.into_iter()
99                .map(|(k, v)| (k, toml_to_config(v)))
100                .collect::<config::Map<String, Value>>(),
101        ),
102    }
103}
104
105#[derive(
106    Eq,
107    PartialEq,
108    Ord,
109    PartialOrd,
110    Debug,
111    Clone,
112    Serialize,
113    Deserialize,
114)]
115pub struct ReClap<T, U>
116where
117    T: Args,
118    U: Subcommand,
119{
120    pub prev: T,
121    pub next: Option<Box<U>>,
122}
123
124impl<T, U> ReClap<T, U>
125where
126    T: Args,
127    U: Subcommand,
128{
129    pub fn vec(self, f: fn(U) -> ReClap<T, U>) -> Vec<T> {
130        let mut prev = vec![self.prev];
131        if let Some(next) = self.next {
132            prev.extend(f(*next).vec(f))
133        }
134        prev
135    }
136
137    pub fn hash_map(
138        self,
139        f: fn(U) -> ReClap<T, U>,
140    ) -> HashMap<String, T> {
141        self.vec(f)
142            .into_iter()
143            .enumerate()
144            .map(|(k, v)| (k.to_string(), v))
145            .collect()
146    }
147}
148
149impl<T, U> Args for ReClap<T, U>
150where
151    T: Args,
152    U: Subcommand,
153{
154    fn augment_args(cmd: clap::Command) -> clap::Command {
155        T::augment_args(cmd).defer(|cmd| {
156            U::augment_subcommands(
157                cmd.disable_help_subcommand(true),
158            )
159        })
160    }
161    fn augment_args_for_update(
162        _: clap::Command,
163    ) -> clap::Command {
164        unimplemented!()
165    }
166}
167
168impl<T, U> FromArgMatches for ReClap<T, U>
169where
170    T: Args,
171    U: Subcommand,
172{
173    fn from_arg_matches(
174        matches: &clap::ArgMatches,
175    ) -> Result<Self, clap::Error> {
176        let prev = T::from_arg_matches(matches)?;
177        let next = if let Some((_name, _sub)) =
178            matches.subcommand()
179        {
180            Some(U::from_arg_matches(matches)?)
181        } else {
182            None
183        };
184        Ok(Self {
185            prev,
186            next: next.map(Box::new),
187        })
188    }
189    fn update_from_arg_matches(
190        &mut self,
191        _: &clap::ArgMatches,
192    ) -> Result<(), clap::Error> {
193        unimplemented!()
194    }
195}
196
197#[derive(
198    Eq,
199    PartialEq,
200    Ord,
201    PartialOrd,
202    Debug,
203    Clone,
204    Serialize,
205    Deserialize,
206)]
207pub struct IdClap<T>(pub T);
208
209impl<T> IdClap<T> {
210    pub fn run(IdClap(x): Self) -> T {
211        x
212    }
213}
214
215impl<T> FromArgMatches for IdClap<T> {
216    fn from_arg_matches(
217        _: &clap::ArgMatches,
218    ) -> Result<Self, clap::Error> {
219        unimplemented!()
220    }
221    fn update_from_arg_matches(
222        &mut self,
223        _: &clap::ArgMatches,
224    ) -> Result<(), clap::Error> {
225        unimplemented!()
226    }
227}
228
229impl<T> Subcommand for IdClap<T> {
230    fn augment_subcommands(
231        _: clap::Command,
232    ) -> clap::Command {
233        unimplemented!()
234    }
235    fn augment_subcommands_for_update(
236        _: clap::Command,
237    ) -> clap::Command {
238        unimplemented!()
239    }
240    fn has_subcommand(_: &str) -> bool {
241        unimplemented!()
242    }
243}