1extern crate clap;
2extern crate preftool;
3#[macro_use]
4extern crate bitflags;
5
6use clap::{App, Arg, ArgMatches};
7use preftool::*;
8use std::collections::HashMap;
9use std::ffi::OsString;
10use std::rc::Rc;
11
12pub type Validator = dyn Fn(String) -> Result<(), String>;
13pub use context::Context;
14
15pub struct ArgBuilder {
16 name: ConfigKey,
17 long: String,
18 help: Option<&'static str>,
19 takes_value: bool,
20 validate: Option<Rc<Validator>>,
21}
22
23impl ArgBuilder {
24 pub fn new(name: ConfigKey, long: String) -> Self {
25 ArgBuilder {
26 name,
27 long,
28 help: None,
29 takes_value: false,
30 validate: None,
31 }
32 }
33
34 pub(crate) fn name(&self) -> &str {
35 self.name.as_ref()
36 }
37
38 pub fn help(&mut self, help: Option<&'static str>) {
39 self.help = help;
40 }
41
42 pub fn get_help(&self) -> Option<&str> {
43 match self.help {
44 None => None,
45 Some(s) => Some(s),
46 }
47 }
48
49 pub fn takes_value(&mut self, takes_value: bool) {
50 self.takes_value = takes_value;
51 }
52
53 pub fn validate<F>(&mut self, f: F)
54 where
55 F: Fn(String) -> Result<(), String> + 'static,
56 {
57 self.validate = Some(Rc::new(f));
58 }
59
60 pub(crate) fn partial_clone(&self) -> Self {
61 ArgBuilder {
62 name: self.name.clone(),
63 long: self.long.clone(),
64 help: self.help,
65 takes_value: self.takes_value,
66 validate: None,
67 }
68 }
69}
70
71impl<'a, 'b, 'c> Into<Arg<'a, 'b>> for &'c ArgBuilder
72where
73 'a: 'b,
74 'c: 'a,
75{
76 fn into(self) -> Arg<'a, 'b> {
77 let mut arg = Arg::with_name(self.name.as_ref())
78 .long(self.long.as_ref())
79 .takes_value(self.takes_value);
80
81 if let Some(help) = self.help {
82 arg = arg.help(help);
83 }
84
85 if let Some(validate) = &self.validate {
86 let validate = validate.clone();
87 arg = arg.validator(move |val| validate(val));
88 }
89
90 if self.takes_value {
91 arg = arg.value_name(self.name.as_ref());
92 }
93
94 arg
95 }
96}
97
98#[derive(Default)]
99pub struct AppBuilder {
100 args: Vec<ArgBuilder>,
101}
102
103impl AppBuilder {
105 pub fn new() -> Self {
106 AppBuilder { args: Vec::new() }
107 }
108
109 pub fn arg(&mut self, arg: ArgBuilder) {
110 self.args.push(arg);
111 }
112
113 pub fn adopt(&mut self, builder: AppBuilder) {
114 for arg in builder.args.into_iter() {
115 self.arg(arg);
116 }
117 }
118
119 pub fn variants(&mut self, variants: &[AppBuilder]) {
120 let mut args = HashMap::new();
121 for variant in variants.iter() {
122 for arg in variant.args.iter() {
123 let name = arg.name();
124 let variants = match args.get_mut(name) {
125 None => {
126 args.insert(name, Vec::new());
127 args.get_mut(name).unwrap()
128 }
129
130 Some(v) => v,
131 };
132
133 variants.push(arg);
134 }
135 }
136
137 for (_name, args) in args.into_iter() {
138 let arg = args.first().unwrap().partial_clone();
140 self.arg(arg);
141 }
142 }
143}
144
145pub trait ClapConfig {
146 fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>);
147 fn populate<'a, 'c>(
148 builder: &mut ConfigurationProviderBuilder,
149 matches: &ArgMatches<'a>,
150 context: Context<'c>,
151 );
152}
153
154pub trait AppExt {
155 fn get_config<T: ClapConfig>(self) -> DefaultConfigurationProvider;
156 fn get_config_from_args<T, I, S>(self, args: I) -> clap::Result<DefaultConfigurationProvider>
157 where
158 T: ClapConfig,
159 I: IntoIterator<Item = S>,
160 S: Into<OsString> + Clone;
161}
162
163pub trait ClapConfigExt: ClapConfig + Sized {
164 fn from_cli<'a, 'b>(app: App<'a, 'b>) -> DefaultConfigurationProvider
165 where
166 'a: 'b,
167 {
168 app.get_config::<Self>()
169 }
170
171 fn from_cli_args<'a, 'b, I, S>(
172 app: App<'a, 'b>,
173 args: I,
174 ) -> clap::Result<DefaultConfigurationProvider>
175 where
176 'a: 'b,
177 I: IntoIterator<Item = S>,
178 S: Into<OsString> + Clone,
179 {
180 app.get_config_from_args::<Self, I, S>(args)
181 }
182}
183
184impl<T: ClapConfig + Sized> ClapConfigExt for T {}
185
186impl<'a, 'b> AppExt for App<'a, 'b>
187where
188 'a: 'b,
189{
190 fn get_config<T: ClapConfig>(self) -> DefaultConfigurationProvider {
191 let mut builder = AppBuilder::new();
192 T::configure(&mut builder, Context::new(), None);
193
194 let mut app = self;
195 for arg in builder.args.iter() {
196 app = app.arg(arg);
197 }
198
199 let mut builder = ConfigurationProviderBuilder::new();
200 let matches = app.get_matches();
201 T::populate(&mut builder, &matches, Context::new());
203
204 builder.build()
205 }
206
207 fn get_config_from_args<T, I, S>(self, args: I) -> clap::Result<DefaultConfigurationProvider>
208 where
209 T: ClapConfig,
210 I: IntoIterator<Item = S>,
211 S: Into<OsString> + Clone,
212 {
213 let mut builder = AppBuilder::new();
214 T::configure(&mut builder, Context::new(), None);
215
216 let mut app = self;
217 for arg in builder.args.iter() {
218 app = app.arg(arg);
219 }
220
221 let mut builder = ConfigurationProviderBuilder::new();
222 let matches = app.get_matches_from_safe(args)?;
223 T::populate(&mut builder, &matches, Context::new());
224
225 Ok(builder.build())
226 }
227}
228
229pub mod paths {
230 use crate::Context;
231 use preftool::ConfigKey;
232
233 pub fn arg_name(context: &Context<'_>) -> ConfigKey {
234 context.join_path(ConfigKey::separator()).into()
235 }
236
237 pub fn arg_long(context: &Context<'_>) -> String {
238 context.join_path("-")
239 }
240}
241
242impl ClapConfig for String {
243 fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>) {
244 let name = paths::arg_name(&context);
245 let long = paths::arg_long(&context);
246 let mut arg = ArgBuilder::new(name, long);
247 arg.help(help);
248 arg.takes_value(true);
249
250 app.arg(arg);
251 }
252
253 fn populate<'a, 'c>(
254 builder: &mut ConfigurationProviderBuilder,
255 matches: &ArgMatches<'a>,
256 context: Context<'c>,
257 ) {
258 let name = paths::arg_name(&context);
259 match matches.value_of(&name) {
260 None => (),
261 Some(v) => {
262 builder.add(name, v);
263 }
264 }
265 }
266}
267
268impl<T: ClapConfig> ClapConfig for Option<T> {
269 fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>) {
270 T::configure(app, context.optional(), help);
271 }
272
273 fn populate<'a, 'c>(
274 builder: &mut ConfigurationProviderBuilder,
275 matches: &ArgMatches<'a>,
276 context: Context<'c>,
277 ) {
278 T::populate(builder, matches, context.optional());
279 }
280}
281
282impl ClapConfig for usize {
283 fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>) {
284 let name = paths::arg_name(&context);
285 let long = paths::arg_long(&context);
286 let mut arg = ArgBuilder::new(name, long);
287 arg.help(help);
288 arg.takes_value(true);
289 arg.validate(|s| match s.parse::<usize>() {
290 Ok(_) => Ok(()),
291 Err(e) => Err(format!("{:?}", e)),
292 });
293
294 app.arg(arg);
295 }
296
297 fn populate<'a, 'c>(
298 builder: &mut ConfigurationProviderBuilder,
299 matches: &ArgMatches<'a>,
300 context: Context<'c>,
301 ) {
302 let name = paths::arg_name(&context);
303 match matches.value_of(&name) {
304 None => (),
305 Some(v) => {
306 builder.add(name, v);
307 }
308 }
309 }
310}
311
312mod context;