#![warn(
// ---------- Stylistic
future_incompatible,
nonstandard_style,
rust_2018_idioms,
trivial_casts,
trivial_numeric_casts,
// ---------- Public
missing_debug_implementations,
missing_docs,
unreachable_pub,
// ---------- Unsafe
unsafe_code,
// ---------- Unused
unused_extern_crates,
unused_import_braces,
unused_qualifications,
unused_results,
)]
use std::env;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, PartialEq)]
pub struct SearchPath {
paths: Vec<PathBuf>,
}
#[cfg(target_family = "windows")]
const PATH_SEPARATOR_CHAR: char = ';';
#[cfg(not(target_family = "windows"))]
const PATH_SEPARATOR_CHAR: char = ':';
const CURRENT_DIR_PATH: &str = ".";
#[derive(Copy, Clone, Debug, PartialEq)]
enum FindKind {
Any,
File,
Directory,
}
impl Default for SearchPath {
fn default() -> Self {
Self {
paths: Default::default(),
}
}
}
impl Display for SearchPath {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
self.paths
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect::<Vec<String>>()
.join(&PATH_SEPARATOR_CHAR.to_string())
)
}
}
impl From<PathBuf> for SearchPath {
fn from(v: PathBuf) -> Self {
Self { paths: vec![v] }
}
}
impl From<Vec<PathBuf>> for SearchPath {
fn from(vs: Vec<PathBuf>) -> Self {
Self { paths: vs }
}
}
impl From<Vec<&Path>> for SearchPath {
fn from(vs: Vec<&Path>) -> Self {
Self {
paths: vs.iter().map(|p| PathBuf::from(p)).collect(),
}
}
}
impl From<Vec<&str>> for SearchPath {
fn from(vs: Vec<&str>) -> Self {
Self {
paths: vs
.iter()
.filter_map(|p| {
if p.trim().is_empty() {
None
} else {
Some(PathBuf::from(p))
}
})
.collect(),
}
}
}
impl From<String> for SearchPath {
fn from(s: String) -> Self {
Self::from(s.as_str())
}
}
impl From<&str> for SearchPath {
fn from(s: &str) -> Self {
Self::from(s.split(PATH_SEPARATOR_CHAR).collect::<Vec<&str>>())
}
}
impl From<SearchPath> for Vec<PathBuf> {
fn from(p: SearchPath) -> Self {
p.paths
}
}
impl IntoIterator for SearchPath {
type Item = PathBuf;
type IntoIter = std::vec::IntoIter<PathBuf>;
fn into_iter(self) -> Self::IntoIter {
self.paths.into_iter()
}
}
impl SearchPath {
pub fn new(env_var: &str) -> Result<Self, Box<dyn Error>> {
match env::var(env_var) {
Ok(path) => Ok(Self::from(path)),
Err(e) => Err(Box::new(e)),
}
}
pub fn path() -> Result<Self, Box<dyn Error>> {
Self::new("PATH")
}
pub fn new_or<T: Into<SearchPath>>(env_var: &str, default: T) -> Self {
match Self::new(env_var) {
Ok(search_path) => search_path,
Err(_) => default.into(),
}
}
pub fn new_or_default(env_var: &str) -> Self {
Self::new_or(env_var, SearchPath::default())
}
pub fn find(&self, file_name: &Path) -> Option<PathBuf> {
self.find_something(file_name, FindKind::Any)
}
pub fn find_all(&self, file_name: &Path) -> Vec<PathBuf> {
let mut results: Vec<PathBuf> = Default::default();
for path in &self.paths {
let mut path = PathBuf::from(path);
path.push(file_name);
if path.exists() {
results.push(path);
}
}
results
}
pub fn find_file(&self, file_name: &Path) -> Option<PathBuf> {
self.find_something(file_name, FindKind::File)
}
pub fn find_directory(&self, file_name: &Path) -> Option<PathBuf> {
self.find_something(file_name, FindKind::Directory)
}
pub fn find_if_name_only(&self, file_name: &Path) -> Option<PathBuf> {
if let Some(_) = file_name.parent() {
self.find(file_name)
} else {
None
}
}
fn find_something(&self, file_name: &Path, kind: FindKind) -> Option<PathBuf> {
for path in &self.paths {
let mut path = PathBuf::from(path);
path.push(file_name);
if (kind == FindKind::Any && path.exists())
|| (kind == FindKind::File && path.is_file())
|| (kind == FindKind::Directory && path.is_dir())
{
return Some(path);
}
}
None
}
pub fn is_empty(&self) -> bool {
self.paths.is_empty()
}
pub fn len(&self) -> usize {
self.paths.len()
}
pub fn contains(&self, path: &PathBuf) -> bool {
self.paths.contains(path)
}
pub fn contains_cwd(&self) -> bool {
self.contains(&PathBuf::from(CURRENT_DIR_PATH))
}
pub fn iter(&self) -> impl Iterator<Item = &PathBuf> {
self.paths.iter()
}
pub fn append(&mut self, path: PathBuf) {
self.paths.push(path)
}
pub fn append_cwd(&mut self) {
self.append(PathBuf::from(CURRENT_DIR_PATH))
}
pub fn prepend(&mut self, path: PathBuf) {
self.paths.insert(0, path)
}
pub fn prepend_cwd(&mut self) {
self.prepend(PathBuf::from(CURRENT_DIR_PATH))
}
pub fn remove(&mut self, path: &PathBuf) {
self.paths.retain(|p| p != path);
}
pub fn dedup(&mut self) {
use std::collections::HashSet;
let mut seen: HashSet<PathBuf> = Default::default();
self.paths.retain(|p| seen.insert(p.clone()))
}
}