summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-03-11 17:38:08 +0800
committer魏曹先生 <1992414357@qq.com>2026-03-11 17:38:08 +0800
commit4effbd209edf96637d7da2b7d29ea1a6de3c637a (patch)
tree397223a02b80b858ceb41af0126d0c9e731f2047
parent794316c6e925097ef6b87693b4a610b4563309e6 (diff)
Add config system and space macro with workspace dependencies
-rw-r--r--Cargo.lock15
-rw-r--r--Cargo.toml23
-rw-r--r--systems/_config/Cargo.toml4
-rw-r--r--systems/_config/src/error.rs19
-rw-r--r--systems/_config/src/lib.rs2
-rw-r--r--systems/_config/src/main.rs3
-rw-r--r--systems/_config/src/rw.rs66
-rw-r--r--systems/_framework/Cargo.toml2
-rw-r--r--systems/_framework/space_macro/Cargo.toml13
-rw-r--r--systems/_framework/space_macro/src/lib.rs60
-rw-r--r--systems/_framework/src/lib.rs2
-rw-r--r--systems/_framework/src/space.rs61
-rw-r--r--systems/sheet/macros/Cargo.toml6
13 files changed, 258 insertions, 18 deletions
diff --git a/Cargo.lock b/Cargo.lock
index d349d5c..0a45ec3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -397,6 +397,10 @@ name = "config_system"
version = "0.1.0"
dependencies = [
"serde",
+ "serde_json",
+ "serde_yaml",
+ "thiserror",
+ "toml",
]
[[package]]
@@ -700,6 +704,7 @@ name = "framework"
version = "0.1.0"
dependencies = [
"just_fmt",
+ "space_macro",
"thiserror",
"tokio",
]
@@ -1806,6 +1811,16 @@ dependencies = [
]
[[package]]
+name = "space_macro"
+version = "0.1.0"
+dependencies = [
+ "just_fmt",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index dff66cf..20d97d4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,9 +28,11 @@ members = [
"systems/_asset",
"systems/_asset/macros",
"systems/_asset/test",
+ "systems/_config",
"systems/_constants",
"systems/_constants/macros",
"systems/_framework",
+ "systems/_framework/space_macro",
"systems/sheet",
"systems/sheet/macros",
"systems/vault",
@@ -50,7 +52,6 @@ members = [
"legacy_utils/cfg_file",
"legacy_utils/cfg_file/cfg_file_derive",
"legacy_utils/cfg_file/cfg_file_test",
- "systems/_config",
# LEGACY AREA
]
@@ -58,15 +59,27 @@ members = [
version = "0.1.0"
[workspace.dependencies]
-just_fmt = "0.1.2"
+# Macro & code gen
proc-macro2 = "1.0"
quote = "1.0"
-regex = "1.12"
-serde = { version = "1", features = ["derive"] }
syn = { version = "2.0", features = ["full", "extra-traits"] }
-thiserror = "2"
+
+# Serialization
+serde = { version = "1", features = ["derive"] }
+serde_json = "1"
+serde_yaml = "0.9"
+toml = "0.9"
+
+# Text processing
+just_fmt = "0.1.2"
+regex = "1.12"
+
+# Async runtime
tokio = { version = "1.50", features = ["full"] }
+# Error handling
+thiserror = "2"
+
[profile.dev]
opt-level = 0
debug = true
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