imag/lib/core/libimagrt/src/configuration.rs
Matthias Beyer eca7219039 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.
2017-10-31 09:25:14 +01:00

179 lines
6 KiB
Rust

//
// imag - the personal information management suite for the commandline
// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; version
// 2.1 of the License.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
use std::path::PathBuf;
use toml::Value;
use clap::App;
use error::RuntimeError as RE;
use error::RuntimeErrorKind as REK;
use error::Result;
use error::ResultExt;
/// 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 fetch_config(searchpath: &PathBuf) -> Result<Value> {
use std::env;
use std::fs::File;
use std::io::Read;
use std::io::Write;
use std::io::stderr;
use xdg_basedir;
use itertools::Itertools;
use libimagutil::variants::generate_variants as gen_vars;
use libimagerror::trace::trace_error;
let variants = vec!["config", "config.toml", "imagrc", "imagrc.toml"];
let modifier = |base: &PathBuf, v: &'static str| {
let mut base = base.clone();
base.push(String::from(v));
base
};
vec![
vec![searchpath.clone()],
gen_vars(searchpath.clone(), variants.clone(), &modifier),
env::var("HOME").map(|home| gen_vars(PathBuf::from(home), variants.clone(), &modifier))
.unwrap_or(vec![]),
xdg_basedir::get_data_home().map(|data_dir| gen_vars(data_dir, variants.clone(), &modifier))
.unwrap_or(vec![]),
].iter()
.flatten()
.filter(|path| path.exists() && path.is_file())
.filter_map(|path| {
let content = {
let f = File::open(path);
if f.is_err() {
let _ = write!(stderr(), "Error opening file: {:?}", f);
return None
}
let mut f = f.unwrap();
let mut s = String::new();
f.read_to_string(&mut s).ok();
s
};
match ::toml::de::from_str::<::toml::Value>(&content[..]) {
Ok(res) => Some(res),
Err(e) => {
let line_col = e
.line_col()
.map(|(line, col)| {
format!("Line {}, Column {}", line, col)
})
.unwrap_or_else(|| String::from("Line unknown, Column unknown"));
let _ = write!(stderr(), "Config file parser error at {}", line_col);
trace_error(&e);
None
}
}
})
.nth(0)
.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 {
fn enable_logging(&self) -> bool {
true
}
fn use_inmemory_fs(&self) -> bool {
false
}
}
impl<'a> InternalConfiguration for App<'a, 'a> {}