diff --git a/src/cli/args.rs b/src/cli/args.rs index 11fda90..f45387f 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -60,6 +60,12 @@ pub enum Commands { debug: bool, }, + /// Open or create the daily entry for a given date + Daily { + /// Date in YYYYMMDD format (defaults to today in configured timezone) + date: Option, + }, + /// Generate shell completions Completions { /// Shell to generate completions for diff --git a/src/cli/commands/daily.rs b/src/cli/commands/daily.rs new file mode 100644 index 0000000..9b108f6 --- /dev/null +++ b/src/cli/commands/daily.rs @@ -0,0 +1,100 @@ +use std::fs; +use std::path::Path; +use std::process::Command; + +use chrono::{Days, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Utc}; +use chrono_tz::Tz; +use walkdir::WalkDir; + +use crate::config::Settings; +use crate::error::StreamdError; +use crate::extract::parse_markdown_file; +use crate::localize::localize_stream_file; +use crate::models::{LocalizedShard, RepositoryConfiguration}; +use crate::timesheet::load_repository_config; + +fn load_all_shards(base_folder: &Path, tz: Tz) -> Result, StreamdError> { + let config = RepositoryConfiguration::new(); + let mut shards = Vec::new(); + + for entry in WalkDir::new(base_folder) + .max_depth(1) + .into_iter() + .filter_map(|e| e.ok()) + { + let path = entry.path(); + if path.extension().map(|e| e == "md").unwrap_or(false) { + let file_name = path.to_string_lossy().to_string(); + let content = fs::read_to_string(path)?; + let stream_file = parse_markdown_file(&file_name, &content); + + if let Ok(shard) = localize_stream_file(&stream_file, &config, tz) { + shards.push(shard); + } + } + } + + Ok(shards) +} + +pub fn run(date: Option) -> Result<(), StreamdError> { + let settings = Settings::load()?; + let base_folder = Path::new(&settings.base_folder); + + let repo_config = load_repository_config(base_folder)?; + let tz: Tz = repo_config + .timezone + .as_deref() + .and_then(|s| s.parse().ok()) + .unwrap_or(chrono_tz::UTC); + + let target_date: NaiveDate = match date { + Some(s) => NaiveDate::parse_from_str(&s, "%Y%m%d").map_err(|_| { + StreamdError::ConfigError("Invalid date format, expected YYYYMMDD".into()) + })?, + None => Utc::now().with_timezone(&tz).date_naive(), + }; + + let day_start = tz + .from_local_datetime(&NaiveDateTime::new(target_date, NaiveTime::MIN)) + .earliest() + .unwrap() + .with_timezone(&Utc); + let day_end = tz + .from_local_datetime(&NaiveDateTime::new( + target_date + Days::new(1), + NaiveTime::MIN, + )) + .earliest() + .unwrap() + .with_timezone(&Utc); + + let all_shards = load_all_shards(base_folder, tz)?; + let mut daily_shards: Vec<_> = all_shards + .into_iter() + .filter(|s| { + s.location + .get("file_type") + .map(|v| v == "daily") + .unwrap_or(false) + }) + .filter(|s| s.moment >= day_start && s.moment < day_end) + .collect(); + daily_shards.sort_by_key(|s| s.moment); + + let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string()); + + if let Some(shard) = daily_shards.first() { + let file_path = shard.location.get("file").unwrap(); + Command::new(&editor).arg(file_path).status()?; + } else { + let now_local = Utc::now().with_timezone(&tz); + let file_name = now_local.format("%Y%m%d-%H%M%S_daily.md").to_string(); + let file_path = base_folder.join(&file_name); + fs::write(&file_path, "# ")?; + Command::new(&editor).arg(&file_path).status()?; + println!("Created {}", file_name); + } + + Ok(()) +} diff --git a/src/cli/commands/mod.rs b/src/cli/commands/mod.rs index d5f79ce..fa6dbba 100644 --- a/src/cli/commands/mod.rs +++ b/src/cli/commands/mod.rs @@ -1,4 +1,5 @@ pub mod completions; +pub mod daily; pub mod edit; pub mod new; pub mod timesheet; diff --git a/src/main.rs b/src/main.rs index 4d311a5..eaa4c91 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ fn main() -> miette::Result<()> { Some(Commands::Timesheet { decimal, debug }) => { streamd::cli::commands::timesheet::run(decimal, debug)? } + Some(Commands::Daily { date }) => streamd::cli::commands::daily::run(date)?, Some(Commands::Completions { shell }) => { streamd::cli::commands::completions::run(shell); }