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

38
Cargo.lock generated
View file

@ -18,11 +18,18 @@ dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9a60d744a80c30fcb657dfe2c1b22bcb3e814c1a1e3674f32bf5820b570fbff"
[[package]]
name = "aquatic"
version = "0.1.0"
dependencies = [
"bittorrent_udp",
"cli_helpers",
"dashmap",
"histogram",
"indexmap",
@ -32,6 +39,7 @@ dependencies = [
"quickcheck",
"quickcheck_macros",
"rand",
"serde",
]
[[package]]
@ -142,6 +150,16 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cli_helpers"
version = "0.1.0"
dependencies = [
"anyhow",
"gumdrop",
"serde",
"toml",
]
[[package]]
name = "clicolors-control"
version = "1.0.1"
@ -237,6 +255,26 @@ dependencies = [
"wasi",
]
[[package]]
name = "gumdrop"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee50908bc1beeac1f2902e0b4e0cd0d844e716f5ebdc6f0cfc1163fe5e10bcde"
dependencies = [
"gumdrop_derive",
]
[[package]]
name = "gumdrop_derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90454ce4de40b7ca6a8968b5ef367bdab48413962588d0d2b1638d60090c35d7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "hermit-abi"
version = "0.1.10"

View file

@ -1,9 +1,10 @@
[workspace]
members = [
"bittorrent_udp",
"aquatic",
"aquatic_bench",
"bittorrent_udp",
"cli_helpers",
]
[profile.release]

View file

@ -13,6 +13,7 @@ name = "aquatic"
[dependencies]
bittorrent_udp = { path = "../bittorrent_udp" }
cli_helpers = { path = "../cli_helpers" }
dashmap = "3"
histogram = "0.6"
indexmap = "1"
@ -20,6 +21,7 @@ mimalloc = { version = "0.1", default-features = false }
mio = { version = "0.7", features = ["udp", "os-poll", "os-util"] }
net2 = "0.2"
rand = { version = "0.7", features = ["small_rng"] }
serde = { version = "1", features = ["derive"] }
[dev-dependencies]
quickcheck = "0.9"

View file

@ -1,4 +1,5 @@
use aquatic;
use cli_helpers;
#[global_allocator]
@ -6,5 +7,8 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
fn main(){
aquatic::run();
cli_helpers::run_with_cli_and_config(
"aquatic: udp bittorrent tracker",
aquatic::run,
)
}

View file

@ -1,7 +1,9 @@
use std::net::SocketAddr;
use serde::{Serialize, Deserialize};
#[derive(Clone)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Config {
/// Spawn this number of threads for workers
pub num_threads: usize,
@ -11,7 +13,7 @@ pub struct Config {
}
#[derive(Clone)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NetworkConfig {
/// Bind to this address
pub address: SocketAddr,
@ -27,14 +29,14 @@ pub struct NetworkConfig {
}
#[derive(Clone)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StatisticsConfig {
/// Print statistics this often (seconds). Don't print when set to zero.
pub interval: u64,
}
#[derive(Clone)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CleaningConfig {
/// Clean torrents and connections this often (seconds)
pub interval: u64,

View file

@ -10,8 +10,7 @@ use config::Config;
use common::State;
pub fn run(){
let config = Config::default();
pub fn run(config: Config){
let state = State::new();
for i in 0..config.num_threads {

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());
}

View file

@ -2,4 +2,4 @@
export RUSTFLAGS="-C target-cpu=native"
cargo run --release --bin aquatic
cargo run --release --bin aquatic -- $@