Skip to content

Commit

Permalink
Merge #178
Browse files Browse the repository at this point in the history
178: Add an `expandconfig` utility to add default values r=ulysseB a=Elarnon

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

Co-authored-by: Basile Clement <basile@clement.pm>
Co-authored-by: Basile Clement <basile.clement@ens.fr>
Co-authored-by: Basile Clement <elarnon@users.noreply.github.com>
  • Loading branch information
4 people committed Dec 3, 2018
2 parents 69b74c3 + 29bcdc8 commit cac2ec4
Show file tree
Hide file tree
Showing 2 changed files with 183 additions and 0 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -76,6 +79,8 @@ rpds = "0.5.0"
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"
Expand Down
178 changes: 178 additions & 0 deletions src/bin/expandconfig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
extern crate serde_yaml;
extern crate toml;

extern crate structopt;

extern crate telamon;

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<OsString>,

/// 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",
case_insensitive = true,
raw(possible_values = "Format::VARIANTS"),
)]
format: Format,
}

impl Opt {
/// Open the input file and returns a `io::Read` handle to it
fn open_input(&self) -> io::Result<Option<Box<dyn Read>>> {
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<Box<dyn Write>> {
try_parse_write(&*self.output)
}

/// Parse the existing configuration
fn parse_config(&self) -> io::Result<telamon::explorer::config::Config> {
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,
}

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::*;

match self {
Toml => write!(f, "toml"),
Yaml => write!(f, "yaml"),
}
}
}

impl std::str::FromStr for Format {
type Err = ParseFormatError;

fn from_str(s: &str) -> Result<Format, Self::Err> {
use Format::*;

if s.eq_ignore_ascii_case("toml") {
Ok(Toml)
} else if s.eq_ignore_ascii_case("yaml") {
Ok(Yaml)
} else {
Err(ParseFormatError {})
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct ParseFormatError {}

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<Box<dyn Read>> {
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<Box<dyn Write>> {
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");

opt.dump_config(&config)
.expect("Unable to dump configuration file");
}

0 comments on commit cac2ec4

Please sign in to comment.