From 353fdc5b539aae0479c7404d0ed6404f82bf633a Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sat, 27 Jun 2026 18:52:49 +0800 Subject: feat(mingling): add directory environment resources and setup Add four new resource types for common directory paths and a convenience setup struct that registers all of them at once. --- CHANGELOG.md | 17 +++++++ mingling/src/res.rs | 3 ++ mingling/src/res/dirs.rs | 11 +++++ mingling/src/res/dirs/current_dir.rs | 82 ++++++++++++++++++++++++++++++ mingling/src/res/dirs/current_exe.rs | 81 ++++++++++++++++++++++++++++++ mingling/src/res/dirs/home_dir.rs | 96 ++++++++++++++++++++++++++++++++++++ mingling/src/res/dirs/temp_dir.rs | 77 +++++++++++++++++++++++++++++ mingling/src/setups.rs | 3 ++ mingling/src/setups/dirs.rs | 42 ++++++++++++++++ 9 files changed, 412 insertions(+) create mode 100644 mingling/src/res/dirs.rs create mode 100644 mingling/src/res/dirs/current_dir.rs create mode 100644 mingling/src/res/dirs/current_exe.rs create mode 100644 mingling/src/res/dirs/home_dir.rs create mode 100644 mingling/src/res/dirs/temp_dir.rs create mode 100644 mingling/src/setups/dirs.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cc4e6f..066cc5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -530,6 +530,23 @@ dispatcher! { } ``` +10. **\[mingling\]** Added the `DirectoryEnvironmentSetup` setup struct, which registers four common directory-based resources (`ResCurrentDir`, `ResCurrentExe`, `ResHomeDir`, `ResTempDir`) in a single call. These resources provide convenient access to the current working directory, the executable's path, the user's home directory, and the system temporary directory, respectively. + +```rust +use mingling::setups::DirectoryEnvironmentSetup; + +program.with_setup(DirectoryEnvironmentSetup::::default()); +``` + +11. **\[mingling\]** Added four new resource types for directory environments: + + - `ResCurrentDir` — Wraps `std::env::current_dir()` as a global resource. Provides `new() -> Result`, `Default` (panics on failure), and conversions from/to `PathBuf`, `&Path`, and `&PathBuf`. + - `ResCurrentExe` — Wraps `std::env::current_exe()` as a global resource. Provides `new() -> Result`, `Default` (panics on failure), and conversions from/to `PathBuf`, `&Path`, and `&PathBuf`. + - `ResHomeDir` — Wraps the user's home directory (`$HOME` on Unix, `%USERPROFILE%` on Windows) as a global resource. Provides `new() -> Result`, `Default` (panics on failure), and conversions from/to `PathBuf`, `&Path`, and `&PathBuf`. + - `ResTempDir` — Wraps `std::env::temp_dir()` as a global resource. Provides `new()` (infallible), `Default`, and conversions from/to `PathBuf`, `&Path`, and `&PathBuf`. + + All four types implement `Deref`, `DerefMut`, `AsRef`, `Clone`, `Debug`, and `PartialEq`. + #### **BREAKING CHANGES** (API CHANGES): 1. **\[core\]** Panic Unwind will not be supported when the `async` feature is enabled diff --git a/mingling/src/res.rs b/mingling/src/res.rs index 982fe91..1dd00aa 100644 --- a/mingling/src/res.rs +++ b/mingling/src/res.rs @@ -1,5 +1,8 @@ mod exit_code; pub use exit_code::*; +mod dirs; +pub use dirs::*; + #[allow(unused_imports)] pub use mingling_core::core_res::*; diff --git a/mingling/src/res/dirs.rs b/mingling/src/res/dirs.rs new file mode 100644 index 0000000..0a0a590 --- /dev/null +++ b/mingling/src/res/dirs.rs @@ -0,0 +1,11 @@ +mod current_dir; +pub use current_dir::*; + +mod current_exe; +pub use current_exe::*; + +mod temp_dir; +pub use temp_dir::*; + +mod home_dir; +pub use home_dir::*; diff --git a/mingling/src/res/dirs/current_dir.rs b/mingling/src/res/dirs/current_dir.rs new file mode 100644 index 0000000..1de84e0 --- /dev/null +++ b/mingling/src/res/dirs/current_dir.rs @@ -0,0 +1,82 @@ +use std::{ + env::current_dir, + path::{Path, PathBuf}, +}; + +/// A global resource for the Mingling library that provides the current working directory. +/// +/// This struct wraps a `PathBuf` representing the current working directory at the time of +/// creation. It is used as a shared resource in the Mingling framework so that multiple +/// components can access the same base directory without repeatedly querying the OS. +/// +/// # Default behavior +/// +/// The `Default` implementation calls `std::env::current_dir().unwrap()` internally, which will +/// **panic** if the current directory cannot be determined (e.g., if it has been deleted). If +/// you want to handle this error gracefully, use [`ResCurrentDir::new`] instead, which returns +/// a `Result`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResCurrentDir { + cwd: PathBuf +} + +impl ResCurrentDir { + /// Creates a new `ResCurrentDir` by querying the OS for the current working directory. + /// + /// Returns `Err` if the current directory cannot be determined (e.g., the directory has been + /// deleted or permissions are insufficient). Unlike the `Default` implementation, this + /// method does not panic on failure. + pub fn new() -> Result { + Ok(Self { cwd: current_dir()? }) + } +} + +impl Default for ResCurrentDir { + fn default() -> Self { + Self { cwd: current_dir().unwrap() } + } +} + +impl From for ResCurrentDir { + fn from(path: PathBuf) -> Self { + Self { cwd: path } + } +} + +impl From<&Path> for ResCurrentDir { + fn from(path: &Path) -> Self { + Self { cwd: path.to_path_buf() } + } +} + +impl From<&PathBuf> for ResCurrentDir { + fn from(path: &PathBuf) -> Self { + Self { cwd: path.clone() } + } +} + +impl From for PathBuf { + fn from(res: ResCurrentDir) -> Self { + res.cwd + } +} + +impl AsRef for ResCurrentDir { + fn as_ref(&self) -> &Path { + self.cwd.as_path() + } +} + +impl std::ops::Deref for ResCurrentDir { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.cwd + } +} + +impl std::ops::DerefMut for ResCurrentDir { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.cwd + } +} diff --git a/mingling/src/res/dirs/current_exe.rs b/mingling/src/res/dirs/current_exe.rs new file mode 100644 index 0000000..051fcee --- /dev/null +++ b/mingling/src/res/dirs/current_exe.rs @@ -0,0 +1,81 @@ +use std::{ + env::current_exe, + path::{Path, PathBuf}, +}; + +/// A global resource that provides the path of the current executable. +/// +/// This struct wraps a `PathBuf` representing the full filesystem path of the +/// currently running executable at the time of creation. +/// +/// # Default behavior +/// +/// The `Default` implementation calls `std::env::current_exe().unwrap()` internally, +/// which will **panic** if the executable path cannot be determined (e.g., if the +/// `/proc` filesystem is not available). If you want to handle this error gracefully, +/// use [`ResCurrentExe::new`] instead, which returns a `Result`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResCurrentExe { + exe: PathBuf, +} + +impl ResCurrentExe { + /// Creates a new `ResCurrentExe` by querying the OS for the current executable path. + /// + /// Returns `Err` if the executable path cannot be determined (e.g., the `/proc` + /// filesystem is not available on Linux, or the process handle is invalid). + /// Unlike the `Default` implementation, this method does not panic on failure. + pub fn new() -> Result { + Ok(Self { exe: current_exe()? }) + } +} + +impl Default for ResCurrentExe { + fn default() -> Self { + Self { exe: current_exe().unwrap() } + } +} + +impl From for ResCurrentExe { + fn from(path: PathBuf) -> Self { + Self { exe: path } + } +} + +impl From<&Path> for ResCurrentExe { + fn from(path: &Path) -> Self { + Self { exe: path.to_path_buf() } + } +} + +impl From<&PathBuf> for ResCurrentExe { + fn from(path: &PathBuf) -> Self { + Self { exe: path.clone() } + } +} + +impl From for PathBuf { + fn from(res: ResCurrentExe) -> Self { + res.exe + } +} + +impl AsRef for ResCurrentExe { + fn as_ref(&self) -> &Path { + self.exe.as_path() + } +} + +impl std::ops::Deref for ResCurrentExe { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.exe + } +} + +impl std::ops::DerefMut for ResCurrentExe { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.exe + } +} diff --git a/mingling/src/res/dirs/home_dir.rs b/mingling/src/res/dirs/home_dir.rs new file mode 100644 index 0000000..de2e5e7 --- /dev/null +++ b/mingling/src/res/dirs/home_dir.rs @@ -0,0 +1,96 @@ +use std::path::{Path, PathBuf}; + +/// A global resource that provides the current user's home directory path. +/// +/// This struct wraps a `PathBuf` representing the user's home directory. +/// +/// **Platform notes:** +/// - On **Unix**: reads the `HOME` environment variable. +/// - On **Windows**: reads the `USERPROFILE` environment variable. +/// +/// # Default behavior +/// +/// The `Default` implementation calls [`ResHomeDir::new`] and **panics** if the home +/// directory cannot be determined (e.g., the relevant environment variable is not set). +/// Use [`ResHomeDir::new`] to handle this error gracefully. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResHomeDir { + home: PathBuf, +} + +impl ResHomeDir { + /// Creates a new `ResHomeDir` by querying the environment for the user's home directory. + /// + /// Returns `Err` if the home directory cannot be determined (e.g., the `HOME` or + /// `USERPROFILE` environment variable is not set). + pub fn new() -> Result { + let home = home_dir_env() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "home directory not found"))?; + Ok(Self { home }) + } +} + +impl Default for ResHomeDir { + fn default() -> Self { + Self { home: home_dir_env().expect("home directory not found") } + } +} + +impl From for ResHomeDir { + fn from(path: PathBuf) -> Self { + Self { home: path } + } +} + +impl From<&Path> for ResHomeDir { + fn from(path: &Path) -> Self { + Self { home: path.to_path_buf() } + } +} + +impl From<&PathBuf> for ResHomeDir { + fn from(path: &PathBuf) -> Self { + Self { home: path.clone() } + } +} + +impl From for PathBuf { + fn from(res: ResHomeDir) -> Self { + res.home + } +} + +impl AsRef for ResHomeDir { + fn as_ref(&self) -> &Path { + self.home.as_path() + } +} + +impl std::ops::Deref for ResHomeDir { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.home + } +} + +impl std::ops::DerefMut for ResHomeDir { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.home + } +} + +/// Returns the user's home directory by checking platform-specific environment variables. +/// +/// - Unix: `$HOME` +/// - Windows: `%USERPROFILE%` +fn home_dir_env() -> Option { + #[cfg(target_os = "windows")] + { + std::env::var_os("USERPROFILE").map(PathBuf::from) + } + #[cfg(not(target_os = "windows"))] + { + std::env::var_os("HOME").map(PathBuf::from) + } +} diff --git a/mingling/src/res/dirs/temp_dir.rs b/mingling/src/res/dirs/temp_dir.rs new file mode 100644 index 0000000..f4f0dca --- /dev/null +++ b/mingling/src/res/dirs/temp_dir.rs @@ -0,0 +1,77 @@ +use std::{ + env::temp_dir, + path::{Path, PathBuf}, +}; + +/// A global resource that provides the system temporary directory path. +/// +/// This struct wraps a `PathBuf` representing the platform's temporary directory +/// (e.g., `/tmp` on Unix, `%TEMP%` on Windows). +/// +/// Note that `std::env::temp_dir()` is infallible — it always returns a path, +/// falling back to a platform-specific default (e.g., `/tmp` on Unix) if the +/// environment variable is not set. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ResTempDir { + tmp: PathBuf, +} + +impl ResTempDir { + /// Creates a new `ResTempDir` by querying the OS for the system temporary directory. + /// + /// This method is infallible since `std::env::temp_dir()` always succeeds, + /// returning a platform-specific default when environment variables are unset. + pub fn new() -> Self { + Self { tmp: temp_dir() } + } +} + +impl Default for ResTempDir { + fn default() -> Self { + Self::new() + } +} + +impl From for ResTempDir { + fn from(path: PathBuf) -> Self { + Self { tmp: path } + } +} + +impl From<&Path> for ResTempDir { + fn from(path: &Path) -> Self { + Self { tmp: path.to_path_buf() } + } +} + +impl From<&PathBuf> for ResTempDir { + fn from(path: &PathBuf) -> Self { + Self { tmp: path.clone() } + } +} + +impl From for PathBuf { + fn from(res: ResTempDir) -> Self { + res.tmp + } +} + +impl AsRef for ResTempDir { + fn as_ref(&self) -> &Path { + self.tmp.as_path() + } +} + +impl std::ops::Deref for ResTempDir { + type Target = PathBuf; + + fn deref(&self) -> &Self::Target { + &self.tmp + } +} + +impl std::ops::DerefMut for ResTempDir { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tmp + } +} diff --git a/mingling/src/setups.rs b/mingling/src/setups.rs index 5546268..e572cf5 100644 --- a/mingling/src/setups.rs +++ b/mingling/src/setups.rs @@ -1,6 +1,9 @@ mod basic; pub use basic::*; +mod dirs; +pub use dirs::*; + mod exit_code; pub use exit_code::*; diff --git a/mingling/src/setups/dirs.rs b/mingling/src/setups/dirs.rs new file mode 100644 index 0000000..a7f81fa --- /dev/null +++ b/mingling/src/setups/dirs.rs @@ -0,0 +1,42 @@ +use std::marker::PhantomData; + +use mingling_core::{ + ProgramCollect, + setup::ProgramSetup, +}; + +use crate::res::{ResCurrentDir, ResCurrentExe, ResHomeDir, ResTempDir}; + +/// Provides the ability to set up commonly used directory resources for the program. +/// +/// This setup item registers the following directory resources in the program: +/// - `ResCurrentDir`: Current working directory +/// - `ResCurrentExe`: Directory containing the executable +/// - `ResHomeDir`: User's home directory +/// - `ResTempDir`: Temporary directory +pub struct DirectoryEnvironmentSetup { + _collect: PhantomData, +} + +impl Default for DirectoryEnvironmentSetup +where + C: ProgramCollect + 'static, +{ + fn default() -> Self { + Self { + _collect: PhantomData, + } + } +} + +impl ProgramSetup for DirectoryEnvironmentSetup +where + C: ProgramCollect + 'static, +{ + fn setup(self, program: &mut crate::Program) { + program.with_resource(ResCurrentDir::default()); + program.with_resource(ResCurrentExe::default()); + program.with_resource(ResHomeDir::default()); + program.with_resource(ResTempDir::default()); + } +} -- cgit