Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an expandconfig utility to add default values #178

Merged
merged 5 commits into from
Dec 3, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
ulysseB marked this conversation as resolved.
Show resolved Hide resolved

[[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
180 changes: 180 additions & 0 deletions src/bin/expandconfig.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
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 { _priv: () })
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct ParseFormatError {
_priv: (),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need this field ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't need it; it's just to prevent creating the error from somewhere else, which is not actually very useful. I can remove it.

}

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