summaryrefslogtreecommitdiffstats
path: root/lib/core/libimagrt/src/configuration.rs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/core/libimagrt/src/configuration.rs')
-rw-r--r--lib/core/libimagrt/src/configuration.rs303
1 files changed, 303 insertions, 0 deletions
diff --git a/lib/core/libimagrt/src/configuration.rs b/lib/core/libimagrt/src/configuration.rs
new file mode 100644
index 00000000..dcb3326b
--- /dev/null
+++ b/lib/core/libimagrt/src/configuration.rs
@@ -0,0 +1,303 @@
+//
+// 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 std::result::Result as RResult;
+use std::ops::Deref;
+
+use toml::Value;
+use clap::App;
+
+generate_error_module!(
+ generate_error_types!(ConfigError, ConfigErrorKind,
+ TOMLParserError => "TOML Parsing error",
+ NoConfigFileFound => "No config file found",
+
+ ConfigOverrideError => "Config override error",
+ ConfigOverrideKeyNotAvailable => "Key not available",
+ ConfigOverrideTypeNotMatching => "Configuration Type not matching"
+
+ );
+);
+
+pub use self::error::{ConfigError, ConfigErrorKind, MapErrInto};
+use libimagerror::into::IntoError;
+
+/// Result type of this module. Either `T` or `ConfigError`
+pub type Result<T> = RResult<T, ConfigError>;
+
+/// `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 libimagutil::iter::*;
+ use self::error::ConfigErrorKind as CEK;
+ use self::error::MapErrInto;
+ use libimagerror::into::IntoError;
+ use libimagstore::toml_ext::TomlValueExt;
+
+ 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[..])
+ .map_err_into(CEK::TOMLParserError)
+ .map(|toml| match toml {
+ Some(value) => match into_value(value, v) {
+ Some(v) => {
+ info!("Successfully overridden: {} = {}", k, v);
+ Ok(v)
+ },
+ None => Err(CEK::ConfigOverrideTypeNotMatching.into_error()),
+ },
+ None => Err(CEK::ConfigOverrideKeyNotAvailable.into_error()),
+ })
+ )
+ .fold_result(|i| i)
+ .map_err(Box::new)
+ .map_err(|e| CEK::ConfigOverrideError.into_error_with_cause(e))
+ }
+}
+
+/// 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,
+ }
+}
+
+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> {
+ 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(&ConfigErrorKind::TOMLParserError.into_error_with_cause(Box::new(e)));
+ None
+ }
+ }
+ })
+ .nth(0)
+ .ok_or(ConfigErrorKind::NoConfigFileFound.into())
+}
+
+pub trait InternalConfiguration {
+ fn enable_logging(&self) -> bool {
+ true
+ }
+
+ fn use_inmemory_fs(&self) -> bool {
+ false
+ }
+}
+
+impl<'a> InternalConfiguration for App<'a, 'a> {}