zlsrs 0.1.6

Rust 标准库扩展工具集,提供更便捷的使用方式
Documentation
//! # 路径操作模块
//!
//! 这个模块提供了路径操作的核心功能,包括路径的规范化、转换和项目目录管理。
//!
//! ## 主要功能
//!
//! - 路径规范化和转换
//! - 项目目录类型管理(程序目录/工作目录)
//! - 临时目录、工作目录等特殊目录的管理
//!
//! ## 核心类型
//!
//! - `ZPath`: 封装了标准库的 `PathBuf`,提供了更多便利的路径操作方法
//! - `ProjectDirType`: 项目目录类型的枚举,用于区分程序目录和工作目录

use std::env;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};

/// 路径操作的核心类型
///
/// 封装了标准库的 `PathBuf`,提供了更多便利的路径操作方法:
/// - 路径规范化
/// - 绝对路径转换
/// - 项目目录相关判断
///
/// # 示例
///
/// ```rust
/// use zlsrs::zfile::ZPath;
///
/// let path = ZPath::new("./tmp/test/path");
/// ```
#[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()
    }
}

/// 获取临时目录
///
/// 返回一个指向系统临时目录的 `ZPath` 实例
pub fn get_tmp_dir() -> ZPath {
    normalize(&ZPath(PathBuf::from("/tmp")))
}

/// 获取程序所在的目录路径
///
/// 返回一个指向程序所在目录的 `ZPath` 实例
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("./"))),
    )
}

/// 获取工作目录
///
/// 返回一个指向当前工作目录的 `ZPath` 实例
pub fn get_work_dir() -> ZPath {
    ZPath(env::current_dir().unwrap_or_else(|_| PathBuf::from("./")))
}

/// 设置项目目录
///
/// 将当前项目目录设置为指定的目录
///
/// # 参数
///
/// - `dir`: 要设置的项目目录
///
/// # 返回值
///
/// - `Result<(), std::io::Error>`: 设置成功返回 `Ok(())`,否则返回错误
pub fn set_project_dir(dir: ZPath) -> Result<(), std::io::Error> {
    if !dir.0.exists() {
        std::fs::create_dir_all(&dir.0)?;
    }
    // env::set_current_dir(&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()
}

/// 规范化路径
///
/// 规范化指定的路径,处理 `.` 和 `..` 等相对路径元素
///
/// # 参数
///
/// - `path`: 要规范化的路径
///
/// # 返回值
///
/// - `ZPath`: 规范化后的路径
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)
}

/// 将相对路径转换为绝对路径
///
/// 将指定的相对路径转换为绝对路径
///
/// # 参数
///
/// - `path`: 要转换的相对路径
///
/// # 返回值
///
/// - `ZPath`: 转换后的绝对路径
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)))
}

/// 判断路径是否为绝对路径
///
/// 判断指定的路径是否为绝对路径
///
/// # 参数
///
/// - `path`: 要判断的路径
///
/// # 返回值
///
/// - `bool`: 如果路径为绝对路径则返回 `true`,否则返回 `false`
fn is_absolute(path: &str) -> bool {
    // Windows 风格的绝对路径 (如 C:\xxx)
    if path.chars().nth(1) == Some(':') && path.chars().nth(2) == Some('\\') {
        return true;
    }

    // Unix 风格的绝对路径 (以 / 开头)
    if path.starts_with('/') {
        return true;
    }

    false
}

impl ZPath {
    /// 创建新的 `ZPath` 实例
    ///
    /// 创建一个新的 `ZPath` 实例,指定的路径将被转换为绝对路径
    ///
    /// # 参数
    ///
    /// - `path`: 要创建的路径
    ///
    /// # 返回值
    ///
    /// - `ZPath`: 创建的 `ZPath` 实例
    pub fn new(path: &str) -> Self {
        to_absolute(path)
    }

    /// 将路径转换为字符串
    ///
    /// 将当前路径转换为字符串
    ///
    /// # 返回值
    ///
    /// - `String`: 转换后的字符串
    pub fn to_str(&self) -> String {
        self.to_string_lossy().into_owned()
    }

    /// 尝试将路径转换为字符串引用
    ///
    /// 尝试将当前路径转换为字符串引用,如果路径包含非UTF-8字符则返回 `None`
    ///
    /// # 返回值
    ///
    /// - `Option<&str>`: 转换后的字符串引用,如果转换失败则返回 `None`
    pub fn to_str_ref(&self) -> Option<&str> {
        self.0.to_str()
    }

    /// 判断路径是否在项目目录下
    ///
    /// 判断当前路径是否在项目目录下
    ///
    /// # 返回值
    ///
    /// - `bool`: 如果路径在项目目录下则返回 `true`,否则返回 `false`
    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(())
}