mdvault_core/domain/behaviors/
weekly.rs1use std::path::PathBuf;
9use std::sync::Arc;
10
11use chrono::Local;
12
13use crate::types::TypeDefinition;
14use crate::vars::datemath::{is_date_expr, try_evaluate_date_expr};
15
16use super::super::context::{CreationContext, FieldPrompt, PromptContext};
17use super::super::traits::{
18 DomainError, DomainResult, NoteBehavior, NoteIdentity, NoteLifecycle, NotePrompts,
19};
20
21pub struct WeeklyBehavior {
23 typedef: Option<Arc<TypeDefinition>>,
24}
25
26impl WeeklyBehavior {
27 pub fn new(typedef: Option<Arc<TypeDefinition>>) -> Self {
29 Self { typedef }
30 }
31}
32
33impl NoteIdentity for WeeklyBehavior {
34 fn generate_id(&self, _ctx: &CreationContext) -> DomainResult<Option<String>> {
35 Ok(None)
37 }
38
39 fn output_path(&self, ctx: &CreationContext) -> DomainResult<PathBuf> {
40 if let Some(ref td) = self.typedef
42 && let Some(ref output) = td.output
43 {
44 return super::render_output_template(output, ctx);
45 }
46
47 let week = ctx
49 .core_metadata
50 .week
51 .as_ref()
52 .ok_or_else(|| DomainError::PathResolution("week not set".into()))?;
53
54 Ok(ctx.config.vault_root.join(format!("Journal/Weekly/{}.md", week)))
55 }
56
57 fn core_fields(&self) -> Vec<&'static str> {
58 vec!["type", "week"]
59 }
60}
61
62impl NoteLifecycle for WeeklyBehavior {
63 fn before_create(&self, ctx: &mut CreationContext) -> DomainResult<()> {
64 let week = if looks_like_week(&ctx.title) {
66 ctx.title.clone()
67 } else {
68 let expr_to_eval = if is_date_expr(&ctx.title) && !ctx.title.contains('|') {
70 format!("{} | %G-W%V", ctx.title)
71 } else {
72 ctx.title.clone()
73 };
74
75 if let Some(evaluated) = try_evaluate_date_expr(&expr_to_eval) {
76 evaluated
77 } else {
78 Local::now().format("%G-W%V").to_string()
79 }
80 };
81
82 ctx.core_metadata.week = Some(week.clone());
83 ctx.core_metadata.title = Some(week.clone());
84 ctx.set_var("week", &week);
85
86 Ok(())
87 }
88
89 fn after_create(&self, _ctx: &CreationContext, _content: &str) -> DomainResult<()> {
90 Ok(())
92 }
93}
94
95impl NotePrompts for WeeklyBehavior {
96 fn type_prompts(&self, _ctx: &PromptContext) -> Vec<FieldPrompt> {
97 vec![] }
99
100 fn should_prompt_schema(&self) -> bool {
101 false }
103}
104
105impl NoteBehavior for WeeklyBehavior {
106 fn type_name(&self) -> &'static str {
107 "weekly"
108 }
109}
110
111fn looks_like_week(s: &str) -> bool {
113 if s.len() < 7 || s.len() > 8 {
114 return false;
115 }
116 let parts: Vec<&str> = s.split("-W").collect();
117 if parts.len() != 2 {
118 return false;
119 }
120 parts[0].len() == 4
121 && (parts[1].len() == 1 || parts[1].len() == 2)
122 && parts[0].chars().all(|c| c.is_ascii_digit())
123 && parts[1].chars().all(|c| c.is_ascii_digit())
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 fn test_looks_like_week() {
132 assert!(looks_like_week("2025-W01"));
133 assert!(looks_like_week("2025-W52"));
134 assert!(looks_like_week("2024-W1"));
135 assert!(!looks_like_week("2025-01"));
136 assert!(!looks_like_week("not a week"));
137 assert!(!looks_like_week("W01-2025"));
138 }
139}