#[cfg(target_os = "windows")]
use std::os::windows::ffi::OsStrExt;
use super::{
read::{combine_path_like, read_path},
write::replace_path,
};
use clap::{crate_authors, Parser};
use itertools::Itertools;
use std::{
env::current_dir,
ffi::OsString,
fs::canonicalize,
io,
path::{Path, PathBuf, MAIN_SEPARATOR},
};
#[derive(Debug, Parser)]
#[clap(
about = "Remove duplicates and non-existent directories",
author = crate_authors!(),
visible_alias = "dedup",
)]
pub struct CleanOpt {
#[clap(short, long)]
quiet: bool,
#[clap(short = 'H', long)]
history: bool,
#[clap(short = 'n', long = "dry-run")]
dry_run: bool,
}
impl CleanOpt {
pub fn validate(&self) -> io::Result<()> {
Ok(())
}
}
pub fn clean_path(opts: &CleanOpt) -> io::Result<()> {
let current_path = read_path();
let newpath = clean_given_path(current_path)?;
match replace_path(newpath, opts.dry_run, opts.history, opts.quiet) {
Ok(()) => Ok(()),
Err(e) => {
if !opts.quiet {
eprintln!("Could not clean `$PATH`. {}", e);
}
Err(e)
}
}
}
pub fn clean_given_path(dirs: Vec<PathBuf>) -> io::Result<OsString> {
let cleaned_dirs = dirs.into_iter().filter(|p| p.exists()).unique().collect();
combine_path_like(cleaned_dirs)
}
pub fn clean_dir_name(dir: &Path) -> PathBuf {
let _cleaned_dir = match has_trailing_slash(&dir) {
true => {
let mut _temp_dir = dir
.to_string_lossy()
.trim_end_matches(MAIN_SEPARATOR)
.to_string();
PathBuf::from(_temp_dir)
}
false => dir.to_path_buf(),
};
make_abs_path(&_cleaned_dir)
}
pub fn clean_dirs_names<P: AsRef<Path>>(dirs: &[P]) -> Vec<PathBuf> {
dirs.iter().map(|p| clean_dir_name(p.as_ref())).collect()
}
fn make_abs_path(p: &Path) -> PathBuf {
match canonicalize(p) {
Ok(p) => p,
Err(_) => {
let mut abs_dir = current_dir().unwrap();
abs_dir.push(p);
abs_dir
}
}
}
#[cfg(target_os = "windows")]
fn has_trailing_slash(p: &Path) -> bool {
let last = p.as_os_str().encode_wide().last();
last == Some(b'\\' as u16) || last == Some(b'/' as u16)
}
#[cfg(target_os = "linux")]
fn has_trailing_slash(p: &Path) -> bool {
p.to_string_lossy().as_bytes().last() == Some(&b'/')
}
#[cfg(target_os = "macos")]
fn has_trailing_slash(p: &Path) -> bool {
p.to_string_lossy().as_bytes().last() == Some(&b'/')
}
#[cfg(test)]
mod tests {
use crate::path::clean::make_abs_path;
use std::path::{Path, PathBuf};
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
fn check_make_abs_path(input: &Path, expected: PathBuf) {
let observed = make_abs_path(input);
assert_eq!(expected, observed);
}
#[test]
#[cfg(not(windows))]
fn remove_middle_relative() {
check_make_abs_path(Path::new("/etc/../etc"), PathBuf::from("/etc"));
}
#[test]
#[cfg(target_os = "windows")]
fn remove_middle_relative() {
check_make_abs_path(
Path::new("C:/Users/../Users"),
PathBuf::from("\\\\?\\C:\\Users"),
);
}
#[test]
#[cfg(target_os = "linux")]
fn relative_path_made_absolute() {
let pwd = PathBuf::from("/bin");
let parent = PathBuf::from("/");
let sibling = PathBuf::from("/etc");
let descendent = PathBuf::from("/bin/sh");
check_make_abs_path(&pwd.join(".."), parent);
check_make_abs_path(&pwd.join("../etc"), sibling);
check_make_abs_path(&pwd.join("../bin/sh"), descendent);
}
#[test]
#[cfg(target_os = "macos")]
fn relative_path_made_absolute() {
let pwd = PathBuf::from("/usr");
let parent = PathBuf::from("/");
let sibling = PathBuf::from("/Applications");
let descendent = PathBuf::from("/usr/bin");
check_make_abs_path(&pwd.join(".."), parent);
check_make_abs_path(&pwd.join("../Applications"), sibling);
check_make_abs_path(&pwd.join("../usr/bin"), descendent);
}
#[test]
#[cfg(target_os = "windows")]
fn relative_path_made_absolute() {
let pwd = PathBuf::from("\\\\?\\C:\\Users");
let parent = PathBuf::from("\\\\?\\C:\\");
let sibling = PathBuf::from("\\\\?\\C:\\Windows");
let descendent = PathBuf::from("\\\\?\\C:\\Users\\Public");
check_make_abs_path(&pwd.join("../"), parent);
check_make_abs_path(&pwd.join("../Windows"), sibling);
check_make_abs_path(&pwd.join("../Users/Public"), descendent);
}
}