Rewrite configuration providing in runtime

Before the configuration object (the raw TOML object) was provided via a
wrapper object `Configuration`. This was ugly and not very nice to use.

Now, we only have the `toml::Value` object we lend out from
`Runtime::config()`.

The changes included libimagrt internal rewrites, which are not visible
to the user. Anyways, this change changes the API for config-fetching
from the runtime, so fixes for all other crates may follow.

The changes also removed the support for reading the "editor" setting
from the configuration file, which was not used anyways (in the example
imagrc.toml file).
The CLI-reading and ENV-reading are still supported, though.
This commit is contained in:
Matthias Beyer 2017-10-28 12:50:22 +02:00
parent 619104b991
commit eca7219039
5 changed files with 126 additions and 242 deletions

View file

@ -30,6 +30,9 @@ This section contains the changelog from the last release to the next release.
* `imag-store` can dump all storeids now
* `imag-annotate` was introduced
* `imag-diagnostics` was added
* The runtime does not read the config file for editor settings anymore.
Specifying an editor either via CLI or via the `$EDITOR` environment
variable still possible.
* Minor changes
* `libimagentryannotation` got a rewrite, is not based on `libimagnotes`

View file

@ -18,228 +18,23 @@
//
use std::path::PathBuf;
use std::ops::Deref;
use toml::Value;
use clap::App;
error_chain! {
types {
ConfigError, ConfigErrorKind, ResultExt, Result;
}
use error::RuntimeError as RE;
use error::RuntimeErrorKind as REK;
use error::Result;
use error::ResultExt;
errors {
TOMLParserError {
description("TOML Parsing error")
display("TOML Parsing error")
}
NoConfigFileFound {
description("No config file found")
display("No config file found")
}
ConfigOverrideError {
description("Config override error")
display("Config override error")
}
ConfigOverrideKeyNotAvailable {
description("Key not available")
display("Key not available")
}
ConfigOverrideTypeNotMatching {
description("Configuration Type not matching")
display("Configuration Type not matching")
}
}
}
use self::ConfigErrorKind as CEK;
use self::ConfigError as CE;
/// `Configuration` object
/// Get a new configuration object.
///
/// Holds all config variables which are globally available plus the configuration object from the
/// config parser, which can be accessed.
#[derive(Debug)]
pub struct Configuration {
/// The plain configuration object for direct access if necessary
config: Value,
/// The verbosity the program should run with
verbosity: bool,
/// The editor which should be used
editor: Option<String>,
///The options the editor should get when opening some file
editor_opts: String,
}
impl Configuration {
/// Get a new configuration object.
///
/// The passed runtimepath is used for searching the configuration file, whereas several file
/// names are tested. If that does not work, the home directory and the XDG basedir are tested
/// with all variants.
///
/// If that doesn't work either, an error is returned.
pub fn new(config_searchpath: &PathBuf) -> Result<Configuration> {
fetch_config(&config_searchpath).map(|cfg| {
let verbosity = get_verbosity(&cfg);
let editor = get_editor(&cfg);
let editor_opts = get_editor_opts(&cfg);
debug!("Building configuration");
debug!(" - verbosity : {:?}", verbosity);
debug!(" - editor : {:?}", editor);
debug!(" - editor-opts: {}", editor_opts);
Configuration {
config: cfg,
verbosity: verbosity,
editor: editor,
editor_opts: editor_opts,
}
})
}
/// Get a new configuration object built from the given toml value.
pub fn with_value(value: Value) -> Configuration {
Configuration{
verbosity: get_verbosity(&value),
editor: get_editor(&value),
editor_opts: get_editor_opts(&value),
config: value,
}
}
/// Get the Editor setting from the configuration
pub fn editor(&self) -> Option<&String> {
self.editor.as_ref()
}
/// Get the underlying configuration TOML object
pub fn config(&self) -> &Value {
&self.config
}
/// Get the configuration of the store, if any.
pub fn store_config(&self) -> Option<&Value> {
match self.config {
Value::Table(ref tabl) => tabl.get("store"),
_ => None,
}
}
/// Override the configuration.
/// The `v` parameter is expected to contain 'key=value' pairs where the key is a path in the
/// TOML tree, the value to be an appropriate value.
///
/// The override fails if the configuration which is about to be overridden does not exist or
/// the `value` part cannot be converted to the type of the configuration value.
///
/// If `v` is empty, this is considered to be a successful `override_config()` call.
pub fn override_config(&mut self, v: Vec<String>) -> Result<()> {
use libimagutil::key_value_split::*;
use toml_query::read::TomlValueReadExt;
let iter = v.into_iter()
.map(|s| { debug!("Trying to process '{}'", s); s })
.filter_map(|s| match s.into_kv() {
Some(kv) => Some(kv.into()),
None => {
warn!("Could split at '=' - will be ignore override");
None
}
})
.map(|(k, v)| self
.config
.read(&k[..])
.chain_err(|| CEK::TOMLParserError)
.map(|toml| match toml {
Some(value) => match into_value(value, v) {
Some(v) => {
info!("Successfully overridden: {} = {}", k, v);
Ok(())
},
None => Err(CE::from_kind(CEK::ConfigOverrideTypeNotMatching)),
},
None => Err(CE::from_kind(CEK::ConfigOverrideKeyNotAvailable)),
})
);
for elem in iter {
let _ = try!(elem.chain_err(|| CEK::ConfigOverrideError));
}
Ok(())
}
}
/// Tries to convert the String `s` into the same type as `value`.
/// The passed runtimepath is used for searching the configuration file, whereas several file
/// names are tested. If that does not work, the home directory and the XDG basedir are tested
/// with all variants.
///
/// Returns None if string cannot be converted.
///
/// Arrays and Tables are not supported and will yield `None`.
fn into_value(value: &Value, s: String) -> Option<Value> {
use std::str::FromStr;
match *value {
Value::String(_) => Some(Value::String(s)),
Value::Integer(_) => FromStr::from_str(&s[..]).ok().map(Value::Integer),
Value::Float(_) => FromStr::from_str(&s[..]).ok().map(Value::Float),
Value::Boolean(_) => {
if s == "true" { Some(Value::Boolean(true)) }
else if s == "false" { Some(Value::Boolean(false)) }
else { None }
}
Value::Datetime(_) => Value::try_from(s).ok(),
_ => None,
}
}
impl Deref for Configuration {
type Target = Value;
fn deref(&self) -> &Value {
&self.config
}
}
fn get_verbosity(v: &Value) -> bool {
match *v {
Value::Table(ref t) => t.get("verbose")
.map_or(false, |v| is_match!(v, &Value::Boolean(true))),
_ => false,
}
}
fn get_editor(v: &Value) -> Option<String> {
match *v {
Value::Table(ref t) => t.get("editor")
.and_then(|v| match *v { Value::String(ref s) => Some(s.clone()), _ => None, }),
_ => None,
}
}
fn get_editor_opts(v: &Value) -> String {
match *v {
Value::Table(ref t) => t.get("editor-opts")
.and_then(|v| match *v { Value::String(ref s) => Some(s.clone()), _ => None, })
.unwrap_or_default(),
_ => String::new(),
}
}
/// Helper to fetch the config file
///
/// Tests several variants for the config file path and uses the first one which works.
fn fetch_config(searchpath: &PathBuf) -> Result<Value> {
/// If that doesn't work either, an error is returned.
pub fn fetch_config(searchpath: &PathBuf) -> Result<Value> {
use std::env;
use std::fs::File;
use std::io::Read;
@ -302,7 +97,72 @@ fn fetch_config(searchpath: &PathBuf) -> Result<Value> {
}
})
.nth(0)
.ok_or(CE::from_kind(ConfigErrorKind::NoConfigFileFound))
.ok_or(RE::from_kind(REK::ConfigNoConfigFileFound))
}
/// Override the configuration.
/// The `v` parameter is expected to contain 'key=value' pairs where the key is a path in the
/// TOML tree, the value to be an appropriate value.
///
/// The override fails if the configuration which is about to be overridden does not exist or
/// the `value` part cannot be converted to the type of the configuration value.
///
/// If `v` is empty, this is considered to be a successful `override_config()` call.
pub fn override_config(val: &mut Value, v: Vec<String>) -> Result<()> {
use libimagutil::key_value_split::*;
use toml_query::read::TomlValueReadExt;
let iter = v.into_iter()
.map(|s| { debug!("Trying to process '{}'", s); s })
.filter_map(|s| match s.into_kv() {
Some(kv) => Some(kv.into()),
None => {
warn!("Could split at '=' - will be ignore override");
None
}
})
.map(|(k, v)| val
.read(&k[..])
.chain_err(|| REK::ConfigTOMLParserError)
.map(|toml| match toml {
Some(value) => match into_value(value, v) {
Some(v) => {
info!("Successfully overridden: {} = {}", k, v);
Ok(())
},
None => Err(RE::from_kind(REK::ConfigOverrideTypeNotMatching)),
},
None => Err(RE::from_kind(REK::ConfigOverrideKeyNotAvailable)),
})
);
for elem in iter {
let _ = try!(elem.chain_err(|| REK::ConfigOverrideError));
}
Ok(())
}
/// Tries to convert the String `s` into the same type as `value`.
///
/// Returns None if string cannot be converted.
///
/// Arrays and Tables are not supported and will yield `None`.
fn into_value(value: &Value, s: String) -> Option<Value> {
use std::str::FromStr;
match *value {
Value::String(_) => Some(Value::String(s)),
Value::Integer(_) => FromStr::from_str(&s[..]).ok().map(Value::Integer),
Value::Float(_) => FromStr::from_str(&s[..]).ok().map(Value::Float),
Value::Boolean(_) => {
if s == "true" { Some(Value::Boolean(true)) }
else if s == "false" { Some(Value::Boolean(false)) }
else { None }
}
Value::Datetime(_) => Value::try_from(s).ok(),
_ => None,
}
}
pub trait InternalConfiguration {
@ -316,3 +176,4 @@ pub trait InternalConfiguration {
}
impl<'a> InternalConfiguration for App<'a, 'a> {}

View file

@ -93,6 +93,31 @@ error_chain! {
display("Missing config for logging format for error logging")
}
ConfigTOMLParserError {
description("Configuration: TOML Parsing error")
display("Configuration: TOML Parsing error")
}
ConfigNoConfigFileFound {
description("Configuration: No config file found")
display("Configuration: No config file found")
}
ConfigOverrideError {
description("Configuration: Config override error")
display("Configuration: Config override error")
}
ConfigOverrideKeyNotAvailable {
description("Configuration: Key not available")
display("Configuration: Key not available")
}
ConfigOverrideTypeNotMatching {
description("Configuration: Configuration Type not matching")
display("Configuration: Configuration Type not matching")
}
}
}

View file

@ -24,7 +24,6 @@ use std::sync::Arc;
use std::sync::Mutex;
use std::ops::Deref;
use configuration::Configuration;
use error::RuntimeErrorKind as EK;
use error::RuntimeError as RE;
use error::ResultExt;
@ -77,7 +76,7 @@ pub struct ImagLogger {
impl ImagLogger {
/// Create a new ImagLogger object with a certain level
pub fn new(matches: &ArgMatches, config: Option<&Configuration>) -> Result<ImagLogger> {
pub fn new(matches: &ArgMatches, config: Option<&Value>) -> Result<ImagLogger> {
let mut handlebars = Handlebars::new();
handlebars.register_escape_fn(::handlebars::no_escape);
@ -220,7 +219,7 @@ fn match_log_level_str(s: &str) -> Result<LogLevel> {
}
}
fn aggregate_global_loglevel(matches: &ArgMatches, config: Option<&Configuration>)
fn aggregate_global_loglevel(matches: &ArgMatches, config: Option<&Value>)
-> Result<LogLevel>
{
fn get_arg_loglevel(matches: &ArgMatches) -> Result<Option<LogLevel>> {
@ -300,7 +299,7 @@ fn translate_destinations(raw: &Vec<Value>) -> Result<Vec<LogDestination>> {
})
}
fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Configuration>)
fn aggregate_global_destinations(matches: &ArgMatches, config: Option<&Value>)
-> Result<Vec<LogDestination>>
{
@ -344,7 +343,7 @@ macro_rules! aggregate_global_format {
};
}
fn aggregate_global_format_trace(config: Option<&Configuration>)
fn aggregate_global_format_trace(config: Option<&Value>)
-> Result<String>
{
aggregate_global_format!("imag.logging.format.trace",
@ -352,7 +351,7 @@ fn aggregate_global_format_trace(config: Option<&Configuration>)
config)
}
fn aggregate_global_format_debug(config: Option<&Configuration>)
fn aggregate_global_format_debug(config: Option<&Value>)
-> Result<String>
{
aggregate_global_format!("imag.logging.format.debug",
@ -360,7 +359,7 @@ fn aggregate_global_format_debug(config: Option<&Configuration>)
config)
}
fn aggregate_global_format_info(config: Option<&Configuration>)
fn aggregate_global_format_info(config: Option<&Value>)
-> Result<String>
{
aggregate_global_format!("imag.logging.format.info",
@ -368,7 +367,7 @@ fn aggregate_global_format_info(config: Option<&Configuration>)
config)
}
fn aggregate_global_format_warn(config: Option<&Configuration>)
fn aggregate_global_format_warn(config: Option<&Value>)
-> Result<String>
{
aggregate_global_format!("imag.logging.format.warn",
@ -376,7 +375,7 @@ fn aggregate_global_format_warn(config: Option<&Configuration>)
config)
}
fn aggregate_global_format_error(config: Option<&Configuration>)
fn aggregate_global_format_error(config: Option<&Value>)
-> Result<String>
{
aggregate_global_format!("imag.logging.format.error",
@ -384,7 +383,7 @@ fn aggregate_global_format_error(config: Option<&Configuration>)
config)
}
fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Configuration>)
fn aggregate_module_settings(_matches: &ArgMatches, config: Option<&Value>)
-> Result<BTreeMap<ModuleName, ModuleSettings>>
{
match config {

View file

@ -23,11 +23,13 @@ use std::env;
use std::process::exit;
pub use clap::App;
use toml::Value;
use toml_query::read::TomlValueReadExt;
use clap::{Arg, ArgMatches};
use log;
use configuration::{Configuration, InternalConfiguration};
use configuration::{fetch_config, override_config, InternalConfiguration};
use error::RuntimeError;
use error::RuntimeErrorKind;
use error::ResultExt;
@ -44,7 +46,7 @@ use spec::CliSpec;
#[derive(Debug)]
pub struct Runtime<'a> {
rtp: PathBuf,
configuration: Option<Configuration>,
configuration: Option<Value>,
cli_matches: ArgMatches<'a>,
store: Store,
}
@ -61,8 +63,6 @@ impl<'a> Runtime<'a> {
{
use libimagerror::trace::trace_error;
use configuration::ConfigErrorKind;
let matches = cli_app.clone().matches();
let rtp = get_rtp_match(&matches);
@ -72,8 +72,8 @@ impl<'a> Runtime<'a> {
debug!("Config path = {:?}", configpath);
let config = match Configuration::new(&configpath) {
Err(e) => if !is_match!(e.kind(), &ConfigErrorKind::NoConfigFileFound) {
let config = match fetch_config(&configpath) {
Err(e) => if !is_match!(e.kind(), &RuntimeErrorKind::ConfigNoConfigFileFound) {
return Err(e).chain_err(|| RuntimeErrorKind::Instantiate);
} else {
println!("No config file found.");
@ -82,7 +82,7 @@ impl<'a> Runtime<'a> {
},
Ok(mut config) => {
if let Err(e) = config.override_config(get_override_specs(&matches)) {
if let Err(e) = override_config(&mut config, get_override_specs(&matches)) {
error!("Could not apply config overrides");
trace_error(&e);
@ -97,7 +97,7 @@ impl<'a> Runtime<'a> {
}
/// Builds the Runtime object using the given `config`.
pub fn with_configuration<C>(cli_app: C, config: Option<Configuration>)
pub fn with_configuration<C>(cli_app: C, config: Option<Value>)
-> Result<Runtime<'a>, RuntimeError>
where C: Clone + CliSpec<'a> + InternalConfiguration
{
@ -105,7 +105,7 @@ impl<'a> Runtime<'a> {
Runtime::_new(cli_app, matches, config)
}
fn _new<C>(mut cli_app: C, matches: ArgMatches<'a>, config: Option<Configuration>)
fn _new<C>(mut cli_app: C, matches: ArgMatches<'a>, config: Option<Value>)
-> Result<Runtime<'a>, RuntimeError>
where C: Clone + CliSpec<'a> + InternalConfiguration
{
@ -139,7 +139,7 @@ impl<'a> Runtime<'a> {
debug!("Store path = {:?}", storepath);
let store_config = match config {
Some(ref c) => c.store_config().cloned(),
Some(ref c) => c.read("store").chain_err(|| RuntimeErrorKind::Instantiate)?.cloned(),
None => None,
};
@ -345,7 +345,7 @@ impl<'a> Runtime<'a> {
}
/// Initialize the internal logger
fn init_logger(matches: &ArgMatches, config: Option<&Configuration>) {
fn init_logger(matches: &ArgMatches, config: Option<&Value>) {
use std::env::var as env_var;
use env_logger;
@ -386,7 +386,7 @@ impl<'a> Runtime<'a> {
}
/// Get the configuration object
pub fn config(&self) -> Option<&Configuration> {
pub fn config(&self) -> Option<&Value> {
self.configuration.as_ref()
}
@ -444,10 +444,6 @@ impl<'a> Runtime<'a> {
self.cli()
.value_of("editor")
.map(String::from)
.or(match self.configuration {
Some(ref c) => c.editor().cloned(),
_ => None,
})
.or(env::var("EDITOR").ok())
.map(Command::new)
}