diff options
| author | 魏曹先生 <1992414357@qq.com> | 2026-05-22 22:10:19 +0800 |
|---|---|---|
| committer | 魏曹先生 <1992414357@qq.com> | 2026-06-17 21:02:05 +0800 |
| commit | 5a5a07c7fad31641d032a743e4e87ffb58ade17d (patch) | |
| tree | b6fbd33e8de82e5f9d9e6b99e3cb2102e47fe3ee /rola-vcs/src | |
Initial commitmain
Diffstat (limited to 'rola-vcs/src')
| -rw-r--r-- | rola-vcs/src/abstracts.rs | 2 | ||||
| -rw-r--r-- | rola-vcs/src/abstracts/dir_pointer.rs | 88 | ||||
| -rw-r--r-- | rola-vcs/src/bucket.rs | 18 | ||||
| -rw-r--r-- | rola-vcs/src/err.rs | 105 | ||||
| -rw-r--r-- | rola-vcs/src/err/io.rs | 10 | ||||
| -rw-r--r-- | rola-vcs/src/lib.rs | 24 | ||||
| -rw-r--r-- | rola-vcs/src/tools.rs | 1 | ||||
| -rw-r--r-- | rola-vcs/src/tools/dir_search.rs | 54 | ||||
| -rw-r--r-- | rola-vcs/src/workdraft.rs | 150 |
9 files changed, 452 insertions, 0 deletions
diff --git a/rola-vcs/src/abstracts.rs b/rola-vcs/src/abstracts.rs new file mode 100644 index 0000000..e707ec3 --- /dev/null +++ b/rola-vcs/src/abstracts.rs @@ -0,0 +1,2 @@ +mod dir_pointer; +pub use dir_pointer::*; diff --git a/rola-vcs/src/abstracts/dir_pointer.rs b/rola-vcs/src/abstracts/dir_pointer.rs new file mode 100644 index 0000000..0e59960 --- /dev/null +++ b/rola-vcs/src/abstracts/dir_pointer.rs @@ -0,0 +1,88 @@ +use std::{ + borrow::Borrow, + ops::{Deref, DerefMut}, + path::PathBuf, +}; + +/// Directory Pointer Data +pub trait DirPtrData { + /// Fix the given path + /// + /// Returns Some(path): use the fixed path + /// Returns None: path cannot be fixed, this pointer is invalid + #[doc(hidden)] + fn fix(raw_path: PathBuf) -> Option<PathBuf>; +} + +#[derive(Debug, Default, Clone)] +pub struct DirPtr<Data: DirPtrData> { + /// Whether the current directory pointer is valid + valid: bool, + + /// Data for the directory pointer + data: Data, + + /// Path to the directory + path: PathBuf, +} + +impl<Data: DirPtrData> DirPtr<Data> { + /// Get a reference to the directory pointer's path + pub fn path_ref(&self) -> &PathBuf { + &self.path + } + + /// Get the directory pointer's path + pub fn path(&self) -> PathBuf { + self.path.clone() + } + + /// Returns whether the directory pointer is valid + pub fn is_valid(&self) -> bool { + self.valid + } +} + +impl<Data: DirPtrData + Default> DirPtr<Data> { + /// Create a new directory pointer with the given path and default data + pub fn new(path: impl Into<PathBuf>) -> Self { + let path = path.into(); + let fixed = Data::fix(path.clone()); + Self { + valid: fixed.is_some(), + data: Data::default(), + path: fixed.unwrap_or(path), + } + } +} + +impl<Data: DirPtrData> AsRef<DirPtr<Data>> for DirPtr<Data> { + fn as_ref(&self) -> &DirPtr<Data> { + self + } +} + +impl<Data: DirPtrData> Deref for DirPtr<Data> { + type Target = Data; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl<Data: DirPtrData> DerefMut for DirPtr<Data> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data + } +} + +impl<Data: DirPtrData> Borrow<Data> for DirPtr<Data> { + fn borrow(&self) -> &Data { + &self.data + } +} + +/// Create a new directory pointer with the given path and default data +pub fn dir_ptr<Data: DirPtrData + Default>(path: impl Into<PathBuf>) -> DirPtr<Data> { + DirPtr::new(path) +} diff --git a/rola-vcs/src/bucket.rs b/rola-vcs/src/bucket.rs new file mode 100644 index 0000000..40de6f8 --- /dev/null +++ b/rola-vcs/src/bucket.rs @@ -0,0 +1,18 @@ +//! Bucket - Rorolala Storage Unit + +use crate::{ + DirPtrData, DirSearchPattern, bucket::constants::ROLA_BUCKET_CONFIG_FILE, dir_search_prev, +}; + +pub mod constants { + /// The name of the bucket config file + pub const ROLA_BUCKET_CONFIG_FILE: &str = "rorolala.toml"; +} + +pub struct Bucket; + +impl DirPtrData for Bucket { + fn fix(raw_path: std::path::PathBuf) -> Option<std::path::PathBuf> { + dir_search_prev(raw_path, DirSearchPattern::File(ROLA_BUCKET_CONFIG_FILE)) + } +} diff --git a/rola-vcs/src/err.rs b/rola-vcs/src/err.rs new file mode 100644 index 0000000..71e0a34 --- /dev/null +++ b/rola-vcs/src/err.rs @@ -0,0 +1,105 @@ +//! Error +//! +//! This module is used to create and log Rorolala standard errors + +use std::{ + fmt::{Display, Formatter}, + io::Error as IoError, +}; + +mod io; + +/// Rorolala standard error +#[derive(Default)] +pub struct RolaError { + pub module: RolaModule, + pub data: RolaErrorData, + pub message: String, +} + +/// Rorolala module, used to locate the source of an error +#[derive(Debug, Eq, Default)] +#[repr(u8)] +pub enum RolaModule { + #[default] + Empty, + + Bucket, + Workdraft, +} + +/// Error data +#[derive(Debug, Default)] +pub enum RolaErrorData { + #[default] + Empty, + + /// IO error + IO(IoError), +} + +impl RolaError { + /// Create a new RolaError + pub fn new(module: RolaModule, data: RolaErrorData, message: String) -> Self { + Self { + module, + data, + message, + } + } + + /// Create an empty RolaError + pub fn empty() -> Self { + Self { + module: RolaModule::Empty, + data: RolaErrorData::Empty, + message: String::new(), + } + } + + /// Set the module + pub fn with_module(mut self, module: RolaModule) -> Self { + self.module = module; + self + } + + /// Set the message + pub fn with_message(mut self, message: String) -> Self { + self.message = message; + self + } + + /// Set the error data + pub fn with_data(mut self, data: RolaErrorData) -> Self { + self.data = data; + self + } +} + +impl Display for RolaError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +impl PartialEq for RolaError { + fn eq(&self, other: &Self) -> bool { + self.message == other.message && self.module == other.module + } +} + +impl Display for RolaModule { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl PartialEq for RolaModule { + fn eq(&self, other: &Self) -> bool { + matches!( + (self, other), + (RolaModule::Bucket, RolaModule::Bucket) + | (RolaModule::Workdraft, RolaModule::Workdraft) + ) + } +} diff --git a/rola-vcs/src/err/io.rs b/rola-vcs/src/err/io.rs new file mode 100644 index 0000000..8dd4c1a --- /dev/null +++ b/rola-vcs/src/err/io.rs @@ -0,0 +1,10 @@ +use crate::{RolaError, RolaErrorData, RolaModule}; + +impl From<(RolaModule, std::io::Error)> for RolaError { + fn from(val: (RolaModule, std::io::Error)) -> Self { + let (module, io_err) = val; + let message = io_err.to_string(); + let data = RolaErrorData::IO(io_err); + RolaError::new(module, data, message) + } +} diff --git a/rola-vcs/src/lib.rs b/rola-vcs/src/lib.rs new file mode 100644 index 0000000..7672ff9 --- /dev/null +++ b/rola-vcs/src/lib.rs @@ -0,0 +1,24 @@ +#![allow(dead_code)] + +mod bucket; + +mod abstracts; +pub use abstracts::*; + +mod workdraft; +pub use workdraft::*; + +mod tools; +pub use tools::*; + +mod err; +pub use err::*; + +#[doc(hidden)] +#[macro_export] +macro_rules! include_mod { + ($module:ident) => { + mod $module; + pub use $module::*; + }; +} diff --git a/rola-vcs/src/tools.rs b/rola-vcs/src/tools.rs new file mode 100644 index 0000000..43b4e33 --- /dev/null +++ b/rola-vcs/src/tools.rs @@ -0,0 +1 @@ +crate::include_mod!(dir_search); diff --git a/rola-vcs/src/tools/dir_search.rs b/rola-vcs/src/tools/dir_search.rs new file mode 100644 index 0000000..3d32c70 --- /dev/null +++ b/rola-vcs/src/tools/dir_search.rs @@ -0,0 +1,54 @@ +use std::path::PathBuf; + +pub enum DirSearchPattern<'a> { + File(&'a str), + Dir(&'a str), +} + +/// Searches upward from the given path towards parent directories. +/// If any ancestor directory contains a file or directory matching the pattern, +/// returns that ancestor directory's path. +pub fn dir_search_prev(path: impl Into<PathBuf>, pattern: DirSearchPattern) -> Option<PathBuf> { + let mut current: PathBuf = path.into(); + // Canonicalize the path if possible to ensure absolute traversal + if let Ok(canonical) = current.canonicalize() { + current = canonical; + } else { + // If canonicalization fails (e.g. path does not exist yet), + // try to make it absolute using current dir + if current.is_relative() + && let Ok(cwd) = std::env::current_dir() { + current = cwd.join(¤t); + } + } + + loop { + // Check if the current directory exists and is a directory + if current.is_dir() { + let has_match = match &pattern { + DirSearchPattern::File(name) => { + let mut entry = current.clone(); + entry.push(name); + entry.is_file() + } + DirSearchPattern::Dir(name) => { + let mut entry = current.clone(); + entry.push(name); + entry.is_dir() + } + }; + + if has_match { + return Some(current); + } + } + + // Try to go to the parent directory + if !current.pop() { + // pop() returns false when there's no parent + break; + } + } + + None +} diff --git a/rola-vcs/src/workdraft.rs b/rola-vcs/src/workdraft.rs new file mode 100644 index 0000000..45bb418 --- /dev/null +++ b/rola-vcs/src/workdraft.rs @@ -0,0 +1,150 @@ +//! Work Draft +//! +//! Work Draft is the local workspace of `Rorolala`, used to store files being modified + +use std::path::PathBuf; + +use crate::{ + DirPtrData, DirSearchPattern, RolaError, RolaModule, dir_search_prev, + workdraft::constants::ROLA_DRAFT_DIR, +}; + +#[rorolala_internal_macros::constants] +pub mod constants { + /// The name of the workdraft directory + pub const ROLA_DRAFT_DIR: &str = ".rola"; + + /// The name of the directory containing bucket bindings + pub const ROLA_BINDED_BUCKETS_DIR: &str = ".rola/BIND/"; + + /// The name of the bind file + pub const ROLA_BINDED_BUCKET_FILE: &str = ".rola/BIND/{bucket}"; +} + +/// Work Draft Pointer +/// +/// This struct is used to point to an operable local work draft directory on disk +#[derive(Debug, Default, Clone)] +pub struct WorkDraft; + +impl DirPtrData for WorkDraft { + fn fix(raw_path: PathBuf) -> Option<PathBuf> { + let draft_dir = ROLA_DRAFT_DIR(); + dir_search_prev(raw_path, DirSearchPattern::Dir(&draft_dir)) + } +} + +impl WorkDraft { + /// Creates a new work draft directory at the given path + pub fn create(path: PathBuf) -> Result<(), RolaError> { + let dir = path.join(ROLA_DRAFT_DIR()); + std::fs::create_dir_all(dir).map_err(|e| RolaError::from((RolaModule::Workdraft, e)))?; + Ok(()) + } +} + +/// Module for managing workdraft bucket bindings +pub mod bucket_bind_mgr { + use std::path::PathBuf; + + use crate::{ + DirPtr, RolaError, RolaModule, WorkDraft, constants::ROLA_BINDED_BUCKETS_DIR, + workdraft::constants::ROLA_BINDED_BUCKET_FILE, + }; + + impl DirPtr<WorkDraft> { + /// Returns the path to the bind file for this work draft + pub fn bind_bucket_path(&self, bucket: impl AsRef<str>) -> PathBuf { + self.path().join(ROLA_BINDED_BUCKET_FILE(bucket)) + } + + /// Returns the path to the bind file for this work draft + pub fn bind_bucket(&self, bucket: impl AsRef<str>) -> Result<String, RolaError> { + let bucket_path = self.bind_bucket_path(bucket); + let parent = bucket_path.parent().ok_or_else(|| -> RolaError { + RolaError::from(( + RolaModule::Workdraft, + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("Invalid bucket path: {}", bucket_path.to_string_lossy()), + ), + )) + })?; + + if !parent.exists() { + std::fs::create_dir_all(parent) + .map_err(|e| RolaError::from((RolaModule::Workdraft, e)))?; + } + + match std::fs::read_to_string(bucket_path) { + Ok(str) => Ok(str), + Err(err) => Err(RolaError::from((RolaModule::Workdraft, err))), + } + } + + /// Binds the work draft to the specified bucket + pub fn bind_bucket_to(&self, bucket: &str) -> Result<(), RolaError> { + let bucket_path = self.bind_bucket_path(bucket); + let parent = bucket_path.parent().ok_or_else(|| -> RolaError { + RolaError::from(( + RolaModule::Workdraft, + std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid bucket path"), + )) + })?; + if !parent.exists() { + std::fs::create_dir_all(parent) + .map_err(|e| RolaError::from((RolaModule::Workdraft, e)))?; + } + std::fs::write(&bucket_path, bucket) + .map_err(|e| RolaError::from((RolaModule::Workdraft, e)))?; + Ok(()) + } + + /// Returns all bound bucket names for this work draft + pub fn binded_buckets(&self) -> Result<Vec<String>, RolaError> { + let bind_dir = self.path().join(ROLA_BINDED_BUCKETS_DIR()); + if !bind_dir.exists() { + return Ok(Vec::new()); + } + + let mut buckets = Vec::new(); + let entries = std::fs::read_dir(&bind_dir) + .map_err(|e| RolaError::from((RolaModule::Workdraft, e)))?; + + for entry in entries { + let entry = entry.map_err(|e| RolaError::from((RolaModule::Workdraft, e)))?; + let path = entry.path(); + + if path.is_file() { + // Read the file content which contains the bucket name + if let Ok(content) = std::fs::read_to_string(&path) { + let bucket = content.trim().to_string(); + if !bucket.is_empty() { + buckets.push(bucket); + } + } + } + } + + Ok(buckets) + } + + /// Removes the binding for the specified bucket + pub fn unbind_bucket(&self, bucket: impl AsRef<str>) -> Result<(), RolaError> { + let bucket_path = self.bind_bucket_path(bucket); + if bucket_path.exists() { + std::fs::remove_file(&bucket_path) + .map_err(|e| RolaError::from((RolaModule::Workdraft, e)))?; + } + Ok(()) + } + + /// Removes bindings for all specified buckets + pub fn unbind_buckets(&self, buckets: &[impl AsRef<str>]) -> Result<(), RolaError> { + for bucket in buckets { + self.unbind_bucket(bucket)?; + } + Ok(()) + } + } +} |
