Better command line argument parsing

Use structs and enums instead of builder style options.
This commit is contained in:
finga 2021-06-30 23:55:21 +02:00
parent 5d20366e5d
commit 34e3e3f32a

View file

@ -1,7 +1,7 @@
#![feature(proc_macro_hygiene, decl_macro)] #![feature(proc_macro_hygiene, decl_macro)]
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use clap::{app_from_crate, App, Arg}; use clap::{crate_authors, crate_version, AppSettings, Clap};
use hmac::{Hmac, Mac, NewMac}; use hmac::{Hmac, Mac, NewMac};
use ipnet::IpNet; use ipnet::IpNet;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
@ -54,6 +54,27 @@ enum WebhookeyError {
Regex(regex::Error), Regex(regex::Error),
} }
#[derive(Clap, Debug)]
enum Command {
/// Verifies if the configuration can be parsed without errors
Configtest,
}
#[derive(Clap, Debug)]
#[clap(
version = crate_version!(),
author = crate_authors!(", "),
global_setting = AppSettings::VersionlessSubcommands,
global_setting = AppSettings::InferSubcommands,
)]
struct Opts {
/// Provide a path to the configuration file
#[clap(short, long, value_name = "FILE")]
config: Option<String>,
#[clap(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields, untagged)] #[serde(deny_unknown_fields, untagged)]
enum AddrType { enum AddrType {
@ -86,6 +107,12 @@ impl IpFilter {
} }
} }
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct Config {
hooks: HashMap<String, Hook>,
}
#[derive(Debug, Deserialize, Serialize)] #[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
struct JsonFilter { struct JsonFilter {
@ -319,12 +346,6 @@ impl FromDataSimple for Hooks {
} }
} }
#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
struct Config {
hooks: HashMap<String, Hook>,
}
fn accept_ip(hook_name: &str, client_ip: &IpAddr, ip: &IpFilter) -> bool { fn accept_ip(hook_name: &str, client_ip: &IpAddr, ip: &IpFilter) -> bool {
if ip.validate(client_ip) { if ip.validate(client_ip) {
info!("Allow hook `{}` from {}", &hook_name, &client_ip); info!("Allow hook `{}` from {}", &hook_name, &client_ip);
@ -475,29 +496,16 @@ fn get_config() -> Result<File> {
fn main() -> Result<()> { fn main() -> Result<()> {
env_logger::init(); env_logger::init();
let cli = app_from_crate!() let cli: Opts = Opts::parse();
.arg(
Arg::new("config")
.short('c')
.long("config")
.takes_value(true)
.value_name("FILE")
.about("Provide a path to the configuration file"),
)
.subcommand(
App::new("configtest")
.about("Verifies if the configuration can be parsed without errors"),
)
.get_matches();
let config: Config = match cli.value_of("config") { let config: Config = match cli.config {
Some(config) => serde_yaml::from_reader(BufReader::new(File::open(config)?))?, Some(config) => serde_yaml::from_reader(BufReader::new(File::open(config)?))?,
_ => serde_yaml::from_reader(BufReader::new(get_config()?))?, _ => serde_yaml::from_reader(BufReader::new(get_config()?))?,
}; };
trace!("Parsed configuration:\n{}", serde_yaml::to_string(&config)?); trace!("Parsed configuration:\n{}", serde_yaml::to_string(&config)?);
if cli.subcommand_matches("configtest").is_some() { if cli.command.is_some() {
debug!("Configtest succeded."); debug!("Configtest succeded.");
println!("Config is OK"); println!("Config is OK");
return Ok(()); return Ok(());