#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(
missing_docs,
clippy::missing_errors_doc,
clippy::missing_panics_doc,
rustdoc::missing_crate_level_docs
)]
#![allow(clippy::bool_comparison)]
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::iter::FusedIterator;
use std::path::{Path, PathBuf};
use std::slice::Iter;
#[derive(Debug, Clone, Default)]
pub struct ConfigDirs {
paths: Vec<PathBuf>,
added_cwd: bool,
added_platform: bool,
#[cfg(unix)]
added_etc: bool,
}
impl ConfigDirs {
pub const fn empty() -> Self {
Self {
paths: Vec::new(),
added_cwd: false,
added_platform: false,
#[cfg(unix)]
added_etc: false,
}
}
#[inline]
pub fn search(&self, app: impl AsRef<Path>, base: impl AsRef<OsStr>, ext: impl AsRef<OsStr>) -> ConfigCandidates {
ConfigCandidates::new(&self.paths, app, base, ext)
}
}
impl ConfigDirs {
pub fn paths(&self) -> &[PathBuf] {
&self.paths
}
}
impl ConfigDirs {
#[inline]
pub fn add_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Self {
self._add_path(path, true)
}
#[inline]
pub fn add_all_paths_until<P1: AsRef<Path>, P2: AsRef<Path>>(&mut self, start: P1, container: P2) -> &mut Self {
fn helper(this: &mut ConfigDirs, start: &Path, container: &Path) {
start
.ancestors()
.take_while(|p| p.starts_with(container))
.for_each(|p| {
this._add_path(p, true);
});
}
helper(self, start.as_ref(), container.as_ref());
self
}
pub fn add_platform_config_dir(&mut self) -> &mut Self {
if self.added_platform {
return self;
}
#[cfg(windows)]
if let Some(path) = dirs_sys::known_folder_roaming_app_data() {
self._add_path(path, false);
self.added_platform = true;
}
#[cfg(not(windows))]
if let Some(path) = std::env::var_os("XDG_CONFIG_HOME").and_then(dirs_sys::is_absolute_path) {
self._add_path(path, false);
self.added_platform = true;
} else if let Some(path) = dirs_sys::home_dir().filter(|p| p.is_absolute()) {
self._add_path(path, true);
self.added_platform = true;
}
self
}
#[inline]
pub fn add_current_dir(&mut self) -> std::io::Result<&mut Self> {
if self.added_cwd == false {
self._add_path(std::env::current_dir()?, true);
self.added_cwd = true;
}
Ok(self)
}
}
#[cfg(unix)]
impl ConfigDirs {
#[inline]
pub fn add_root_etc(&mut self) -> &mut Self {
if self.added_etc == false {
self._add_path("/etc", false);
self.added_etc = true;
}
self
}
}
impl ConfigDirs {
#[inline]
pub(crate) fn _add_path<P>(&mut self, path: P, check_for_dot_config: bool) -> &mut Self
where
P: AsRef<Path>,
{
fn helper(this: &mut ConfigDirs, pr: &Path, check_for_dot_config: bool) {
let path = if check_for_dot_config == false || pr.ends_with(".config") {
Cow::Borrowed(pr)
} else {
Cow::Owned(pr.join(".config"))
};
if this.paths.iter().all(|p| p != &path) {
this.paths.push(path.into_owned());
}
}
helper(self, path.as_ref(), check_for_dot_config);
self
}
}
pub struct ConfigCandidates<'c> {
conf: WithLocal,
paths: Iter<'c, PathBuf>,
}
impl<'c> ConfigCandidates<'c> {
pub(crate) fn new(
paths: &'c [PathBuf],
app: impl AsRef<Path>,
base: impl AsRef<OsStr>,
ext: impl AsRef<OsStr>,
) -> Self {
Self {
conf: WithLocal::new(app.as_ref().join(base.as_ref()), ext),
paths: paths.iter(),
}
}
}
impl Iterator for ConfigCandidates<'_> {
type Item = WithLocal;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let dir = self.paths.next()?;
Some(self.conf.joined_to(dir))
}
#[inline]
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
let dir = self.paths.last()?;
Some(self.conf.joined_to(dir))
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
let dir = self.paths.nth(n)?;
Some(self.conf.joined_to(dir))
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.paths.size_hint()
}
#[inline]
fn count(self) -> usize
where
Self: Sized,
{
self.paths.count()
}
}
impl DoubleEndedIterator for ConfigCandidates<'_> {
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
let dir = self.paths.next_back()?;
Some(self.conf.joined_to(dir))
}
}
impl ExactSizeIterator for ConfigCandidates<'_> {}
impl FusedIterator for ConfigCandidates<'_> {}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct WithLocal {
path: PathBuf,
local_path: PathBuf,
}
impl WithLocal {
#[inline]
pub fn new(base: impl Into<OsString>, ext: impl AsRef<OsStr>) -> Self {
fn helper(mut path: OsString, ext: &OsStr) -> WithLocal {
let mut local_path = path.clone();
local_path.push(".local");
if ext.is_empty() == false {
path.push(".");
path.push(ext);
local_path.push(".");
local_path.push(ext);
}
WithLocal {
path: path.into(),
local_path: local_path.into(),
}
}
helper(base.into(), ext.as_ref())
}
#[inline]
pub fn path(&self) -> &Path {
&self.path
}
#[inline]
pub fn local_path(&self) -> &Path {
&self.local_path
}
#[inline]
pub fn into_paths(self) -> (PathBuf, PathBuf) {
(self.path, self.local_path)
}
}
impl WithLocal {
fn joined_to(&self, base: &Path) -> Self {
Self {
path: base.join(&self.path),
local_path: base.join(&self.local_path),
}
}
}