refactor: rewrite in rust
This commit is contained in:
parent
20a3e8b437
commit
ed493cff29
72 changed files with 5684 additions and 3688 deletions
42
src/models/dimension.rs
Normal file
42
src/models/dimension.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A Dimension represents an axis along which shards can be categorized.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Dimension {
|
||||
/// Human-readable name for display purposes.
|
||||
pub display_name: String,
|
||||
|
||||
/// Optional description of what this dimension represents.
|
||||
#[serde(default)]
|
||||
pub comment: Option<String>,
|
||||
|
||||
/// Whether values in this dimension should propagate to child shards.
|
||||
#[serde(default)]
|
||||
pub propagate: bool,
|
||||
|
||||
/// Tracks whether 'propagate' was explicitly set (for merge semantics).
|
||||
#[serde(skip)]
|
||||
pub propagate_was_set: bool,
|
||||
}
|
||||
|
||||
impl Dimension {
|
||||
pub fn new(display_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
display_name: display_name.into(),
|
||||
comment: None,
|
||||
propagate: false,
|
||||
propagate_was_set: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_comment(mut self, comment: impl Into<String>) -> Self {
|
||||
self.comment = Some(comment.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_propagate(mut self, propagate: bool) -> Self {
|
||||
self.propagate = propagate;
|
||||
self.propagate_was_set = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
63
src/models/localized_shard.rs
Normal file
63
src/models/localized_shard.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A LocalizedShard extends a Shard with temporal and dimensional context.
|
||||
/// It represents a shard that has been placed within the repository's coordinate system.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct LocalizedShard {
|
||||
/// Markers are tags that appear at the beginning of a line before any content.
|
||||
pub markers: Vec<String>,
|
||||
|
||||
/// Tags are @-prefixed identifiers that appear after content has started.
|
||||
pub tags: Vec<String>,
|
||||
|
||||
/// The starting line number in the source file (1-indexed).
|
||||
pub start_line: usize,
|
||||
|
||||
/// The ending line number in the source file (1-indexed).
|
||||
pub end_line: usize,
|
||||
|
||||
/// The moment in time this shard is associated with.
|
||||
pub moment: DateTime<Utc>,
|
||||
|
||||
/// The dimensional location of this shard (dimension name -> value).
|
||||
pub location: IndexMap<String, String>,
|
||||
|
||||
/// Child shards nested within this shard.
|
||||
pub children: Vec<LocalizedShard>,
|
||||
}
|
||||
|
||||
impl LocalizedShard {
|
||||
pub fn new(start_line: usize, end_line: usize, moment: DateTime<Utc>) -> Self {
|
||||
Self {
|
||||
markers: Vec::new(),
|
||||
tags: Vec::new(),
|
||||
start_line,
|
||||
end_line,
|
||||
moment,
|
||||
location: IndexMap::new(),
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_markers(mut self, markers: Vec<String>) -> Self {
|
||||
self.markers = markers;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
|
||||
self.tags = tags;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_location(mut self, location: IndexMap<String, String>) -> Self {
|
||||
self.location = location;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_children(mut self, children: Vec<LocalizedShard>) -> Self {
|
||||
self.children = children;
|
||||
self
|
||||
}
|
||||
}
|
||||
76
src/models/marker.rs
Normal file
76
src/models/marker.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use indexmap::IndexSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// A MarkerPlacement defines how a marker affects dimension values.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct MarkerPlacement {
|
||||
/// Only apply this placement if all markers in `if_with` are also present.
|
||||
#[serde(default)]
|
||||
pub if_with: IndexSet<String>,
|
||||
|
||||
/// The dimension to place a value in.
|
||||
pub dimension: String,
|
||||
|
||||
/// The value to place. If None, uses the marker name itself.
|
||||
#[serde(default)]
|
||||
pub value: Option<String>,
|
||||
|
||||
/// Whether this placement should overwrite existing values in the dimension.
|
||||
#[serde(default = "default_overwrites")]
|
||||
pub overwrites: bool,
|
||||
}
|
||||
|
||||
fn default_overwrites() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl MarkerPlacement {
|
||||
pub fn new(dimension: impl Into<String>) -> Self {
|
||||
Self {
|
||||
if_with: IndexSet::new(),
|
||||
dimension: dimension.into(),
|
||||
value: None,
|
||||
overwrites: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_if_with(mut self, if_with: impl IntoIterator<Item = impl Into<String>>) -> Self {
|
||||
self.if_with = if_with.into_iter().map(Into::into).collect();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_value(mut self, value: impl Into<String>) -> Self {
|
||||
self.value = Some(value.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_overwrites(mut self, overwrites: bool) -> Self {
|
||||
self.overwrites = overwrites;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A Marker defines how an @-tag should be interpreted for dimensional placement.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Marker {
|
||||
/// Human-readable name for display purposes.
|
||||
pub display_name: String,
|
||||
|
||||
/// The dimensional placements this marker creates.
|
||||
#[serde(default)]
|
||||
pub placements: Vec<MarkerPlacement>,
|
||||
}
|
||||
|
||||
impl Marker {
|
||||
pub fn new(display_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
display_name: display_name.into(),
|
||||
placements: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_placements(mut self, placements: Vec<MarkerPlacement>) -> Self {
|
||||
self.placements = placements;
|
||||
self
|
||||
}
|
||||
}
|
||||
11
src/models/mod.rs
Normal file
11
src/models/mod.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
mod dimension;
|
||||
mod localized_shard;
|
||||
mod marker;
|
||||
mod shard;
|
||||
mod timecard;
|
||||
|
||||
pub use dimension::Dimension;
|
||||
pub use localized_shard::LocalizedShard;
|
||||
pub use marker::{Marker, MarkerPlacement};
|
||||
pub use shard::{RepositoryConfiguration, Shard, StreamFile};
|
||||
pub use timecard::{SpecialDayType, Timecard, Timesheet};
|
||||
115
src/models/shard.rs
Normal file
115
src/models/shard.rs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
use indexmap::IndexMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{Dimension, Marker};
|
||||
|
||||
/// A Shard represents a section of a markdown file that may contain markers and tags.
|
||||
/// Shards form a tree structure where children inherit context from their parents.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Shard {
|
||||
/// Markers are tags that appear at the beginning of a line before any content.
|
||||
/// They define dimensional placement for the shard.
|
||||
#[serde(default)]
|
||||
pub markers: Vec<String>,
|
||||
|
||||
/// Tags are @-prefixed identifiers that appear after content has started.
|
||||
/// They are informational but don't affect dimensional placement.
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
|
||||
/// The starting line number in the source file (1-indexed).
|
||||
pub start_line: usize,
|
||||
|
||||
/// The ending line number in the source file (1-indexed).
|
||||
pub end_line: usize,
|
||||
|
||||
/// Child shards nested within this shard.
|
||||
#[serde(default)]
|
||||
pub children: Vec<Shard>,
|
||||
}
|
||||
|
||||
impl Shard {
|
||||
pub fn new(start_line: usize, end_line: usize) -> Self {
|
||||
Self {
|
||||
markers: Vec::new(),
|
||||
tags: Vec::new(),
|
||||
start_line,
|
||||
end_line,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_markers(mut self, markers: Vec<String>) -> Self {
|
||||
self.markers = markers;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_tags(mut self, tags: Vec<String>) -> Self {
|
||||
self.tags = tags;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_children(mut self, children: Vec<Shard>) -> Self {
|
||||
self.children = children;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A StreamFile represents a parsed markdown file with its associated shard tree.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct StreamFile {
|
||||
/// The file name or path of the source file.
|
||||
pub file_name: String,
|
||||
|
||||
/// The root shard representing the entire file's content structure.
|
||||
pub shard: Option<Shard>,
|
||||
}
|
||||
|
||||
impl StreamFile {
|
||||
pub fn new(file_name: impl Into<String>) -> Self {
|
||||
Self {
|
||||
file_name: file_name.into(),
|
||||
shard: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_shard(mut self, shard: Shard) -> Self {
|
||||
self.shard = Some(shard);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Repository configuration defines the dimensions and markers used to organize shards.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RepositoryConfiguration {
|
||||
/// Dimensions define the axes along which shards can be positioned.
|
||||
pub dimensions: IndexMap<String, Dimension>,
|
||||
|
||||
/// Markers define how @-tags map to dimension placements.
|
||||
pub markers: IndexMap<String, Marker>,
|
||||
}
|
||||
|
||||
impl RepositoryConfiguration {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
dimensions: IndexMap::new(),
|
||||
markers: IndexMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_dimension(mut self, name: impl Into<String>, dimension: Dimension) -> Self {
|
||||
self.dimensions.insert(name.into(), dimension);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_marker(mut self, name: impl Into<String>, marker: Marker) -> Self {
|
||||
self.markers.insert(name.into(), marker);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RepositoryConfiguration {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
77
src/models/timecard.rs
Normal file
77
src/models/timecard.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use chrono::NaiveDate;
|
||||
use chrono::NaiveTime;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Type of special day that affects timesheet calculations.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SpecialDayType {
|
||||
#[serde(rename = "VACATION")]
|
||||
Vacation,
|
||||
#[serde(rename = "UNDERTIME")]
|
||||
Undertime,
|
||||
#[serde(rename = "HOLIDAY")]
|
||||
Holiday,
|
||||
#[serde(rename = "WEEKEND")]
|
||||
Weekend,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for SpecialDayType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SpecialDayType::Vacation => write!(f, "VACATION"),
|
||||
SpecialDayType::Undertime => write!(f, "UNDERTIME"),
|
||||
SpecialDayType::Holiday => write!(f, "HOLIDAY"),
|
||||
SpecialDayType::Weekend => write!(f, "WEEKEND"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A Timecard represents a single work period with start and end times.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Timecard {
|
||||
pub from_time: NaiveTime,
|
||||
pub to_time: NaiveTime,
|
||||
}
|
||||
|
||||
impl Timecard {
|
||||
pub fn new(from_time: NaiveTime, to_time: NaiveTime) -> Self {
|
||||
Self { from_time, to_time }
|
||||
}
|
||||
}
|
||||
|
||||
/// A Timesheet aggregates all time tracking information for a single day.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Timesheet {
|
||||
pub date: NaiveDate,
|
||||
#[serde(default)]
|
||||
pub is_sick_leave: bool,
|
||||
#[serde(default)]
|
||||
pub special_day_type: Option<SpecialDayType>,
|
||||
pub timecards: Vec<Timecard>,
|
||||
}
|
||||
|
||||
impl Timesheet {
|
||||
pub fn new(date: NaiveDate) -> Self {
|
||||
Self {
|
||||
date,
|
||||
is_sick_leave: false,
|
||||
special_day_type: None,
|
||||
timecards: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_sick_leave(mut self, is_sick_leave: bool) -> Self {
|
||||
self.is_sick_leave = is_sick_leave;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_special_day_type(mut self, special_day_type: SpecialDayType) -> Self {
|
||||
self.special_day_type = Some(special_day_type);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_timecards(mut self, timecards: Vec<Timecard>) -> Self {
|
||||
self.timecards = timecards;
|
||||
self
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue