Add new Runtime constructor

that accepts a Configuration argument;
removed `GetConfiguration` trait and all the previous changes
needed for it
This commit is contained in:
Robert Ignat 2017-06-08 23:35:15 +02:00 committed by Matthias Beyer
parent 9a79232446
commit 6ded27c6ea
4 changed files with 155 additions and 181 deletions

View file

@ -350,14 +350,11 @@ mod tests {
use libimagrt::spec::CliSpec; use libimagrt::spec::CliSpec;
use libimagrt::runtime::Runtime; use libimagrt::runtime::Runtime;
use libimagrt::error::RuntimeError; use libimagrt::error::RuntimeError;
use libimagrt::configuration::{Configuration, GetConfiguration, InternalConfiguration, use libimagrt::configuration::{Configuration, InternalConfiguration};
Result as ConfigResult, ConfigErrorKind};
use libimagrt::configuration::error::MapErrInto;
use libimagstore::storeid::StoreId; use libimagstore::storeid::StoreId;
use libimagstore::store::{Result as StoreResult, FileLockEntry}; use libimagstore::store::{Result as StoreResult, FileLockEntry};
static TOML_CONFIG_FILE: &[u8] = include_bytes!("../test_config.toml");
static DEFAULT_ENTRY: &str = "\ static DEFAULT_ENTRY: &str = "\
---\ ---\
[imag]\ [imag]\
@ -392,20 +389,18 @@ version = \"0.3.0\"\
} }
} }
impl<'a> GetConfiguration for MockLinkApp<'a> {
fn get_configuration(&self, _: &PathBuf) -> ConfigResult<Configuration> {
::toml::de::from_slice(TOML_CONFIG_FILE)
.map_err_into(ConfigErrorKind::TOMLParserError)
.map(Configuration::new)
}
}
impl<'a> InternalConfiguration for MockLinkApp<'a> { impl<'a> InternalConfiguration for MockLinkApp<'a> {
fn enable_logging(&self) -> bool { fn enable_logging(&self) -> bool {
false false
} }
} }
fn generate_test_config() -> Option<Configuration> {
::toml::de::from_str("[store]\nimplicit-create=true")
.map(Configuration::with_value)
.ok()
}
fn generate_test_runtime<'a>(mut args: Vec<&'static str>) -> Result<Runtime<'a>, RuntimeError> { fn generate_test_runtime<'a>(mut args: Vec<&'static str>) -> Result<Runtime<'a>, RuntimeError> {
let mut cli_args = vec!["imag-link", let mut cli_args = vec!["imag-link",
"--no-color", "--no-color",
@ -417,7 +412,7 @@ version = \"0.3.0\"\
cli_args.append(&mut args); cli_args.append(&mut args);
let cli_app = MockLinkApp::new(cli_args); let cli_app = MockLinkApp::new(cli_args);
Runtime::new(cli_app) Runtime::with_configuration(cli_app, generate_test_config())
} }
fn create_test_entry<'a, S: AsRef<OsStr>>(rt: &'a Runtime, name: S) -> StoreResult<StoreId> { fn create_test_entry<'a, S: AsRef<OsStr>>(rt: &'a Runtime, name: S) -> StoreResult<StoreId> {

View file

@ -1,43 +0,0 @@
[store]
implicit-create = true
store-unload-hook-aspects = []
pre-create-hook-aspects = []
post-create-hook-aspects = []
pre-move-hook-aspects = []
post-move-hook-aspects = []
pre-retrieve-hook-aspects = []
post-retrieve-hook-aspects = []
pre-update-hook-aspects = []
post-update-hook-aspects = []
pre-delete-hook-aspects = []
post-delete-hook-aspects = []
[store.aspects.debug]
parallel = false
[store.aspects.vcs]
parallel = false
[store.hooks.stdhook_debug]
aspect = "vcs"
[store.hooks.stdhook_git_update]
aspect = "vcs"
enabled = false
[store.hooks.stdhook_git_update.commit]
enabled = false
[store.hooks.stdhook_git_delete]
aspect = "vcs"
enabled = false
[store.hooks.stdhook_git_delete.commit]
enabled = false
[store.hooks.stdhook_git_storeunload]
aspect = "vcs"
enabled = false
[store.hooks.stdhook_git_storeunload.commit]
enabled = false

View file

@ -73,21 +73,33 @@ impl Configuration {
/// with all variants. /// with all variants.
/// ///
/// If that doesn't work either, an error is returned. /// If that doesn't work either, an error is returned.
pub fn new(toml_config: Value) -> Configuration { pub fn new(rtp: &PathBuf) -> Result<Configuration> {
let verbosity = get_verbosity(&toml_config); fetch_config(&rtp).map(|cfg| {
let editor = get_editor(&toml_config); let verbosity = get_verbosity(&cfg);
let editor_opts = get_editor_opts(&toml_config); let editor = get_editor(&cfg);
let editor_opts = get_editor_opts(&cfg);
debug!("Building configuration"); debug!("Building configuration");
debug!(" - verbosity : {:?}", verbosity); debug!(" - verbosity : {:?}", verbosity);
debug!(" - editor : {:?}", editor); debug!(" - editor : {:?}", editor);
debug!(" - editor-opts: {}", editor_opts); debug!(" - editor-opts: {}", editor_opts);
Configuration { Configuration {
config: toml_config, config: cfg,
verbosity: verbosity, verbosity: verbosity,
editor: editor, editor: editor,
editor_opts: editor_opts, 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,
} }
} }
@ -211,73 +223,67 @@ fn get_editor_opts(v: &Value) -> String {
} }
} }
pub trait GetConfiguration { /// Helper to fetch the config file
/// Helper to fetch the config file ///
/// /// Tests several variants for the config file path and uses the first one which works.
/// Default implementation tests several variants for the config file path and uses the first fn fetch_config(rtp: &PathBuf) -> Result<Value> {
/// one which works. use std::env;
fn get_configuration(&self, rtp: &PathBuf) -> Result<Configuration> { use std::fs::File;
use std::env; use std::io::Read;
use std::fs::File; use std::io::Write;
use std::io::Read; use std::io::stderr;
use std::io::Write;
use std::io::stderr;
use xdg_basedir; use xdg_basedir;
use itertools::Itertools; use itertools::Itertools;
use libimagutil::variants::generate_variants as gen_vars; use libimagutil::variants::generate_variants as gen_vars;
use libimagerror::trace::trace_error; use libimagerror::trace::trace_error;
let variants = vec!["config", "config.toml", "imagrc", "imagrc.toml"]; let variants = vec!["config", "config.toml", "imagrc", "imagrc.toml"];
let modifier = |base: &PathBuf, v: &'static str| { let modifier = |base: &PathBuf, v: &'static str| {
let mut base = base.clone(); let mut base = base.clone();
base.push(String::from(v)); base.push(String::from(v));
base base
}; };
vec![ vec![
vec![rtp.clone()], vec![rtp.clone()],
gen_vars(rtp.clone(), variants.clone(), &modifier), gen_vars(rtp.clone(), variants.clone(), &modifier),
env::var("HOME").map(|home| gen_vars(PathBuf::from(home), variants.clone(), &modifier)) env::var("HOME").map(|home| gen_vars(PathBuf::from(home), variants.clone(), &modifier))
.unwrap_or(vec![]), .unwrap_or(vec![]),
xdg_basedir::get_data_home().map(|data_dir| gen_vars(data_dir, variants.clone(), &modifier)) xdg_basedir::get_data_home().map(|data_dir| gen_vars(data_dir, variants.clone(), &modifier))
.unwrap_or(vec![]), .unwrap_or(vec![]),
].iter() ].iter()
.flatten() .flatten()
.filter(|path| path.exists() && path.is_file()) .filter(|path| path.exists() && path.is_file())
.map(|path| { .map(|path| {
let content = { let content = {
let mut s = String::new(); let mut s = String::new();
let f = File::open(path); let f = File::open(path);
if f.is_err() { if f.is_err() {
return None return None
}
let mut f = f.unwrap();
f.read_to_string(&mut s).ok();
s
};
match ::toml::de::from_str(&content[..]) {
Ok(res) => res,
Err(e) => {
write!(stderr(), "Config file parser error:").ok();
trace_error(&e);
None
}
} }
}) let mut f = f.unwrap();
.filter(|loaded| loaded.is_some()) f.read_to_string(&mut s).ok();
.nth(0) s
.map(|inner| Value::Table(inner.unwrap())) };
.ok_or(ConfigErrorKind::NoConfigFileFound.into())
.map(Configuration::new)
}
}
impl<'a> GetConfiguration for App<'a, 'a> {} match ::toml::de::from_str(&content[..]) {
Ok(res) => res,
Err(e) => {
write!(stderr(), "Config file parser error:").ok();
trace_error(&e);
None
}
}
})
.filter(|loaded| loaded.is_some())
.nth(0)
.map(|inner| Value::Table(inner.unwrap()))
.ok_or(ConfigErrorKind::NoConfigFileFound.into())
}
pub trait InternalConfiguration { pub trait InternalConfiguration {
fn enable_logging(&self) -> bool { fn enable_logging(&self) -> bool {

View file

@ -29,7 +29,7 @@ use clap::{Arg, ArgMatches};
use log; use log;
use log::LogLevelFilter; use log::LogLevelFilter;
use configuration::{Configuration, GetConfiguration, InternalConfiguration}; use configuration::{Configuration, InternalConfiguration};
use error::RuntimeError; use error::RuntimeError;
use error::RuntimeErrorKind; use error::RuntimeErrorKind;
use error::MapErrInto; use error::MapErrInto;
@ -56,14 +56,9 @@ impl<'a> Runtime<'a> {
/// and builds the Runtime object with it. /// and builds the Runtime object with it.
/// ///
/// The cli_app object should be initially build with the ::get_default_cli_builder() function. /// The cli_app object should be initially build with the ::get_default_cli_builder() function.
pub fn new<C>(mut cli_app: C) -> Result<Runtime<'a>, RuntimeError> pub fn new<C>(cli_app: C) -> Result<Runtime<'a>, RuntimeError>
where C: Clone + CliSpec<'a> + GetConfiguration + InternalConfiguration where C: Clone + CliSpec<'a> + InternalConfiguration
{ {
use std::env;
use std::io::stdout;
use clap::Shell;
use libimagerror::trace::trace_error; use libimagerror::trace::trace_error;
use libimagerror::into::IntoError; use libimagerror::into::IntoError;
@ -71,11 +66,51 @@ impl<'a> Runtime<'a> {
let matches = cli_app.clone().matches(); let matches = cli_app.clone().matches();
let is_debugging = matches.is_present("debugging"); let rtp = get_rtp_match(&matches);
let is_verbose = matches.is_present("verbosity");
let colored = !matches.is_present("no-color-output"); let configpath = matches.value_of(Runtime::arg_config_name())
.map_or_else(|| rtp.clone(), PathBuf::from);
let config = match Configuration::new(&configpath) {
Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound {
return Err(RuntimeErrorKind::Instantiate.into_error_with_cause(Box::new(e)));
} else {
warn!("No config file found.");
warn!("Continuing without configuration file");
None
},
Ok(mut config) => {
if let Err(e) = config.override_config(get_override_specs(&matches)) {
error!("Could not apply config overrides");
trace_error(&e);
// TODO: continue question (interactive)
}
Some(config)
}
};
Runtime::with_configuration(cli_app, config)
}
/// Builds the Runtime object using the given `config`.
pub fn with_configuration<C>(mut cli_app: C, config: Option<Configuration>) -> Result<Runtime<'a>, RuntimeError>
where C: Clone + CliSpec<'a> + InternalConfiguration
{
use std::io::stdout;
use clap::Shell;
let matches = cli_app.clone().matches();
let is_debugging = matches.is_present(Runtime::arg_debugging_name());
if cli_app.enable_logging() { if cli_app.enable_logging() {
let is_verbose = matches.is_present(Runtime::arg_verbosity_name());
let colored = !matches.is_present(Runtime::arg_no_color_output_name());
Runtime::init_logger(is_debugging, is_verbose, colored); Runtime::init_logger(is_debugging, is_verbose, colored);
} }
@ -89,64 +124,29 @@ impl<'a> Runtime<'a> {
_ => debug!("Not generating shell completion script"), _ => debug!("Not generating shell completion script"),
} }
let rtp : PathBuf = matches.value_of("runtimepath") let rtp = get_rtp_match(&matches);
.map_or_else(|| {
env::var("HOME") let storepath = matches.value_of(Runtime::arg_storepath_name())
.map(PathBuf::from)
.map(|mut p| { p.push(".imag"); p})
.unwrap_or_else(|_| {
panic!("You seem to be $HOME-less. Please get a $HOME before using this software. We are sorry for you and hope you have some accommodation anyways.");
})
}, PathBuf::from);
let storepath = matches.value_of("storepath")
.map_or_else(|| { .map_or_else(|| {
let mut spath = rtp.clone(); let mut spath = rtp.clone();
spath.push("store"); spath.push("store");
spath spath
}, PathBuf::from); }, PathBuf::from);
let configpath = matches.value_of("config") let store_config = match config {
.map_or_else(|| rtp.clone(), PathBuf::from);
debug!("RTP path = {:?}", rtp);
debug!("Store path = {:?}", storepath);
debug!("Config path = {:?}", configpath);
let cfg = match cli_app.get_configuration(&configpath) {
Err(e) => if e.err_type() != ConfigErrorKind::NoConfigFileFound {
return Err(RuntimeErrorKind::Instantiate.into_error_with_cause(Box::new(e)));
} else {
warn!("No config file found.");
warn!("Continuing without configuration file");
None
},
Ok(mut cfg) => {
if let Err(e) = cfg.override_config(get_override_specs(&matches)) {
error!("Could not apply config overrides");
trace_error(&e);
// TODO: continue question (interactive)
}
Some(cfg)
}
};
let store_config = match cfg {
Some(ref c) => c.store_config().cloned(), Some(ref c) => c.store_config().cloned(),
None => None, None => None,
}; };
if is_debugging { if is_debugging {
write!(stderr(), "Config: {:?}\n", cfg).ok(); write!(stderr(), "Config: {:?}\n", config).ok();
write!(stderr(), "Store-config: {:?}\n", store_config).ok(); write!(stderr(), "Store-config: {:?}\n", store_config).ok();
} }
Store::new(storepath.clone(), store_config).map(|store| { Store::new(storepath, store_config).map(|store| {
Runtime { Runtime {
cli_matches: matches, cli_matches: matches,
configuration: cfg, configuration: config,
rtp: rtp, rtp: rtp,
store: store, store: store,
} }
@ -408,6 +408,22 @@ impl<'a> Runtime<'a> {
} }
} }
fn get_rtp_match<'a>(matches: &ArgMatches<'a>) -> PathBuf {
use std::env;
matches.value_of(Runtime::arg_runtimepath_name())
.map_or_else(|| {
env::var("HOME")
.map(PathBuf::from)
.map(|mut p| { p.push(".imag"); p })
.unwrap_or_else(|_| {
panic!("You seem to be $HOME-less. Please get a $HOME before using this \
software. We are sorry for you and hope you have some \
accommodation anyways.");
})
}, PathBuf::from)
}
fn get_override_specs(matches: &ArgMatches) -> Vec<String> { fn get_override_specs(matches: &ArgMatches) -> Vec<String> {
matches matches
.values_of("config-override") .values_of("config-override")