From ac52668a3df9cd3edefa1c8a333885b7d7b9d172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Frosteg=C3=A5rd?= Date: Thu, 9 Apr 2020 16:46:35 +0200 Subject: [PATCH] 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. --- Cargo.lock | 38 ++++++++++++++++++ Cargo.toml | 3 +- aquatic/Cargo.toml | 2 + aquatic/src/bin/aquatic.rs | 6 ++- aquatic/src/lib/config.rs | 12 +++--- aquatic/src/lib/lib.rs | 3 +- cli_helpers/Cargo.toml | 11 ++++++ cli_helpers/src/lib.rs | 80 ++++++++++++++++++++++++++++++++++++++ scripts/run-server.sh | 2 +- 9 files changed, 147 insertions(+), 10 deletions(-) create mode 100644 cli_helpers/Cargo.toml create mode 100644 cli_helpers/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d67a7ed..232db2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index 0f76a4e..b7a5993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,10 @@ [workspace] members = [ - "bittorrent_udp", "aquatic", "aquatic_bench", + "bittorrent_udp", + "cli_helpers", ] [profile.release] diff --git a/aquatic/Cargo.toml b/aquatic/Cargo.toml index 4767d61..4a1ec85 100644 --- a/aquatic/Cargo.toml +++ b/aquatic/Cargo.toml @@ -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" diff --git a/aquatic/src/bin/aquatic.rs b/aquatic/src/bin/aquatic.rs index 943c13e..7c44968 100644 --- a/aquatic/src/bin/aquatic.rs +++ b/aquatic/src/bin/aquatic.rs @@ -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, + ) } \ No newline at end of file diff --git a/aquatic/src/lib/config.rs b/aquatic/src/lib/config.rs index 5a67152..c6fe9f4 100644 --- a/aquatic/src/lib/config.rs +++ b/aquatic/src/lib/config.rs @@ -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, @@ -88,4 +90,4 @@ impl Default for CleaningConfig { max_connection_age: 60 * 5, } } -} \ No newline at end of file +} diff --git a/aquatic/src/lib/lib.rs b/aquatic/src/lib/lib.rs index 63f6e50..3175388 100644 --- a/aquatic/src/lib/lib.rs +++ b/aquatic/src/lib/lib.rs @@ -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 { diff --git a/cli_helpers/Cargo.toml b/cli_helpers/Cargo.toml new file mode 100644 index 0000000..12287db --- /dev/null +++ b/cli_helpers/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "cli_helpers" +version = "0.1.0" +authors = ["Joakim FrostegÄrd "] +edition = "2018" + +[dependencies] +anyhow = "1" +gumdrop = "0.7" +serde = { version = "1", features = ["derive"] } +toml = "0.5" \ No newline at end of file diff --git a/cli_helpers/src/lib.rs b/cli_helpers/src/lib.rs new file mode 100644 index 0000000..8098c41 --- /dev/null +++ b/cli_helpers/src/lib.rs @@ -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, + #[options(help = "print default config file")] + print_config: bool, + #[options(help = "print help message")] + help: bool, +} + + +fn config_from_toml_file(path: String) -> anyhow::Result + 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() -> 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( + title: &str, + // Function that takes config file and runs application + f: F, +) where T: Default + Serialize + DeserializeOwned, F: Fn(T) { + let args: Vec = ::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::()); + } 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()); +} \ No newline at end of file diff --git a/scripts/run-server.sh b/scripts/run-server.sh index fd186da..b2534ef 100755 --- a/scripts/run-server.sh +++ b/scripts/run-server.sh @@ -2,4 +2,4 @@ export RUSTFLAGS="-C target-cpu=native" -cargo run --release --bin aquatic \ No newline at end of file +cargo run --release --bin aquatic -- $@