From cc50b48ba86afda3d81491ca86e4d244a29cf9ad Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Sat, 1 Dec 2018 15:46:16 +0100 Subject: [PATCH 1/4] Add an `expandconfig` utility to add default values This patch adds an `expandconfig` binary which is able to load a configuration file, and write it back (in either TOML or YAML format) with the default values expanded. This is useful in a variety of scenarios: - Check that a configuration file is valid with: expandconfig /path/to/config.toml - See default field values and structure with: expandconfig - See all available fields with (YAML displays fields with a default `None` value while TOML skips them): expandconfig -f yaml --- Cargo.toml | 4 ++ src/bin/expandconfig.rs | 113 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 src/bin/expandconfig.rs diff --git a/Cargo.toml b/Cargo.toml index 23dbb93ad..69694f6a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ name = "bench-perf-model" path = "tools/bench_perf_model/main.rs" required-features = ["cuda"] +[[bin]] +name = "expandconfig" + [[bench]] name = "descent" harness = false @@ -76,6 +79,7 @@ rpds = "0.5.0" serde = "1.0" serde_derive = "1.0" serde_json = "1.0.22" +serde_yaml = "0.8" bincode = "1.0" std-semaphore = "0.1.0" tempfile = "3.0.1" diff --git a/src/bin/expandconfig.rs b/src/bin/expandconfig.rs new file mode 100644 index 000000000..dceb27965 --- /dev/null +++ b/src/bin/expandconfig.rs @@ -0,0 +1,113 @@ +extern crate getopts; +extern crate serde_yaml; +extern crate toml; + +extern crate telamon; + +use std::env; +use std::io::{Read, Write}; + +use getopts::Options; + +enum Format { + Toml, + Yaml, +} + +/// Expand a configuration. +/// +/// # Arguments +/// +/// - input: Path to the input file to use. If `None`, the default configuration is loaded. If +/// "-", the configuration is read from the standard input. +/// +/// - output: Path to the output file. If "-", the configuration is printed to the standard +/// output. +fn expand_config(input: Option<&str>, output: &str, format: Format) { + let input_str = input + .map(|input| { + let mut input_str = String::new(); + match input { + "-" => { + let stdin = std::io::stdin(); + stdin.lock().read_to_string(&mut input_str).unwrap(); + } + _ => { + std::fs::File::open(input) + .unwrap() + .read_to_string(&mut input_str) + .unwrap(); + } + }; + input_str + }).unwrap_or_else(|| "".to_string()); + + let config: telamon::explorer::config::Config = toml::from_str(&input_str).unwrap(); + + let output_str = match format { + Format::Toml => toml::to_string(&config).unwrap(), + Format::Yaml => serde_yaml::to_string(&config).unwrap(), + }; + + match output { + "-" => print!("{}", output_str), + _ => { + write!(std::fs::File::create(output).unwrap(), "{}", output_str); + } + } +} + +fn print_usage(program: &str, opts: Options) { + let brief = format!( + "Usage: {} PATH [options] + +Loads an explorer configuration file and outputs it with the default values added. This can also +be used as a simple validator of an explorer configuration file by discarding the output. +", + program + ); + print!("{}", opts.usage(&brief)); +} + +fn main() { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optflag("h", "help", "Prints help information"); + opts.optopt("o", "output", "Path to the output file", "PATH"); + opts.optopt("f", "format", "Output format", "[toml|yaml]"); + + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => panic!(f.to_string()), + }; + + if matches.opt_present("h") { + print_usage(&program, opts); + return; + } + + let output = matches.opt_str("o").unwrap_or_else(|| "-".to_string()); + let input = if !matches.free.is_empty() { + Some(&matches.free[0][..]) + } else { + None + }; + + let format = match matches + .opt_str("f") + .unwrap_or_else(|| "toml".to_string()) + .to_lowercase() + .as_ref() + { + "yaml" => Format::Yaml, + "toml" => Format::Toml, + _ => { + print_usage(&program, opts); + return; + } + }; + + expand_config(input, &output, format); +} From 8181ac2d19e8a849a4d816ef9ddb6a430a60da34 Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Mon, 3 Dec 2018 14:04:28 +0100 Subject: [PATCH 2/4] Convert `expandconfig` to use structopt --- Cargo.toml | 1 + src/bin/expandconfig.rs | 241 +++++++++++++++++++++++++--------------- 2 files changed, 152 insertions(+), 90 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 69694f6a6..0fcedbc66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ serde = "1.0" serde_derive = "1.0" serde_json = "1.0.22" serde_yaml = "0.8" +structopt = "0.2" bincode = "1.0" std-semaphore = "0.1.0" tempfile = "3.0.1" diff --git a/src/bin/expandconfig.rs b/src/bin/expandconfig.rs index dceb27965..dc41f881b 100644 --- a/src/bin/expandconfig.rs +++ b/src/bin/expandconfig.rs @@ -1,113 +1,174 @@ -extern crate getopts; extern crate serde_yaml; extern crate toml; +extern crate structopt; + extern crate telamon; -use std::env; -use std::io::{Read, Write}; +use std::ffi::{OsStr, OsString}; +use std::fmt; +use std::fs; +use std::io; +use std::io::prelude::*; + +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt( + name = "expandconfig", + about = " +Loads an explorer configuration file and outputs it with the default values added. This can also +be used as a simple validator of an explorer configuration file by discarding the output. +", + raw(setting = "structopt::clap::AppSettings::ColoredHelp") +)] +struct Opt { + /// Path to the input file. Use default config if not specified. + #[structopt(parse(from_os_str))] + input: Option, + + /// Path to the output file. Use stdout if not specified. + #[structopt( + short = "o", + long = "output", + default_value = "-", + parse(from_os_str) + )] + output: OsString, + + /// Output format. + #[structopt( + short = "f", + long = "format", + default_value = "toml", + possible_value = "toml", + possible_value = "yaml" + )] + format: Format, +} + +impl Opt { + /// Open the input file and returns a `io::Read` handle to it + fn open_input(&self) -> io::Result>> { + match self.input.as_ref().map(|input| try_parse_read(&*input)) { + Some(Err(err)) => Err(err), + Some(Ok(input)) => Ok(Some(input)), + None => Ok(None), + } + } + + /// Create the output file and returns a `io::Write` handle to it + fn create_output(&self) -> io::Result> { + try_parse_write(&*self.output) + } -use getopts::Options; + /// Parse the existing configuration + fn parse_config(&self) -> io::Result { + let input_str = match self.open_input()? { + Some(mut input) => { + let mut input_str = String::new(); + input.read_to_string(&mut input_str)?; + input_str + } + None => "".to_string(), + }; + + Ok(toml::from_str(&input_str).map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("TOML deserialization error: {}", err), + ) + })?) + } + /// Dump a configuration in the requested format + fn dump_config(&self, config: &telamon::explorer::config::Config) -> io::Result<()> { + let output_str = match self.format { + Format::Toml => toml::to_string(config).map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("TOML serialization error: {}", err), + ) + })?, + Format::Yaml => serde_yaml::to_string(config).map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("YAML serialization error: {}", err), + ) + })?, + }; + + write!(self.create_output()?, "{}", output_str); + + Ok(()) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Format { Toml, Yaml, } -/// Expand a configuration. -/// -/// # Arguments -/// -/// - input: Path to the input file to use. If `None`, the default configuration is loaded. If -/// "-", the configuration is read from the standard input. -/// -/// - output: Path to the output file. If "-", the configuration is printed to the standard -/// output. -fn expand_config(input: Option<&str>, output: &str, format: Format) { - let input_str = input - .map(|input| { - let mut input_str = String::new(); - match input { - "-" => { - let stdin = std::io::stdin(); - stdin.lock().read_to_string(&mut input_str).unwrap(); - } - _ => { - std::fs::File::open(input) - .unwrap() - .read_to_string(&mut input_str) - .unwrap(); - } - }; - input_str - }).unwrap_or_else(|| "".to_string()); - - let config: telamon::explorer::config::Config = toml::from_str(&input_str).unwrap(); - - let output_str = match format { - Format::Toml => toml::to_string(&config).unwrap(), - Format::Yaml => serde_yaml::to_string(&config).unwrap(), - }; - - match output { - "-" => print!("{}", output_str), - _ => { - write!(std::fs::File::create(output).unwrap(), "{}", output_str); +impl fmt::Display for Format { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use Format::*; + + match self { + Toml => write!(f, "toml"), + Yaml => write!(f, "yaml"), } } } -fn print_usage(program: &str, opts: Options) { - let brief = format!( - "Usage: {} PATH [options] - -Loads an explorer configuration file and outputs it with the default values added. This can also -be used as a simple validator of an explorer configuration file by discarding the output. -", - program - ); - print!("{}", opts.usage(&brief)); +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseFormatError { + _priv: (), } -fn main() { - let args: Vec = env::args().collect(); - let program = args[0].clone(); - - let mut opts = Options::new(); - opts.optflag("h", "help", "Prints help information"); - opts.optopt("o", "output", "Path to the output file", "PATH"); - opts.optopt("f", "format", "Output format", "[toml|yaml]"); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => panic!(f.to_string()), - }; - - if matches.opt_present("h") { - print_usage(&program, opts); - return; +impl fmt::Display for ParseFormatError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + "valid formats are `toml` and `yaml`".fmt(f) } +} - let output = matches.opt_str("o").unwrap_or_else(|| "-".to_string()); - let input = if !matches.free.is_empty() { - Some(&matches.free[0][..]) - } else { - None - }; - - let format = match matches - .opt_str("f") - .unwrap_or_else(|| "toml".to_string()) - .to_lowercase() - .as_ref() - { - "yaml" => Format::Yaml, - "toml" => Format::Toml, - _ => { - print_usage(&program, opts); - return; +impl std::str::FromStr for Format { + type Err = ParseFormatError; + + fn from_str(s: &str) -> Result { + use Format::*; + + match &*s.to_lowercase() { + "toml" => Ok(Toml), + "yaml" => Ok(Yaml), + _ => Err(ParseFormatError { _priv: () }), } - }; + } +} + +/// Parse a `io::Read` instance from a path. This +fn try_parse_read(source: &OsStr) -> io::Result> { + match source.to_str() { + Some("-") => Ok(Box::new(io::stdin())), + _ => Ok(Box::new(fs::File::open(source)?)), + } +} + +/// Parse a `io::Write` instance from a path. +fn try_parse_write(source: &OsStr) -> io::Result> { + match source.to_str() { + Some("-") => Ok(Box::new(io::stdout())), + _ => Ok(Box::new(fs::File::create(source)?)), + } +} + +fn main() { + let opt = Opt::from_args(); + + let config = opt + .parse_config() + .expect("Unable to parse configuration file"); - expand_config(input, &output, format); + opt.dump_config(&config) + .expect("Unable to dump configuration file"); } From dc33b2d8ff3961faeac6f0cee10a648d3446fedf Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Mon, 3 Dec 2018 14:23:50 +0100 Subject: [PATCH 3/4] Cleanup format parsing --- src/bin/expandconfig.rs | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/bin/expandconfig.rs b/src/bin/expandconfig.rs index dc41f881b..afd091d22 100644 --- a/src/bin/expandconfig.rs +++ b/src/bin/expandconfig.rs @@ -41,8 +41,8 @@ struct Opt { short = "f", long = "format", default_value = "toml", - possible_value = "toml", - possible_value = "yaml" + case_insensitive = true, + raw(possible_values = "Format::VARIANTS"), )] format: Format, } @@ -110,6 +110,10 @@ enum Format { Yaml, } +impl Format { + const VARIANTS: &'static [&'static str; 2] = &["toml", "yaml"]; +} + impl fmt::Display for Format { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use Format::*; @@ -121,31 +125,33 @@ impl fmt::Display for Format { } } -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ParseFormatError { - _priv: (), -} - -impl fmt::Display for ParseFormatError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - "valid formats are `toml` and `yaml`".fmt(f) - } -} - impl std::str::FromStr for Format { type Err = ParseFormatError; fn from_str(s: &str) -> Result { use Format::*; - match &*s.to_lowercase() { - "toml" => Ok(Toml), - "yaml" => Ok(Yaml), - _ => Err(ParseFormatError { _priv: () }), + if s.eq_ignore_ascii_case("toml") { + Ok(Toml) + } else if s.eq_ignore_ascii_case("yaml") { + Ok(Yaml) + } else { + Err(ParseFormatError { _priv: () }) } } } +#[derive(Debug, Clone, PartialEq, Eq)] +struct ParseFormatError { + _priv: (), +} + +impl fmt::Display for ParseFormatError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "valid values: {}", Format::VARIANTS.join(", ")) + } +} + /// Parse a `io::Read` instance from a path. This fn try_parse_read(source: &OsStr) -> io::Result> { match source.to_str() { From bf422bc1c0fca34f7fa18ec79eaba35768a6d6fc Mon Sep 17 00:00:00 2001 From: Basile Clement Date: Mon, 3 Dec 2018 19:17:54 +0100 Subject: [PATCH 4/4] Remove private field in ParseFormatError --- src/bin/expandconfig.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bin/expandconfig.rs b/src/bin/expandconfig.rs index afd091d22..4cb2152cb 100644 --- a/src/bin/expandconfig.rs +++ b/src/bin/expandconfig.rs @@ -136,15 +136,13 @@ impl std::str::FromStr for Format { } else if s.eq_ignore_ascii_case("yaml") { Ok(Yaml) } else { - Err(ParseFormatError { _priv: () }) + Err(ParseFormatError {}) } } } #[derive(Debug, Clone, PartialEq, Eq)] -struct ParseFormatError { - _priv: (), -} +struct ParseFormatError {} impl fmt::Display for ParseFormatError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {