Merge pull request #1155 from matthiasbeyer/libimagrt/config-refactoring

Rewrite configuration providing in runtime
This commit is contained in:
Matthias Beyer 2017-10-31 12:58:46 +01:00 committed by GitHub
commit 429194b5d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 137 additions and 258 deletions

View file

@ -99,7 +99,7 @@ fn main() {
.map_err_trace_exit_unwrap(1);
let query = format!("view.viewers.{}", viewer);
match config.config().read(&query) {
match config.read(&query) {
Err(e) => trace_error_exit(&e, 1),
Ok(None) => {
error!("Cannot find '{}' in config", query);

View file

@ -256,7 +256,6 @@ fn main() {
fn fetch_aliases(rt: &Runtime) -> Result<BTreeMap<String, String>, String> {
let cfg = try!(rt.config().ok_or_else(|| String::from("No configuration found")));
let value = cfg
.config()
.read("imag.aliases")
.map_err(|_| String::from("Reading from config failed"));

View file

@ -169,7 +169,7 @@ fn get_collection_name(rt: &Runtime,
.and_then(|scmd| scmd.value_of(collection_argument_name).map(String::from))
.unwrap_or_else(|| {
rt.config()
.map(|cfg| match cfg.config().read("bookmark.default_collection") {
.map(|cfg| match cfg.read("bookmark.default_collection") {
Err(e) => trace_error_exit(&e, 1),
Ok(Some(&Value::String(ref name))) => name.clone(),
Ok(None) => {

View file

@ -53,7 +53,6 @@ pub fn get_diary_timed_config(rt: &Runtime, diary_name: &str) -> Result<Option<T
None => Ok(None),
Some(cfg) => {
let v = cfg
.config()
.read(&format!("diary.diaries.{}.timed", diary_name))
.chain_err(|| DiaryErrorKind::IOError);

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

View file

@ -34,9 +34,7 @@ pub fn get_default_diary_name(rt: &Runtime) -> Option<String> {
}
pub fn get_diary_config_section<'a>(rt: &'a Runtime) -> Option<&'a Value> {
rt.config()
.map(|config| config.config())
.and_then(|config| match config.read(&String::from("diary")) {
rt.config().and_then(|config| match config.read(&String::from("diary")) {
Ok(x) => x,
Err(_) => None,
})

View file

@ -34,9 +34,8 @@ pub struct Readline {
impl Readline {
pub fn new(rt: &Runtime) -> Result<Readline> {
let cfg = try!(rt.config().ok_or(IEK::NoConfigError));
let c = try!(rt.config().ok_or(IEK::NoConfigError));
let c = cfg.config();
let histfile = try!(c.lookup("ui.cli.readline_history_file").ok_or(IEK::ConfigError));
let histsize = try!(c.lookup("ui.cli.readline_history_size").ok_or(IEK::ConfigError));
let histigndups = try!(c.lookup("ui.cli.readline_history_ignore_dups").ok_or(IEK::ConfigError));

View file

@ -51,7 +51,8 @@ macro_rules! make_mock_app {
use libimagrt::spec::CliSpec;
use libimagrt::runtime::Runtime;
use libimagrt::error::RuntimeError;
use libimagrt::configuration::{Configuration, InternalConfiguration};
use libimagrt::configuration::InternalConfiguration;
use toml::Value;
#[derive(Clone)]
struct MockLinkApp<'a> {
@ -89,9 +90,8 @@ macro_rules! make_mock_app {
}
#[allow(unused)]
pub fn generate_minimal_test_config() -> Option<Configuration> { ::toml::de::from_str("[store]\nimplicit-create=true")
.map(Configuration::with_value)
.ok()
pub fn generate_minimal_test_config() -> Option<Value> {
::toml::de::from_str("[store]\nimplicit-create=true").ok()
}
#[allow(unused)]