diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-03-11 17:38:08 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-03-11 17:38:08 +0800 |
| commit | 4effbd209edf96637d7da2b7d29ea1a6de3c637a (patch) | |
| tree | 397223a02b80b858ceb41af0126d0c9e731f2047 /systems | |
| parent | 794316c6e925097ef6b87693b4a610b4563309e6 (diff) | |
Add config system and space macro with workspace dependencies
Diffstat (limited to 'systems')
| -rw-r--r-- | systems/_config/Cargo.toml | 4 | ||||
| -rw-r--r-- | systems/_config/src/error.rs | 19 | ||||
| -rw-r--r-- | systems/_config/src/lib.rs | 2 | ||||
| -rw-r--r-- | systems/_config/src/main.rs | 3 | ||||
| -rw-r--r-- | systems/_config/src/rw.rs | 66 | ||||
| -rw-r--r-- | systems/_framework/Cargo.toml | 2 | ||||
| -rw-r--r-- | systems/_framework/space_macro/Cargo.toml | 13 | ||||
| -rw-r--r-- | systems/_framework/space_macro/src/lib.rs | 60 | ||||
| -rw-r--r-- | systems/_framework/src/lib.rs | 2 | ||||
| -rw-r--r-- | systems/_framework/src/space.rs | 61 | ||||
| -rw-r--r-- | systems/sheet/macros/Cargo.toml | 6 |
11 files changed, 225 insertions, 13 deletions
diff --git a/systems/_config/Cargo.toml b/systems/_config/Cargo.toml index afc1d81..11e08f3 100644 --- a/systems/_config/Cargo.toml +++ b/systems/_config/Cargo.toml @@ -5,3 +5,7 @@ version.workspace = true [dependencies] serde.workspace = true +serde_json.workspace = true +serde_yaml.workspace = true +thiserror.workspace = true +toml.workspace = true diff --git a/systems/_config/src/error.rs b/systems/_config/src/error.rs new file mode 100644 index 0000000..791aab2 --- /dev/null +++ b/systems/_config/src/error.rs @@ -0,0 +1,19 @@ +use std::path::PathBuf; + +#[derive(thiserror::Error, Debug)] +pub enum ConfigError { + #[error("Failed to read config file: {0}")] + ConfigReadFailed(#[source] std::io::Error), + + #[error("Failed to write config file: {0}")] + ConfigWriteFailed(#[source] std::io::Error), + + #[error("Config file not found: {0}")] + FileNotFound(PathBuf), + + #[error("IO error: {0}")] + Io(#[source] std::io::Error), + + #[error("Other error: {0}")] + Other(String), +} diff --git a/systems/_config/src/lib.rs b/systems/_config/src/lib.rs new file mode 100644 index 0000000..26fe31a --- /dev/null +++ b/systems/_config/src/lib.rs @@ -0,0 +1,2 @@ +pub mod error; +pub mod rw; diff --git a/systems/_config/src/main.rs b/systems/_config/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/systems/_config/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/systems/_config/src/rw.rs b/systems/_config/src/rw.rs new file mode 100644 index 0000000..36ea00c --- /dev/null +++ b/systems/_config/src/rw.rs @@ -0,0 +1,66 @@ +use crate::error::ConfigError; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +pub async fn read_config<C>(path: impl AsRef<Path>) -> Result<C, ConfigError> +where + C: for<'a> Deserialize<'a>, +{ + let path_ref = path.as_ref(); + let ext = path_ref + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("json"); + let format = ext_fmt(&ext.to_lowercase()); + + let content = std::fs::read_to_string(path_ref).map_err(ConfigError::ConfigReadFailed)?; + + match format { + Format::Yaml => { + serde_yaml::from_str(&content).map_err(|e| ConfigError::Other(e.to_string())) + } + Format::Toml => toml::from_str(&content).map_err(|e| ConfigError::Other(e.to_string())), + Format::Json => { + serde_json::from_str(&content).map_err(|e| ConfigError::Other(e.to_string())) + } + } +} + +pub async fn write_config<C>(path: impl AsRef<Path>, config: &C) -> Result<(), ConfigError> +where + C: Serialize, +{ + let path_ref = path.as_ref(); + let ext = path_ref + .extension() + .and_then(|ext| ext.to_str()) + .unwrap_or("json"); + let format = ext_fmt(&ext.to_lowercase()); + + let content = match format { + Format::Yaml => { + serde_yaml::to_string(config).map_err(|e| ConfigError::Other(e.to_string()))? + } + Format::Toml => toml::to_string(config).map_err(|e| ConfigError::Other(e.to_string()))?, + Format::Json => { + serde_json::to_string_pretty(config).map_err(|e| ConfigError::Other(e.to_string()))? + } + }; + + std::fs::write(path_ref, content).map_err(ConfigError::ConfigWriteFailed) +} + +enum Format { + Yaml, + Toml, + Json, +} + +fn ext_fmt(ext: &str) -> Format { + match ext { + "yaml" | "yml" => Format::Yaml, + "toml" | "tml" => Format::Toml, + "json" => Format::Json, + _ => Format::Json, + } +} diff --git a/systems/_framework/Cargo.toml b/systems/_framework/Cargo.toml index 663b7be..25d5fee 100644 --- a/systems/_framework/Cargo.toml +++ b/systems/_framework/Cargo.toml @@ -4,6 +4,8 @@ edition = "2024" version.workspace = true [dependencies] +space_macro = { path = "space_macro" } + just_fmt.workspace = true thiserror.workspace = true tokio.workspace = true diff --git a/systems/_framework/space_macro/Cargo.toml b/systems/_framework/space_macro/Cargo.toml new file mode 100644 index 0000000..dea3f21 --- /dev/null +++ b/systems/_framework/space_macro/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "space_macro" +version.workspace = true +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn.workspace = true +quote.workspace = true +proc-macro2.workspace = true +just_fmt.workspace = true diff --git a/systems/_framework/space_macro/src/lib.rs b/systems/_framework/space_macro/src/lib.rs new file mode 100644 index 0000000..7877181 --- /dev/null +++ b/systems/_framework/space_macro/src/lib.rs @@ -0,0 +1,60 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{DeriveInput, parse_macro_input}; + +#[proc_macro_derive(SpaceRootTest)] +pub fn space_root_test_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + + let test_mod_name = syn::Ident::new( + &format!( + "test_{}_space_root", + just_fmt::snake_case!(name.to_string()) + ), + name.span(), + ); + + let expanded = quote! { + #[cfg(test)] + mod #test_mod_name { + use super::*; + use framework::space::Space; + use std::env::{current_dir, set_current_dir}; + use tokio::fs::{create_dir_all, remove_dir_all}; + + #[tokio::test] + async fn test_create_space() { + let temp_dir = current_dir().unwrap().join(".temp").join(stringify!(#name)); + remove_dir_all(&temp_dir).await.ok(); + create_dir_all(&temp_dir).await.unwrap(); + set_current_dir(&temp_dir).unwrap(); + + let space = Space::new(#name::default()); + + assert!(space.space_dir_current().is_err()); + + space.init_here().await.unwrap(); + + assert!(space.space_dir_current().is_ok()); + + let space_dir = space.space_dir_current().unwrap(); + let pattern = <#name as framework::space::SpaceRoot>::get_pattern(); + + match pattern { + framework::space::SpaceRootFindPattern::IncludeDotDir(dir_name) => { + let expected_dir = space_dir.join(dir_name); + assert!(expected_dir.exists(), "Space directory {:?} should exist", expected_dir); + } + framework::space::SpaceRootFindPattern::IncludeFile(file_name) => { + let expected_file = space_dir.join(file_name); + assert!(expected_file.exists(), "Space file {:?} should exist", expected_file); + } + } + } + } + }; + + TokenStream::from(expanded) +} diff --git a/systems/_framework/src/lib.rs b/systems/_framework/src/lib.rs index 9ffc73e..b0a3f9b 100644 --- a/systems/_framework/src/lib.rs +++ b/systems/_framework/src/lib.rs @@ -1 +1,3 @@ pub mod space; +#[allow(unused_imports)] +pub use space_macro::*; diff --git a/systems/_framework/src/space.rs b/systems/_framework/src/space.rs index 47d3c0d..c57fa26 100644 --- a/systems/_framework/src/space.rs +++ b/systems/_framework/src/space.rs @@ -10,7 +10,7 @@ use std::{ pub mod error; -pub struct Space<T: SpaceContent> { +pub struct Space<T: SpaceRoot> { path_format_cfg: PathFormatConfig, content: T, @@ -18,7 +18,7 @@ pub struct Space<T: SpaceContent> { current_dir: Option<PathBuf>, } -impl<T: SpaceContent> Space<T> { +impl<T: SpaceRoot> Space<T> { /// Create a new `Space` instance with the given content. pub fn new(content: T) -> Self { Space { @@ -32,6 +32,49 @@ impl<T: SpaceContent> Space<T> { } } + /// Initialize a space at the given path. + /// + /// Checks if a space exists at the given path. If not, creates a new space + /// by calling `T::create_space()` at that path. + pub async fn init(&self, path: impl AsRef<Path>) -> Result<(), SpaceError> { + let path = path.as_ref(); + let pattern = T::get_pattern(); + + if !find_space_root_with(path.to_path_buf(), pattern).is_ok() { + T::create_space(path).await?; + } + Ok(()) + } + + /// Create a new space at the given path with the specified name. + /// + /// The full path is constructed as `path/name`. Checks if a space already + /// exists at that location. If not, creates a new space by calling + /// `T::create_space()` at that path. + pub async fn create(&self, path: impl AsRef<Path>, name: &str) -> Result<(), SpaceError> { + let full_path = path.as_ref().join(name); + self.init(full_path).await + } + + /// Initialize a space in the current directory. + /// + /// Checks if a space exists in the current directory. If not, creates a new space + /// by calling `T::create_space()` at the current directory. + pub async fn init_here(&self) -> Result<(), SpaceError> { + let current_dir = self.current_dir()?; + self.init(current_dir).await + } + + /// Create a new space in the current directory with the specified name. + /// + /// The full path is constructed as `current_dir/name`. Checks if a space already + /// exists at that location. If not, creates a new space by calling + /// `T::create_space()` at that path. + pub async fn create_here(&self, name: &str) -> Result<(), SpaceError> { + let current_dir = self.current_dir()?; + self.create(current_dir, name).await + } + /// Consume the `Space`, returning the inner content. pub fn into_inner(self) -> T { self.content @@ -101,7 +144,7 @@ impl<T: SpaceContent> Space<T> { } } -impl<T: SpaceContent> Space<T> { +impl<T: SpaceRoot> Space<T> { /// Convert a relative path to an absolute path within the space. /// /// The path is formatted according to the space's path format configuration. @@ -309,27 +352,31 @@ impl<T: SpaceContent> Space<T> { } } -impl<T: SpaceContent> From<T> for Space<T> { +impl<T: SpaceRoot> From<T> for Space<T> { fn from(content: T) -> Self { Space::<T>::new(content) } } -impl<T: SpaceContent> AsRef<T> for Space<T> { +impl<T: SpaceRoot> AsRef<T> for Space<T> { fn as_ref(&self) -> &T { &self.content } } -impl<T: SpaceContent> Deref for Space<T> { +impl<T: SpaceRoot> Deref for Space<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.as_ref() } } -pub trait SpaceContent { +pub trait SpaceRoot: Sized { + /// Get the pattern used to identify the space root fn get_pattern() -> SpaceRootFindPattern; + + /// Given a non-space directory, implement logic to make it a space-recognizable directory + fn create_space(path: &Path) -> impl Future<Output = Result<(), SpaceError>> + Send; } pub enum SpaceRootFindPattern { diff --git a/systems/sheet/macros/Cargo.toml b/systems/sheet/macros/Cargo.toml index d7a59d1..d1d6e59 100644 --- a/systems/sheet/macros/Cargo.toml +++ b/systems/sheet/macros/Cargo.toml @@ -7,6 +7,6 @@ edition = "2024" proc-macro = true [dependencies] -syn = { version = "2.0", features = ["full", "extra-traits"] } -quote = "1.0" -proc-macro2 = "1.0" +syn.workspace = true +quote.workspace = true +proc-macro2.workspace = true |
