Skip to main content

preftool_clap/
lib.rs

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
103// TODO: This should be a trait, so config providers could modify behaivour for "children".
104impl 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      // TODO: Validate that HELP text is the same
139      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    // println!("Matches: {:#?}", matches);
202    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;