use std::env;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
#[derive(Debug, Clone)]
pub struct ZPath(PathBuf);
impl From<PathBuf> for ZPath {
fn from(path_buf: PathBuf) -> Self {
ZPath(path_buf)
}
}
impl From<&Path> for ZPath {
fn from(path: &Path) -> Self {
ZPath(path.to_path_buf())
}
}
impl Deref for ZPath {
type Target = Path;
fn deref(&self) -> &Self::Target {
self.0.as_path()
}
}
impl AsRef<Path> for ZPath {
fn as_ref(&self) -> &Path {
self.0.as_path()
}
}
pub fn get_tmp_dir() -> ZPath {
normalize(&ZPath(PathBuf::from("/tmp")))
}
pub fn get_exe_dir() -> ZPath {
ZPath(
env::current_exe()
.ok()
.and_then(|exe_path| exe_path.parent().map(PathBuf::from))
.unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from("./"))),
)
}
pub fn get_work_dir() -> ZPath {
ZPath(env::current_dir().unwrap_or_else(|_| PathBuf::from("./")))
}
pub fn set_project_dir(dir: ZPath) -> Result<(), std::io::Error> {
if !dir.0.exists() {
std::fs::create_dir_all(&dir.0)?;
}
reset_project_dir(dir)?;
Ok(())
}
pub fn get_project_dir() -> ZPath {
PROJECT_DIR
.get_or_init(|| Mutex::new(get_exe_dir()))
.lock()
.unwrap()
.clone()
}
fn normalize(path: &ZPath) -> ZPath {
let canonical = std::fs::canonicalize(&path.0).unwrap_or_else(|_| path.0.clone());
let component_count = canonical.components().count();
let mut components = Vec::with_capacity(component_count);
for component in canonical.components() {
match component {
std::path::Component::Prefix(p) => components.push(p.as_os_str().to_owned()),
std::path::Component::RootDir => components.push(std::ffi::OsString::from("/")),
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
if let Some(last) = components.last() {
if last != "/" && !Path::new(last).is_absolute() {
components.pop();
}
}
}
std::path::Component::Normal(c) => components.push(c.to_owned()),
}
}
let mut normalized = PathBuf::with_capacity(component_count);
for component in components {
normalized.push(component);
}
ZPath(normalized)
}
pub fn to_absolute(path: &str) -> ZPath {
let path_buf = PathBuf::from(path);
if is_absolute(&path) {
return normalize(&ZPath(path_buf));
}
normalize(&ZPath(get_project_dir().join(path)))
}
fn is_absolute(path: &str) -> bool {
if path.chars().nth(1) == Some(':') && path.chars().nth(2) == Some('\\') {
return true;
}
if path.starts_with('/') {
return true;
}
false
}
impl ZPath {
pub fn new(path: &str) -> Self {
to_absolute(path)
}
pub fn to_str(&self) -> String {
self.to_string_lossy().into_owned()
}
pub fn to_str_ref(&self) -> Option<&str> {
self.0.to_str()
}
pub fn is_in_project_dir(&self) -> bool {
let project_dir = get_project_dir();
if self.0.is_absolute() {
self.0.starts_with(&project_dir.0)
} else {
to_absolute(&self.to_string_lossy())
.0
.starts_with(&project_dir.0)
}
}
}
static PROJECT_DIR: OnceLock<Mutex<ZPath>> = OnceLock::new();
fn reset_project_dir(dir: ZPath) -> Result<(), std::io::Error> {
if let Some(project_dir) = PROJECT_DIR.get() {
let mut current = project_dir
.lock()
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "获取项目目录锁失败"))?;
*current = dir;
} else {
PROJECT_DIR
.set(Mutex::new(dir))
.map_err(|_| std::io::Error::new(std::io::ErrorKind::Other, "设置项目目录失败"))?;
}
Ok(())
}