extern crate preftool;
extern crate preftool_dirs;
use preftool::*;
use preftool_dirs::*;
use std::collections::HashSet;
use std::fs::File;
use std::io::{Error, ErrorKind, Read, Result};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
pub trait ConfigurationFileFormat: Clone + Sized {
type Provider: ConfigurationProvider + 'static;
fn get_config<R: Read>(reader: R) -> Result<Self::Provider>;
fn default_suffixes() -> &'static [&'static str];
fn file<P: AsRef<Path>>(path: P) -> ConfigurationFileSource<Self> {
ConfigurationFileSource::new(PathBuf::from(path.as_ref()), false)
}
fn files<P: AsRef<Path>, I: IntoIterator<Item = P> + Clone + 'static, S: SearchPaths + Clone>(
suffixes: I,
search_paths: S,
app_name: String,
) -> ConfigurationFileSources<S, Self> {
ConfigurationFileSources::new(suffixes, search_paths, app_name)
}
fn default_config_files<S: SearchPaths + Clone>(
search_paths: S,
app_name: String,
) -> ConfigurationFileSources<S, Self> {
Self::files(Self::default_suffixes(), search_paths, app_name)
}
}
#[derive(Clone)]
pub struct ConfigurationFileSource<F: ConfigurationFileFormat> {
format: PhantomData<*const F>,
path: PathBuf,
required: bool,
}
impl<F: ConfigurationFileFormat> ConfigurationFileSource<F> {
fn new(path: PathBuf, required: bool) -> Self {
Self {
format: PhantomData,
path,
required,
}
}
}
impl<F> ConfigurationSource for ConfigurationFileSource<F>
where
F: ConfigurationFileFormat,
{
fn build<B: ConfigurationBuilder>(self, mut builder: B) -> Result<B> {
if self.required && !self.path.is_file() {
return Err(Error::new(
ErrorKind::NotFound,
format!("Required config file {:#?} not found.", &self.path),
));
}
if self.path.is_file() {
let f = File::open(&self.path)?;
builder = builder.push_provider(F::get_config(f)?);
}
Ok(builder)
}
}
#[derive(Clone)]
pub struct ConfigurationFileSources<S, F>
where
S: SearchPaths + Clone,
F: ConfigurationFileFormat,
{
format: PhantomData<*const F>,
search_paths: S,
suffixes: Vec<PathBuf>,
app_name: String,
}
impl<S, F> ConfigurationFileSources<S, F>
where
S: SearchPaths + Clone,
F: ConfigurationFileFormat,
{
fn new<P: AsRef<Path>, I: IntoIterator<Item = P> + Clone + 'static>(
suffixes: I,
search_paths: S,
app_name: String,
) -> Self {
let suffixes = suffixes
.into_iter()
.map(|name| PathBuf::from(name.as_ref()))
.collect();
Self {
format: PhantomData,
search_paths,
suffixes,
app_name,
}
}
}
impl<S, F> ConfigurationSource for ConfigurationFileSources<S, F>
where
S: SearchPaths + Clone + 'static,
F: ConfigurationFileFormat + 'static,
{
fn build<B: ConfigurationBuilder>(self, mut builder: B) -> Result<B> {
let search_paths = self.search_paths;
let suffixes = self.suffixes;
let app_name = self.app_name;
for file in search_paths.files(move |p| {
let mut files = HashSet::new();
if p.is_bin() || p.is_cwd() || p.is_user() {
for suffix in suffixes.iter() {
files.insert(
p.path()
.join(format!("{}rc{}", app_name, suffix.to_str().unwrap())),
);
files.insert(
p.path()
.join(format!(".{}rc{}", app_name, suffix.to_str().unwrap())),
);
}
}
if p.is_app() {
for suffix in suffixes.iter() {
files.insert(p.path().join(format!("config{}", suffix.to_str().unwrap())));
}
}
files.into_iter()
}) {
let f = File::open(file)?;
builder = builder.push_provider(F::get_config(f)?);
}
Ok(builder)
}
}