add crate cli_helpers with option and config parsing; use in aquatic

Putting cli functionality into its own crate will allow using it
from aquatic_bench and possibly other programs.
This commit is contained in:
Joakim Frostegård 2020-04-09 16:46:35 +02:00
parent 06756f1c74
commit ac52668a3d
9 changed files with 147 additions and 10 deletions

11
cli_helpers/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "cli_helpers"
version = "0.1.0"
authors = ["Joakim Frostegård <joakim.frostegard@gmail.com>"]
edition = "2018"
[dependencies]
anyhow = "1"
gumdrop = "0.7"
serde = { version = "1", features = ["derive"] }
toml = "0.5"

80
cli_helpers/src/lib.rs Normal file
View file

@ -0,0 +1,80 @@
use std::fs::File;
use std::io::Read;
use anyhow;
use gumdrop::Options;
use serde::{Serialize, de::DeserializeOwned};
use toml;
#[derive(Debug, Options)]
struct AppOptions {
#[options(help = "run with config file", short = "c", meta = "PATH")]
config_file: Option<String>,
#[options(help = "print default config file")]
print_config: bool,
#[options(help = "print help message")]
help: bool,
}
fn config_from_toml_file<T>(path: String) -> anyhow::Result<T>
where T: DeserializeOwned
{
let mut file = File::open(path)?;
let mut data = String::new();
file.read_to_string(&mut data)?;
toml::from_str(&data).map_err(|e| anyhow::anyhow!("Parse failed: {}", e))
}
fn default_config_as_toml<T>() -> String
where T: Default + Serialize
{
toml::to_string_pretty(&T::default())
.expect("Could not serialize default config to toml")
}
pub fn run_with_cli_and_config<T, F>(
title: &str,
// Function that takes config file and runs application
f: F,
) where T: Default + Serialize + DeserializeOwned, F: Fn(T) {
let args: Vec<String> = ::std::env::args().collect();
match AppOptions::parse_args_default(&args[1..]){
Ok(opts) => {
if opts.help_requested(){
print_help(title, None);
} else if opts.print_config {
print!("{}", default_config_as_toml::<T>());
} else if let Some(config_file) = opts.config_file {
match config_from_toml_file(config_file){
Ok(config) => f(config),
Err(err) => {
eprintln!("Error while reading config file: {}", err);
::std::process::exit(1);
}
}
} else {
f(T::default())
}
},
Err(err) => {
print_help(title, Some(&format!("{}", err)))
}
}
}
fn print_help(title: &str, opt_error: Option<&str>){
println!("{}", title);
if let Some(error) = opt_error {
println!("\nError: {}.", error);
}
println!("\n{}", AppOptions::usage());
}