diff --git a/Cargo.toml b/Cargo.toml index fccb9c9..af271d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ log = ["dep:log", "dep:simple_logger"] [dependencies] anyhow = "1.0.95" +chrono = "0.4.41" clap = { version = "4.5.26", features = ["derive"] } log = { version = "0.4.27", optional = true } rand = "0.9.1" diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 10d0637..cae0806 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -105,9 +105,10 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( } println!(""); println!("{}", issue.description); - for (uuid, comment) in issue.comments.iter() { + for comment in &issue.comments { println!(""); - println!("comment: {}", uuid); + println!("comment: {}", comment.uuid); + println!("timestamp: {}", comment.timestamp); println!("{}", comment.description); } } diff --git a/src/comment.rs b/src/comment.rs index ab2ced9..8cd00a0 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -2,6 +2,8 @@ use std::io::Write; #[derive(Debug, PartialEq)] pub struct Comment { + pub uuid: String, + pub timestamp: chrono::DateTime, pub description: String, /// This is the directory that the comment lives in. Only used @@ -39,12 +41,16 @@ impl Comment { } } } - if description == None { return Err(CommentError::CommentParseError); } + let timestamp = crate::git::git_log_oldest_timestamp(comment_dir)?; + let dir = std::path::PathBuf::from(comment_dir); + Ok(Self { + uuid: String::from(dir.file_name().unwrap().to_string_lossy()), + timestamp, description: description.unwrap(), dir: std::path::PathBuf::from(comment_dir), }) @@ -95,8 +101,11 @@ mod tests { std::path::Path::new("test/0001/dd79c8cfb8beeacd0460429944b4ecbe95a31561/comments/9055dac36045fe36545bed7ae7b49347"); let comment = Comment::new_from_dir(comment_dir).unwrap(); let expected = Comment { + uuid: String::from("9055dac36045fe36545bed7ae7b49347"), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00") + .unwrap() + .with_timezone(&chrono::Local), description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), - dir: std::path::PathBuf::from(comment_dir), }; assert_eq!(comment, expected); diff --git a/src/git.rs b/src/git.rs index 30f703e..d26293a 100644 --- a/src/git.rs +++ b/src/git.rs @@ -4,6 +4,8 @@ use std::io::Write; pub enum GitError { #[error(transparent)] StdIoError(#[from] std::io::Error), + #[error(transparent)] + ParseIntError(#[from] std::num::ParseIntError), #[error("Oops, something went wrong")] Oops, } @@ -180,6 +182,32 @@ pub fn sync(dir: &std::path::Path, remote: &str, branch: &str) -> Result<(), Git Ok(()) } +pub fn git_log_oldest_timestamp( + path: &std::path::Path, +) -> Result, GitError> { + let mut git_dir = std::path::PathBuf::from(path); + git_dir.pop(); + let result = std::process::Command::new("git") + .args([ + "log", + "--pretty=format:%at", + &path.file_name().unwrap().to_string_lossy(), + ]) + .current_dir(&git_dir) + .output()?; + if !result.status.success() { + println!("stdout: {}", std::str::from_utf8(&result.stdout).unwrap()); + println!("stderr: {}", std::str::from_utf8(&result.stderr).unwrap()); + return Err(GitError::Oops); + } + let timestamp_str = std::str::from_utf8(&result.stdout).unwrap(); + let timestamp_i64 = timestamp_str.parse::()?; + let timestamp = chrono::DateTime::from_timestamp(timestamp_i64, 0) + .unwrap() + .with_timezone(&chrono::Local); + Ok(timestamp) +} + pub fn create_orphan_branch(branch: &str) -> Result<(), GitError> { { let tmp_worktree = tempfile::tempdir().unwrap(); diff --git a/src/issue.rs b/src/issue.rs index e099616..1e84294 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -23,7 +23,7 @@ pub struct Issue { pub description: String, pub state: State, pub dependencies: Option>, - pub comments: std::collections::HashMap, + pub comments: Vec, /// This is the directory that the issue lives in. Only used /// internally by the entomologist library. @@ -85,7 +85,7 @@ impl Issue { let mut description: Option = None; let mut state = State::New; // default state, if not specified in the issue let mut dependencies: Option> = None; - let mut comments = std::collections::HashMap::::new(); + let mut comments = Vec::::new(); for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -127,16 +127,16 @@ impl Issue { } fn read_comments( - comments: &mut std::collections::HashMap, + comments: &mut Vec, dir: &std::path::Path, ) -> Result<(), IssueError> { for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { - let uuid = direntry.file_name(); let comment = crate::comment::Comment::new_from_dir(&direntry.path())?; - comments.insert(String::from(uuid.to_string_lossy()), comment); + comments.push(comment); } } + comments.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); Ok(()) } @@ -148,10 +148,13 @@ impl Issue { } let rnd: u128 = rand::random(); - dir.push(&format!("{:032x}", rnd)); + let uuid = format!("{:032x}", rnd); + dir.push(&uuid); std::fs::create_dir(&dir)?; Ok(crate::comment::Comment { + uuid, + timestamp: chrono::Local::now(), description: String::from(""), // FIXME dir, }) @@ -166,7 +169,7 @@ impl Issue { description: String::from(""), // FIXME: kind of bogus to use the empty string as None state: State::New, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir: issue_dir, }) } @@ -242,7 +245,7 @@ mod tests { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: State::New, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); @@ -256,7 +259,7 @@ mod tests { description: String::from("minimal"), state: State::InProgress, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir: std::path::PathBuf::from(issue_dir), }; assert_eq!(issue, expected); diff --git a/src/issues.rs b/src/issues.rs index 28df2e9..06af867 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -99,7 +99,7 @@ mod tests { description: String::from("minimal"), state: crate::issue::State::InProgress, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -113,7 +113,7 @@ mod tests { description: String::from("this is the title of my issue\n\nThis is the description of my issue.\nIt is multiple lines.\n* Arbitrary contents\n* But let's use markdown by convention\n"), state: crate::issue::State::New, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, } ); @@ -136,7 +136,7 @@ mod tests { description: String::from("oh yeah we got titles"), state: crate::issue::State::Done, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -148,12 +148,12 @@ mod tests { let comment_uuid = String::from("9055dac36045fe36545bed7ae7b49347"); comment_dir.push("comments"); comment_dir.push(&comment_uuid); - let mut expected_comments = - std::collections::HashMap::::new(); - expected_comments.insert( - String::from(&comment_uuid), + let mut expected_comments = Vec::::new(); + expected_comments.push( crate::comment::Comment { + uuid: comment_uuid, description: String::from("This is a comment on issue dd79c8cfb8beeacd0460429944b4ecbe95a31561\n\nIt has multiple lines\n"), + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00").unwrap().with_timezone(&chrono::Local), dir: std::path::PathBuf::from(comment_dir), } ); @@ -186,7 +186,7 @@ mod tests { description: String::from("oh yeah we got titles\n"), state: crate::issue::State::Done, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -200,7 +200,7 @@ mod tests { description: String::from("issues out the wazoo\n\nLots of words\nthat don't say much\nbecause this is just\na test\n"), state: crate::issue::State::WontDo, dependencies: None, - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, ); @@ -217,7 +217,7 @@ mod tests { crate::issue::IssueHandle::from("3fa5bfd93317ad25772680071d5ac3259cd2384f"), crate::issue::IssueHandle::from("dd79c8cfb8beeacd0460429944b4ecbe95a31561"), ]), - comments: std::collections::HashMap::::new(), + comments: Vec::::new(), dir, }, );