diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 1698954..fff83e1 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -24,31 +24,22 @@ struct Args { enum Commands { /// List issues. List { - /// Filter strings, describes issues to include in the list. - /// Each filter string is of the form "name=condition". - /// The supported names and their matching conditions are: + /// Filter string, describes issues to include in the list. + /// The filter string is composed of chunks separated by ":". + /// Each chunk is of the form "name=condition". The supported + /// names and their matching conditions are: /// /// "state": Comma-separated list of states to list. - /// Example: "state=new,backlog". Defaults to - /// "new,backlog,blocked,inprogress". /// - /// "assignee": Comma-separated list of assignees to include in - /// the list. The empty string includes issues with no assignee. - /// Example: "assignee=seb," lists issues assigned to "seb" and - /// issues without an assignee. Defaults to include all issues. + /// "assignee": Comma-separated list of assignees to list. + /// Defaults to all assignees if not set. /// - /// "tag": Comma-separated list of tags to include, or exclude - /// if prefixed with "-". Example: "tag=bug,-docs" shows issues - /// that are tagged "bug" and not tagged "docs". Defaults to - /// including all tags and excluding none. + /// "tag": Comma-separated list of tags to include or exclude + /// (if prefixed with "-"). If omitted, defaults to including + /// all tags and excluding none. /// - /// "done-time": Time range of issue completion, in the form - /// "[START]..[END]". Includes issues that were marked Done - /// between START and END. START and END are both in RFC 3339 - /// format, e.g. "YYYY-MM-DDTHH:MM:SS[+-]HH:MM". If START - /// is omitted, defaults to the beginning of time. If END is - /// omitted, defaults to the end of time. - filter: Vec, + #[arg(default_value_t = String::from("state=New,Backlog,Blocked,InProgress"))] + filter: String, }, /// Create a new issue. @@ -93,12 +84,6 @@ enum Commands { #[arg(allow_hyphen_values = true)] tag: Option, }, - - /// Get or set the `done_time` of the Issue. - DoneTime { - issue_id: String, - done_time: Option, - }, } fn handle_command( @@ -108,14 +93,7 @@ fn handle_command( match &args.command { Commands::List { filter } => { let issues = entomologist::database::read_issues_database(issues_database_source)?; - let filter = { - let mut f = entomologist::Filter::new(); - for filter_str in filter { - f.parse(filter_str)?; - } - f - }; - + let filter = entomologist::Filter::new_from_str(filter)?; let mut uuids_by_state = std::collections::HashMap::< entomologist::issue::State, Vec<&entomologist::issue::IssueHandle>, @@ -145,19 +123,6 @@ fn handle_command( } } - if let Some(issue_done_time) = issue.done_time { - if let Some(start_done_time) = filter.start_done_time { - if start_done_time > issue_done_time { - continue; - } - } - if let Some(end_done_time) = filter.end_done_time { - if end_done_time < issue_done_time { - continue; - } - } - } - // This issue passed all the filters, include it in list. uuids_by_state .entry(issue.state.clone()) @@ -181,7 +146,7 @@ fn handle_command( these_uuids.sort_by(|a_id, b_id| { let a = issues.issues.get(*a_id).unwrap(); let b = issues.issues.get(*b_id).unwrap(); - a.creation_time.cmp(&b.creation_time) + a.timestamp.cmp(&b.timestamp) }); println!("{:?}:", state); for uuid in these_uuids { @@ -226,10 +191,8 @@ fn handle_command( } Commands::New { description } => { - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; + let issues_database = + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; match entomologist::issue::Issue::new(&issues_database.dir, description) { Err(entomologist::issue::IssueError::EmptyDescription) => { println!("no new issue created"); @@ -247,10 +210,8 @@ fn handle_command( } Commands::Edit { uuid } => { - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; + let issues_database = + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; if let Some(issue) = issues.get_mut_issue(uuid) { match issue.edit_description() { @@ -289,10 +250,7 @@ fn handle_command( Some(issue) => { println!("issue {}", issue_id); println!("author: {}", issue.author); - println!("creation_time: {}", issue.creation_time); - if let Some(done_time) = &issue.done_time { - println!("done_time: {}", done_time); - } + println!("timestamp: {}", issue.timestamp); println!("state: {:?}", issue.state); if let Some(dependencies) = &issue.dependencies { println!("dependencies: {:?}", dependencies); @@ -306,7 +264,7 @@ fn handle_command( println!(""); println!("comment: {}", comment.uuid); println!("author: {}", comment.author); - println!("creation_time: {}", comment.creation_time); + println!("timestamp: {}", comment.timestamp); println!(""); println!("{}", comment.description); } @@ -322,10 +280,8 @@ fn handle_command( new_state, } => match new_state { Some(new_state) => { - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; + let issues_database = + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; match issues.issues.get_mut(issue_id) { Some(issue) => { @@ -357,10 +313,8 @@ fn handle_command( issue_id, description, } => { - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; + let issues_database = + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; let mut issues = entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; let Some(issue) = issues.get_mut_issue(issue_id) else { return Err(anyhow::anyhow!("issue {} not found", issue_id)); @@ -385,13 +339,9 @@ fn handle_command( } Commands::Sync { remote } => { - if let entomologist::database::IssuesDatabaseSource::Branch(branch) = - issues_database_source - { - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; + if let entomologist::database::IssuesDatabaseSource::Branch(branch) = issues_database_source { + let issues_database = + entomologist::database::make_issues_database(issues_database_source, entomologist::database::IssuesDatabaseAccess::ReadWrite)?; entomologist::git::sync(&issues_database.dir, remote, branch)?; println!("synced {:?} with {:?}", branch, remote); } else { @@ -478,38 +428,6 @@ fn handle_command( } } } - - Commands::DoneTime { - issue_id, - done_time, - } => { - let issues = entomologist::database::read_issues_database(issues_database_source)?; - let Some(issue) = issues.issues.get(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - match done_time { - Some(done_time) => { - // Add or remove tag. - let issues_database = entomologist::database::make_issues_database( - issues_database_source, - entomologist::database::IssuesDatabaseAccess::ReadWrite, - )?; - let mut issues = - entomologist::issues::Issues::new_from_dir(&issues_database.dir)?; - let Some(issue) = issues.get_mut_issue(issue_id) else { - return Err(anyhow::anyhow!("issue {} not found", issue_id)); - }; - let done_time = chrono::DateTime::parse_from_rfc3339(done_time) - .unwrap() - .with_timezone(&chrono::Local); - issue.set_done_time(done_time)?; - } - None => match &issue.done_time { - Some(done_time) => println!("done_time: {}", done_time), - None => println!("None"), - }, - }; - } } Ok(()) @@ -523,9 +441,7 @@ fn main() -> anyhow::Result<()> { // println!("{:?}", args); let issues_database_source = match (&args.issues_dir, &args.issues_branch) { - (Some(dir), None) => { - entomologist::database::IssuesDatabaseSource::Dir(std::path::Path::new(dir)) - } + (Some(dir), None) => entomologist::database::IssuesDatabaseSource::Dir(std::path::Path::new(dir)), (None, Some(branch)) => entomologist::database::IssuesDatabaseSource::Branch(branch), (None, None) => entomologist::database::IssuesDatabaseSource::Branch("entomologist-data"), (Some(_), Some(_)) => { diff --git a/src/comment.rs b/src/comment.rs index 216b34f..c8e26c9 100644 --- a/src/comment.rs +++ b/src/comment.rs @@ -4,7 +4,7 @@ use std::io::{IsTerminal, Write}; pub struct Comment { pub uuid: String, pub author: String, - pub creation_time: chrono::DateTime, + pub timestamp: chrono::DateTime, pub description: String, /// This is the directory that the comment lives in. Only used @@ -53,13 +53,13 @@ impl Comment { } let author = crate::git::git_log_oldest_author(comment_dir)?; - let creation_time = crate::git::git_log_oldest_timestamp(comment_dir)?; + 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()), author, - creation_time, + timestamp, description: description.unwrap(), dir: std::path::PathBuf::from(comment_dir), }) @@ -84,7 +84,7 @@ impl Comment { let mut comment = crate::comment::Comment { uuid, author: String::from(""), // this will be updated from git when we re-read this comment - creation_time: chrono::Local::now(), + timestamp: chrono::Local::now(), description: String::from(""), // this will be set immediately below dir: dir.clone(), }; @@ -204,7 +204,7 @@ mod tests { let expected = Comment { uuid: String::from("9055dac36045fe36545bed7ae7b49347"), author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00") + 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"), diff --git a/src/issue.rs b/src/issue.rs index bc3d959..0de413c 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -22,8 +22,7 @@ pub type IssueHandle = String; pub struct Issue { pub id: String, pub author: String, - pub creation_time: chrono::DateTime, - pub done_time: Option>, + pub timestamp: chrono::DateTime, pub tags: Vec, pub state: State, pub dependencies: Option>, @@ -44,8 +43,6 @@ pub enum IssueError { EnvVarError(#[from] std::env::VarError), #[error(transparent)] CommentError(#[from] crate::comment::CommentError), - #[error(transparent)] - ChronoParseError(#[from] chrono::format::ParseError), #[error("Failed to parse issue")] IssueParseError, #[error("Failed to parse state")] @@ -109,7 +106,6 @@ impl Issue { let mut comments = Vec::::new(); let mut assignee: Option = None; let mut tags = Vec::::new(); - let mut done_time: Option> = None; for direntry in dir.read_dir()? { if let Ok(direntry) = direntry { @@ -123,11 +119,6 @@ impl Issue { assignee = Some(String::from( std::fs::read_to_string(direntry.path())?.trim(), )); - } else if file_name == "done_time" { - let raw_done_time = chrono::DateTime::<_>::parse_from_rfc3339( - std::fs::read_to_string(direntry.path())?.trim(), - )?; - done_time = Some(raw_done_time.into()); } else if file_name == "dependencies" { let dep_strings = std::fs::read_to_string(direntry.path())?; let deps: Vec = dep_strings @@ -168,13 +159,12 @@ impl Issue { }; let author = crate::git::git_log_oldest_author(dir)?; - let creation_time = crate::git::git_log_oldest_timestamp(dir)?; + let timestamp = crate::git::git_log_oldest_timestamp(dir)?; Ok(Self { id, author, - creation_time, - done_time, + timestamp, tags, state: state, dependencies, @@ -195,7 +185,7 @@ impl Issue { comments.push(comment); } } - comments.sort_by(|a, b| a.creation_time.cmp(&b.creation_time)); + comments.sort_by(|a, b| a.timestamp.cmp(&b.timestamp)); Ok(()) } @@ -230,8 +220,7 @@ impl Issue { let mut issue = Self { id: String::from(&issue_id), author: String::from(""), - creation_time: chrono::Local::now(), - done_time: None, + timestamp: chrono::Local::now(), tags: Vec::::new(), state: State::New, dependencies: None, @@ -254,7 +243,8 @@ impl Issue { None => issue.edit_description_file()?, }; - issue.commit(&format!("create new issue {}", issue_id))?; + crate::git::add(&issue_dir)?; + crate::git::commit(&issue_dir, &format!("create new issue {}", issue_id))?; Ok(issue) } @@ -263,15 +253,21 @@ impl Issue { pub fn edit_description(&mut self) -> Result<(), IssueError> { self.edit_description_file()?; let description_filename = self.description_filename(); - self.commit(&format!( - "edit description of issue {}", - description_filename - .parent() - .unwrap() - .file_name() - .unwrap() - .to_string_lossy(), - ))?; + crate::git::add(&description_filename)?; + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &description_filename.parent().unwrap(), + &format!( + "edit description of issue {}", + description_filename + .parent() + .unwrap() + .file_name() + .unwrap() + .to_string_lossy() + ), + )?; + } Ok(()) } @@ -283,22 +279,24 @@ impl Issue { } } - /// Change the State of the Issue. If the new state is `Done`, - /// set the Issue `done_time`. Commits. + /// Change the State of the Issue. pub fn set_state(&mut self, new_state: State) -> Result<(), IssueError> { let old_state = self.state.clone(); let mut state_filename = std::path::PathBuf::from(&self.dir); state_filename.push("state"); let mut state_file = std::fs::File::create(&state_filename)?; write!(state_file, "{}", new_state)?; - self.commit(&format!( - "change state of issue {}, {} -> {}", - self.dir.file_name().unwrap().to_string_lossy(), - old_state, - new_state, - ))?; - if new_state == State::Done { - self.set_done_time(chrono::Local::now())?; + crate::git::add(&state_filename)?; + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &self.dir, + &format!( + "change state of issue {}, {} -> {}", + self.dir.file_name().unwrap().to_string_lossy(), + old_state, + new_state, + ), + )?; } Ok(()) } @@ -311,24 +309,6 @@ impl Issue { Ok(()) } - /// Set the `done_time` of the Issue. Commits. - pub fn set_done_time( - &mut self, - done_time: chrono::DateTime, - ) -> Result<(), IssueError> { - let mut done_time_filename = std::path::PathBuf::from(&self.dir); - done_time_filename.push("done_time"); - let mut done_time_file = std::fs::File::create(&done_time_filename)?; - write!(done_time_file, "{}", done_time.to_rfc3339())?; - self.done_time = Some(done_time.clone()); - self.commit(&format!( - "set done-time of issue {} to {}", - self.dir.file_name().unwrap().to_string_lossy(), - done_time, - ))?; - Ok(()) - } - /// Set the Assignee of an Issue. pub fn set_assignee(&mut self, new_assignee: &str) -> Result<(), IssueError> { let old_assignee = match &self.assignee { @@ -339,12 +319,18 @@ impl Issue { assignee_filename.push("assignee"); let mut assignee_file = std::fs::File::create(&assignee_filename)?; write!(assignee_file, "{}", new_assignee)?; - self.commit(&format!( - "change assignee of issue {}, {} -> {}", - self.dir.file_name().unwrap().to_string_lossy(), - old_assignee, - new_assignee, - ))?; + crate::git::add(&assignee_filename)?; + if crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { + crate::git::commit( + &self.dir, + &format!( + "change assignee of issue {}, {} -> {}", + self.dir.file_name().unwrap().to_string_lossy(), + old_assignee, + new_assignee, + ), + )?; + } Ok(()) } @@ -458,15 +444,7 @@ impl Issue { for tag in &self.tags { writeln!(tags_file, "{}", tag)?; } - self.commit(commit_message)?; - Ok(()) - } - - fn commit(&self, commit_message: &str) -> Result<(), IssueError> { - crate::git::add(&self.dir)?; - if !crate::git::worktree_is_dirty(&self.dir.to_string_lossy())? { - return Ok(()); - } + crate::git::add(&tags_filename)?; crate::git::commit(&self.dir, commit_message)?; Ok(()) } @@ -483,10 +461,9 @@ mod tests { let expected = Issue { id: String::from("3943fc5c173fdf41c0a22251593cd476d96e6c9f"), author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::from([ String::from("tag1"), String::from("TAG2"), @@ -511,10 +488,9 @@ mod tests { let expected = Issue { id: String::from("7792b063eef6d33e7da5dc1856750c149ba678c6"), author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::new(), state: State::InProgress, dependencies: None, diff --git a/src/issues.rs b/src/issues.rs index 709a79d..42e964d 100644 --- a/src/issues.rs +++ b/src/issues.rs @@ -90,10 +90,9 @@ mod tests { expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::new(), state: crate::issue::State::InProgress, dependencies: None, @@ -110,10 +109,9 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T12:14:26-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::from([ String::from("tag1"), String::from("TAG2"), @@ -143,14 +141,9 @@ mod tests { expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: Some( - chrono::DateTime::parse_from_rfc3339("2025-07-15T15:15:15-06:00") - .unwrap() - .with_timezone(&chrono::Local), - ), tags: Vec::::new(), state: crate::issue::State::Done, dependencies: None, @@ -172,7 +165,7 @@ mod tests { crate::comment::Comment { uuid: comment_uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-07T15:26:26-06:00").unwrap().with_timezone(&chrono::Local), + 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), } @@ -181,10 +174,9 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("Sebastian Kuzminsky "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-03T11:59:44-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: None, @@ -210,10 +202,9 @@ mod tests { expected.add_issue(crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::new(), state: crate::issue::State::Done, dependencies: None, @@ -230,10 +221,9 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: None, @@ -251,10 +241,9 @@ mod tests { crate::issue::Issue { id: uuid, author: String::from("sigil-03 "), - creation_time: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") + timestamp: chrono::DateTime::parse_from_rfc3339("2025-07-05T13:55:49-06:00") .unwrap() .with_timezone(&chrono::Local), - done_time: None, tags: Vec::::new(), state: crate::issue::State::WontDo, dependencies: Some(vec![ diff --git a/src/lib.rs b/src/lib.rs index b6245b9..17104ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,10 @@ use std::str::FromStr; pub mod comment; -pub mod database; pub mod git; pub mod issue; pub mod issues; - -use crate::issue::State; +pub mod database; #[derive(Debug, thiserror::Error)] pub enum ParseFilterError { @@ -14,8 +12,6 @@ pub enum ParseFilterError { ParseError, #[error(transparent)] IssueParseError(#[from] crate::issue::IssueError), - #[error(transparent)] - ChronoParseError(#[from] chrono::format::ParseError), } // FIXME: It's easy to imagine a full dsl for filtering issues, for now @@ -27,13 +23,12 @@ pub struct Filter<'a> { pub include_assignees: std::collections::HashSet<&'a str>, pub include_tags: std::collections::HashSet<&'a str>, pub exclude_tags: std::collections::HashSet<&'a str>, - pub start_done_time: Option>, - pub end_done_time: Option>, } impl<'a> Filter<'a> { - pub fn new() -> Filter<'a> { - Self { + pub fn new_from_str(filter_str: &'a str) -> Result, ParseFilterError> { + use crate::issue::State; + let mut f = Filter { include_states: std::collections::HashSet::::from([ State::InProgress, State::Blocked, @@ -43,75 +38,51 @@ impl<'a> Filter<'a> { include_assignees: std::collections::HashSet::<&'a str>::new(), include_tags: std::collections::HashSet::<&'a str>::new(), exclude_tags: std::collections::HashSet::<&'a str>::new(), - start_done_time: None, - end_done_time: None, - } - } + }; - pub fn parse(&mut self, filter_str: &'a str) -> Result<(), ParseFilterError> { - let tokens: Vec<&str> = filter_str.split("=").collect(); - if tokens.len() != 2 { - return Err(ParseFilterError::ParseError); - } - - match tokens[0] { - "state" => { - self.include_states.clear(); - for s in tokens[1].split(",") { - self.include_states - .insert(crate::issue::State::from_str(s)?); - } - } - - "assignee" => { - self.include_assignees.clear(); - for s in tokens[1].split(",") { - self.include_assignees.insert(s); - } - } - - "tag" => { - self.include_tags.clear(); - self.exclude_tags.clear(); - for s in tokens[1].split(",") { - if s.len() == 0 { - return Err(ParseFilterError::ParseError); - } - if s.chars().nth(0).unwrap() == '-' { - self.exclude_tags.insert(&s[1..]); - } else { - self.include_tags.insert(s); - } - } - } - - "done-time" => { - self.start_done_time = None; - self.end_done_time = None; - let times: Vec<&str> = tokens[1].split("..").collect(); - if times.len() > 2 { - return Err(ParseFilterError::ParseError); - } - if times[0].len() != 0 { - self.start_done_time = Some( - chrono::DateTime::parse_from_rfc3339(times[0])? - .with_timezone(&chrono::Local), - ); - } - if times[1].len() != 0 { - self.end_done_time = Some( - chrono::DateTime::parse_from_rfc3339(times[1])? - .with_timezone(&chrono::Local), - ); - } - } - - _ => { - println!("unknown filter string '{}'", filter_str); + for filter_chunk_str in filter_str.split(":") { + let tokens: Vec<&str> = filter_chunk_str.split("=").collect(); + if tokens.len() != 2 { return Err(ParseFilterError::ParseError); } + + match tokens[0] { + "state" => { + f.include_states.clear(); + for s in tokens[1].split(",") { + f.include_states.insert(crate::issue::State::from_str(s)?); + } + } + + "assignee" => { + f.include_assignees.clear(); + for s in tokens[1].split(",") { + f.include_assignees.insert(s); + } + } + + "tag" => { + f.include_tags.clear(); + f.exclude_tags.clear(); + for s in tokens[1].split(",") { + if s.len() == 0 { + return Err(ParseFilterError::ParseError); + } + if s.chars().nth(0).unwrap() == '-' { + f.exclude_tags.insert(&s[1..]); + } else { + f.include_tags.insert(s); + } + } + } + + _ => { + println!("unknown filter chunk '{}'", filter_chunk_str); + return Err(ParseFilterError::ParseError); + } + } } - Ok(()) + Ok(f) } } diff --git a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time b/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time deleted file mode 100644 index d455c4d..0000000 --- a/test/0001/3fa5bfd93317ad25772680071d5ac3259cd2384f/done_time +++ /dev/null @@ -1 +0,0 @@ -2025-07-15T15:15:15-06:00