#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate cfg_if;
extern crate dirs_sys;
#[macro_use]
extern crate slog;
extern crate slog_stdlog;
use preftool::*;
use slog::Drain;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
macro_rules! vec_opt {
(__count) => {
0
};
(__count $_fst:expr, $($value:expr,)*) => {
1 + vec_opt!(__count $($value:expr,)*)
};
(__add $vec:ident) => {
};
(__add $vec:ident $val:expr, $($value:expr,)*) => {
match $val {
None => (),
Some(v) => {
$vec.push(v);
}
};
vec_opt!(__add $vec $($value,)*);
};
(
$($value:expr),+$(,)?
) => {{
let mut vec = Vec::with_capacity(vec_opt!(__count $($value,)+));
vec_opt!(__add vec $($value,)+);
vec
}};
}
cfg_if! {
if #[cfg(target_os = "windows")] {
mod win;
use win as sys;
} else if #[cfg(target_os = "macos")] {
mod mac;
use mac as sys;
} else {
mod lin;
use lin as sys;
}
}
bitflags! {
pub struct ConfigPathFlags: u32 {
const NONE = 0;
const APP_CONFIG_PATH = 1 << 0;
const USER_CONFIG_PATH = 1 << 1;
const BIN_CONFIG_PATH = 1 << 2;
const CWD_CONFIG_PATH = 1 << 3;
}
}
#[derive(Debug, Clone)]
pub struct ConfigPath {
path: PathBuf,
flags: ConfigPathFlags,
}
impl ConfigPath {
pub fn is_app(&self) -> bool {
self.flags.contains(ConfigPathFlags::APP_CONFIG_PATH)
}
pub fn is_user(&self) -> bool {
self.flags.contains(ConfigPathFlags::USER_CONFIG_PATH)
}
pub fn is_bin(&self) -> bool {
self.flags.contains(ConfigPathFlags::BIN_CONFIG_PATH)
}
pub fn is_cwd(&self) -> bool {
self.flags.contains(ConfigPathFlags::CWD_CONFIG_PATH)
}
pub fn path(&self) -> &Path {
&self.path
}
}
impl ConfigPath {
pub fn new(path: PathBuf, flags: ConfigPathFlags) -> Self {
Self { path, flags }
}
}
pub trait SearchPaths: LogOwner {
type Iter: Iterator<Item = ConfigPath> + 'static;
fn iter(&self) -> Self::Iter;
}
pub trait SearchPathsExt: SearchPaths {
fn files<P, I, F>(&self, get_names: F) -> Box<dyn Iterator<Item = PathBuf>>
where
P: AsRef<Path>,
I: IntoIterator<Item = P>,
I::IntoIter: 'static,
F: 'static + Fn(ConfigPath) -> I,
{
let logger = self.logger().clone();
Box::new(
self
.iter()
.flat_map(move |dir| {
let logger = logger.clone();
get_names(dir).into_iter().filter_map(move |p| {
let p = p.as_ref();
if p.is_file() {
trace!(logger, "Found config file {path:?}", path = p.to_str());
Some(PathBuf::from(p))
} else {
None
}
})
})
.fuse(),
)
}
}
impl<T: SearchPaths> SearchPathsExt for T {}
#[derive(Debug, Clone)]
pub struct ConfigDirs {
logger: slog::Logger,
dirs: Vec<PathBuf>,
app_dir: PathBuf,
home_dir: Option<PathBuf>,
cwd_dir: PathBuf,
}
impl ConfigDirs {
pub fn new<S: AsRef<str>>(
application: S,
logger: Option<slog::Logger>,
) -> std::io::Result<ConfigDirs> {
let ret = sys::config_dirs(
application.as_ref(),
logger.unwrap_or_else(|| slog::Logger::root(slog_stdlog::StdLog.fuse(), o!())),
)?;
debug!(ret.logger, "Config dirs"; &ret);
Ok(ret)
}
}
impl LogOwner for ConfigDirs {
#[inline]
fn logger(&self) -> &slog::Logger {
&self.logger
}
}
impl SearchPaths for ConfigDirs {
type Iter = std::vec::IntoIter<ConfigPath>;
fn iter(&self) -> Self::Iter {
let mut dirs = Vec::new();
let mut indexes = HashMap::new();
fn add<'p: 'c, 'c>(
logger: &slog::Logger,
indexes: &'c mut HashMap<&'p Path, usize>,
dirs: &'c mut Vec<ConfigPath>,
path: &'p Path,
flags: ConfigPathFlags,
) {
match indexes.get(path) {
None => {
let index = dirs.len();
indexes.insert(path, index);
trace!(
logger,
"Added config search path {path:?}",
path = path.to_str()
);
dirs.push(ConfigPath::new(path.to_owned(), flags));
}
Some(i) => {
let item = dirs.get_mut(*i).unwrap();
item.flags |= flags;
}
}
};
fn add_out<'p: 'c, 'c>(
logger: &slog::Logger,
indexes: &'c mut HashMap<&'p Path, usize>,
dirs: &'c mut Vec<ConfigPath>,
path: &'p Path,
flags: ConfigPathFlags,
) {
add(logger, indexes, dirs, path, flags);
match path.parent() {
None => (),
Some(p) => {
add_out(logger, indexes, dirs, p, flags);
}
}
};
for dir in self.dirs.iter() {
add(
&self.logger,
&mut indexes,
&mut dirs,
dir,
ConfigPathFlags::APP_CONFIG_PATH,
);
}
if let Some(home_dir) = &self.home_dir {
add(
&self.logger,
&mut indexes,
&mut dirs,
home_dir,
ConfigPathFlags::USER_CONFIG_PATH,
);
}
add_out(
&self.logger,
&mut indexes,
&mut dirs,
&self.app_dir,
ConfigPathFlags::BIN_CONFIG_PATH,
);
add_out(
&self.logger,
&mut indexes,
&mut dirs,
&self.cwd_dir,
ConfigPathFlags::CWD_CONFIG_PATH,
);
dirs.into_iter()
}
}
impl slog::KV for ConfigDirs {
fn serialize(&self, rec: &slog::Record, serializer: &mut slog::Serializer) -> slog::Result {
slog::Value::serialize(
&log::PathArg::from(&self.app_dir),
rec,
"app_dir",
serializer,
)?;
slog::Value::serialize(&log::PathArg::from(&self.cwd_dir), rec, "cwd", serializer)?;
slog::Value::serialize(
&self.home_dir.as_ref().map(log::PathArg::from),
rec,
"home_dir",
serializer,
)?;
Ok(())
}
}
mod log {
use slog::{Key, Record, Result, Serializer, Value};
use std::path::Path;
pub struct PathArg<'a>(&'a Path);
impl<'a> PathArg<'a> {
fn new<P: AsRef<Path>>(path: &'a P) -> Self {
Self(path.as_ref())
}
}
impl<'a, P: AsRef<Path>> From<&'a P> for PathArg<'a> {
fn from(path: &'a P) -> Self {
Self::new(path)
}
}
impl<'a> Value for PathArg<'a> {
fn serialize(&self, rec: &Record, key: Key, serializer: &mut Serializer) -> Result {
Value::serialize(&self.0.display(), rec, key, serializer)
}
}
}