extern crate clap;
extern crate preftool;
#[macro_use]
extern crate bitflags;
use clap::{App, Arg, ArgMatches};
use preftool::*;
use std::collections::HashMap;
use std::ffi::OsString;
use std::rc::Rc;
pub type Validator = dyn Fn(String) -> Result<(), String>;
pub use context::Context;
pub struct ArgBuilder {
name: ConfigKey,
long: String,
help: Option<&'static str>,
takes_value: bool,
validate: Option<Rc<Validator>>,
}
impl ArgBuilder {
pub fn new(name: ConfigKey, long: String) -> Self {
ArgBuilder {
name,
long,
help: None,
takes_value: false,
validate: None,
}
}
pub(crate) fn name(&self) -> &str {
self.name.as_ref()
}
pub fn help(&mut self, help: Option<&'static str>) {
self.help = help;
}
pub fn get_help(&self) -> Option<&str> {
match self.help {
None => None,
Some(s) => Some(s),
}
}
pub fn takes_value(&mut self, takes_value: bool) {
self.takes_value = takes_value;
}
pub fn validate<F>(&mut self, f: F)
where
F: Fn(String) -> Result<(), String> + 'static,
{
self.validate = Some(Rc::new(f));
}
pub(crate) fn partial_clone(&self) -> Self {
ArgBuilder {
name: self.name.clone(),
long: self.long.clone(),
help: self.help,
takes_value: self.takes_value,
validate: None,
}
}
}
impl<'a, 'b, 'c> Into<Arg<'a, 'b>> for &'c ArgBuilder
where
'a: 'b,
'c: 'a,
{
fn into(self) -> Arg<'a, 'b> {
let mut arg = Arg::with_name(self.name.as_ref())
.long(self.long.as_ref())
.takes_value(self.takes_value);
if let Some(help) = self.help {
arg = arg.help(help);
}
if let Some(validate) = &self.validate {
let validate = validate.clone();
arg = arg.validator(move |val| validate(val));
}
if self.takes_value {
arg = arg.value_name(self.name.as_ref());
}
arg
}
}
#[derive(Default)]
pub struct AppBuilder {
args: Vec<ArgBuilder>,
}
impl AppBuilder {
pub fn new() -> Self {
AppBuilder { args: Vec::new() }
}
pub fn arg(&mut self, arg: ArgBuilder) {
self.args.push(arg);
}
pub fn adopt(&mut self, builder: AppBuilder) {
for arg in builder.args.into_iter() {
self.arg(arg);
}
}
pub fn variants(&mut self, variants: &[AppBuilder]) {
let mut args = HashMap::new();
for variant in variants.iter() {
for arg in variant.args.iter() {
let name = arg.name();
let variants = match args.get_mut(name) {
None => {
args.insert(name, Vec::new());
args.get_mut(name).unwrap()
}
Some(v) => v,
};
variants.push(arg);
}
}
for (_name, args) in args.into_iter() {
let arg = args.first().unwrap().partial_clone();
self.arg(arg);
}
}
}
pub trait ClapConfig {
fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>);
fn populate<'a, 'c>(
builder: &mut ConfigurationProviderBuilder,
matches: &ArgMatches<'a>,
context: Context<'c>,
);
}
pub trait AppExt {
fn get_config<T: ClapConfig>(self) -> DefaultConfigurationProvider;
fn get_config_from_args<T, I, S>(self, args: I) -> clap::Result<DefaultConfigurationProvider>
where
T: ClapConfig,
I: IntoIterator<Item = S>,
S: Into<OsString> + Clone;
}
pub trait ClapConfigExt: ClapConfig + Sized {
fn from_cli<'a, 'b>(app: App<'a, 'b>) -> DefaultConfigurationProvider
where
'a: 'b,
{
app.get_config::<Self>()
}
fn from_cli_args<'a, 'b, I, S>(
app: App<'a, 'b>,
args: I,
) -> clap::Result<DefaultConfigurationProvider>
where
'a: 'b,
I: IntoIterator<Item = S>,
S: Into<OsString> + Clone,
{
app.get_config_from_args::<Self, I, S>(args)
}
}
impl<T: ClapConfig + Sized> ClapConfigExt for T {}
impl<'a, 'b> AppExt for App<'a, 'b>
where
'a: 'b,
{
fn get_config<T: ClapConfig>(self) -> DefaultConfigurationProvider {
let mut builder = AppBuilder::new();
T::configure(&mut builder, Context::new(), None);
let mut app = self;
for arg in builder.args.iter() {
app = app.arg(arg);
}
let mut builder = ConfigurationProviderBuilder::new();
let matches = app.get_matches();
T::populate(&mut builder, &matches, Context::new());
builder.build()
}
fn get_config_from_args<T, I, S>(self, args: I) -> clap::Result<DefaultConfigurationProvider>
where
T: ClapConfig,
I: IntoIterator<Item = S>,
S: Into<OsString> + Clone,
{
let mut builder = AppBuilder::new();
T::configure(&mut builder, Context::new(), None);
let mut app = self;
for arg in builder.args.iter() {
app = app.arg(arg);
}
let mut builder = ConfigurationProviderBuilder::new();
let matches = app.get_matches_from_safe(args)?;
T::populate(&mut builder, &matches, Context::new());
Ok(builder.build())
}
}
pub mod paths {
use crate::Context;
use preftool::ConfigKey;
pub fn arg_name(context: &Context<'_>) -> ConfigKey {
context.join_path(ConfigKey::separator()).into()
}
pub fn arg_long(context: &Context<'_>) -> String {
context.join_path("-")
}
}
impl ClapConfig for String {
fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>) {
let name = paths::arg_name(&context);
let long = paths::arg_long(&context);
let mut arg = ArgBuilder::new(name, long);
arg.help(help);
arg.takes_value(true);
app.arg(arg);
}
fn populate<'a, 'c>(
builder: &mut ConfigurationProviderBuilder,
matches: &ArgMatches<'a>,
context: Context<'c>,
) {
let name = paths::arg_name(&context);
match matches.value_of(&name) {
None => (),
Some(v) => {
builder.add(name, v);
}
}
}
}
impl<T: ClapConfig> ClapConfig for Option<T> {
fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>) {
T::configure(app, context.optional(), help);
}
fn populate<'a, 'c>(
builder: &mut ConfigurationProviderBuilder,
matches: &ArgMatches<'a>,
context: Context<'c>,
) {
T::populate(builder, matches, context.optional());
}
}
impl ClapConfig for usize {
fn configure<'c>(app: &mut AppBuilder, context: Context<'c>, help: Option<&'static str>) {
let name = paths::arg_name(&context);
let long = paths::arg_long(&context);
let mut arg = ArgBuilder::new(name, long);
arg.help(help);
arg.takes_value(true);
arg.validate(|s| match s.parse::<usize>() {
Ok(_) => Ok(()),
Err(e) => Err(format!("{:?}", e)),
});
app.arg(arg);
}
fn populate<'a, 'c>(
builder: &mut ConfigurationProviderBuilder,
matches: &ArgMatches<'a>,
context: Context<'c>,
) {
let name = paths::arg_name(&context);
match matches.value_of(&name) {
None => (),
Some(v) => {
builder.add(name, v);
}
}
}
}
mod context;