summaryrefslogtreecommitdiff
path: root/utils/cfg_file/src
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-01-12 04:28:28 +0800
committer魏曹先生 <1992414357@qq.com>2026-01-12 04:51:34 +0800
commitc5fb22694e95f12c24b8d8af76999be7aea3fcec (patch)
tree399d8a24ce491fb635f3d09f2123290fe784059e /utils/cfg_file/src
parent444754489aca0454eb54e15a49fb8a6db0b68a07 (diff)
Reorganize crate structure and move documentation files
Diffstat (limited to 'utils/cfg_file/src')
-rw-r--r--utils/cfg_file/src/config.rs263
-rw-r--r--utils/cfg_file/src/lib.rs7
2 files changed, 270 insertions, 0 deletions
diff --git a/utils/cfg_file/src/config.rs b/utils/cfg_file/src/config.rs
new file mode 100644
index 0000000..d3f5477
--- /dev/null
+++ b/utils/cfg_file/src/config.rs
@@ -0,0 +1,263 @@
+use async_trait::async_trait;
+use bincode2;
+use ron;
+use serde::{Deserialize, Serialize};
+use std::{
+ borrow::Cow,
+ env::current_dir,
+ io::Error,
+ path::{Path, PathBuf},
+};
+use tokio::{fs, io::AsyncReadExt};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+enum ConfigFormat {
+ Yaml,
+ Toml,
+ Ron,
+ Json,
+ Bincode,
+}
+
+impl ConfigFormat {
+ fn from_filename(filename: &str) -> Option<Self> {
+ if filename.ends_with(".yaml") || filename.ends_with(".yml") {
+ Some(Self::Yaml)
+ } else if filename.ends_with(".toml") || filename.ends_with(".tom") {
+ Some(Self::Toml)
+ } else if filename.ends_with(".ron") {
+ Some(Self::Ron)
+ } else if filename.ends_with(".json") {
+ Some(Self::Json)
+ } else if filename.ends_with(".bcfg") {
+ Some(Self::Bincode)
+ } else {
+ None
+ }
+ }
+}
+
+/// # Trait - ConfigFile
+///
+/// Used to implement more convenient persistent storage functionality for structs
+///
+/// This trait requires the struct to implement Default and serde's Serialize and Deserialize traits
+///
+/// ## Implementation
+///
+/// ```ignore
+/// // Your struct
+/// #[derive(Default, Serialize, Deserialize)]
+/// struct YourData;
+///
+/// impl ConfigFile for YourData {
+/// type DataType = YourData;
+///
+/// // Specify default path
+/// fn default_path() -> Result<PathBuf, Error> {
+/// Ok(current_dir()?.join("data.json"))
+/// }
+/// }
+/// ```
+///
+/// > **Using derive macro**
+/// >
+/// > We provide the derive macro `#[derive(ConfigFile)]`
+/// >
+/// > You can implement this trait more quickly, please check the module cfg_file::cfg_file_derive
+///
+#[async_trait]
+pub trait ConfigFile: Serialize + for<'a> Deserialize<'a> + Default {
+ type DataType: Serialize + for<'a> Deserialize<'a> + Default + Send + Sync;
+
+ fn default_path() -> Result<PathBuf, Error>;
+
+ /// # Read from default path
+ ///
+ /// Read data from the path specified by default_path()
+ ///
+ /// ```ignore
+ /// fn main() -> Result<(), std::io::Error> {
+ /// let data = YourData::read().await?;
+ /// }
+ /// ```
+ async fn read() -> Result<Self::DataType, std::io::Error>
+ where
+ Self: Sized + Send + Sync,
+ {
+ let path = Self::default_path()?;
+ Self::read_from(path).await
+ }
+
+ /// # Read from the given path
+ ///
+ /// Read data from the path specified by the path parameter
+ ///
+ /// ```ignore
+ /// fn main() -> Result<(), std::io::Error> {
+ /// let data_path = current_dir()?.join("data.json");
+ /// let data = YourData::read_from(data_path).await?;
+ /// }
+ /// ```
+ async fn read_from(path: impl AsRef<Path> + Send) -> Result<Self::DataType, std::io::Error>
+ where
+ Self: Sized + Send + Sync,
+ {
+ let path = path.as_ref();
+ let cwd = current_dir()?;
+ let file_path = cwd.join(path);
+
+ // Check if file exists
+ if fs::metadata(&file_path).await.is_err() {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ "Config file not found",
+ ));
+ }
+
+ // Determine file format first
+ let format = file_path
+ .file_name()
+ .and_then(|name| name.to_str())
+ .and_then(ConfigFormat::from_filename)
+ .unwrap_or(ConfigFormat::Bincode); // Default to Bincode
+
+ // Deserialize based on format
+ let result = match format {
+ ConfigFormat::Yaml => {
+ let mut file = fs::File::open(&file_path).await?;
+ let mut contents = String::new();
+ file.read_to_string(&mut contents).await?;
+ serde_yaml::from_str(&contents)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
+ }
+ ConfigFormat::Toml => {
+ let mut file = fs::File::open(&file_path).await?;
+ let mut contents = String::new();
+ file.read_to_string(&mut contents).await?;
+ toml::from_str(&contents)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
+ }
+ ConfigFormat::Ron => {
+ let mut file = fs::File::open(&file_path).await?;
+ let mut contents = String::new();
+ file.read_to_string(&mut contents).await?;
+ ron::from_str(&contents)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
+ }
+ ConfigFormat::Json => {
+ let mut file = fs::File::open(&file_path).await?;
+ let mut contents = String::new();
+ file.read_to_string(&mut contents).await?;
+ serde_json::from_str(&contents)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
+ }
+ ConfigFormat::Bincode => {
+ // For Bincode, we need to read the file as bytes directly
+ let bytes = fs::read(&file_path).await?;
+ bincode2::deserialize(&bytes)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?
+ }
+ };
+
+ Ok(result)
+ }
+
+ /// # Write to default path
+ ///
+ /// Write data to the path specified by default_path()
+ ///
+ /// ```ignore
+ /// fn main() -> Result<(), std::io::Error> {
+ /// let data = YourData::default();
+ /// YourData::write(&data).await?;
+ /// }
+ /// ```
+ async fn write(val: &Self::DataType) -> Result<(), std::io::Error>
+ where
+ Self: Sized + Send + Sync,
+ {
+ let path = Self::default_path()?;
+ Self::write_to(val, path).await
+ }
+ /// # Write to the given path
+ ///
+ /// Write data to the path specified by the path parameter
+ ///
+ /// ```ignore
+ /// fn main() -> Result<(), std::io::Error> {
+ /// let data = YourData::default();
+ /// let data_path = current_dir()?.join("data.json");
+ /// YourData::write_to(&data, data_path).await?;
+ /// }
+ /// ```
+ async fn write_to(
+ val: &Self::DataType,
+ path: impl AsRef<Path> + Send,
+ ) -> Result<(), std::io::Error>
+ where
+ Self: Sized + Send + Sync,
+ {
+ let path = path.as_ref();
+
+ if let Some(parent) = path.parent()
+ && !parent.exists()
+ {
+ tokio::fs::create_dir_all(parent).await?;
+ }
+
+ let cwd = current_dir()?;
+ let file_path = cwd.join(path);
+
+ // Determine file format
+ let format = file_path
+ .file_name()
+ .and_then(|name| name.to_str())
+ .and_then(ConfigFormat::from_filename)
+ .unwrap_or(ConfigFormat::Bincode); // Default to Bincode
+
+ match format {
+ ConfigFormat::Yaml => {
+ let contents = serde_yaml::to_string(val)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
+ fs::write(&file_path, contents).await?
+ }
+ ConfigFormat::Toml => {
+ let contents = toml::to_string(val)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
+ fs::write(&file_path, contents).await?
+ }
+ ConfigFormat::Ron => {
+ let mut pretty_config = ron::ser::PrettyConfig::new();
+ pretty_config.new_line = Cow::from("\n");
+ pretty_config.indentor = Cow::from(" ");
+
+ let contents = ron::ser::to_string_pretty(val, pretty_config)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
+ fs::write(&file_path, contents).await?
+ }
+ ConfigFormat::Json => {
+ let contents = serde_json::to_string(val)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
+ fs::write(&file_path, contents).await?
+ }
+ ConfigFormat::Bincode => {
+ let bytes = bincode2::serialize(val)
+ .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
+ fs::write(&file_path, bytes).await?
+ }
+ }
+ Ok(())
+ }
+
+ /// Check if the file returned by `default_path` exists
+ fn exist() -> bool
+ where
+ Self: Sized + Send + Sync,
+ {
+ let Ok(path) = Self::default_path() else {
+ return false;
+ };
+ path.exists()
+ }
+}
diff --git a/utils/cfg_file/src/lib.rs b/utils/cfg_file/src/lib.rs
new file mode 100644
index 0000000..72246e7
--- /dev/null
+++ b/utils/cfg_file/src/lib.rs
@@ -0,0 +1,7 @@
+#[cfg(feature = "derive")]
+extern crate cfg_file_derive;
+
+#[cfg(feature = "derive")]
+pub use cfg_file_derive::*;
+
+pub mod config;