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}