diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index a597fd9..af01182 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. @@ -94,7 +85,7 @@ enum Commands { tag: Option, }, - /// Get or set the `done_time` of the Issue. + // Set the `done_time` of the Issue. DoneTime { issue_id: String, done_time: Option, @@ -108,14 +99,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>, diff --git a/src/lib.rs b/src/lib.rs index b6245b9..3ec1ba8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,8 +6,6 @@ pub mod git; pub mod issue; pub mod issues; -use crate::issue::State; - #[derive(Debug, thiserror::Error)] pub enum ParseFilterError { #[error("Failed to parse filter")] @@ -32,8 +30,9 @@ pub struct Filter<'a> { } 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, @@ -45,73 +44,70 @@ impl<'a> Filter<'a> { 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); + } + } + } + + "done-time" => { + let times: Vec<&str> = tokens[1].split("..").collect(); + if times.len() > 2 { + return Err(ParseFilterError::ParseError); + } + if times[0].len() != 0 { + f.start_done_time = Some( + chrono::DateTime::parse_from_rfc3339(times[0])? + .with_timezone(&chrono::Local), + ); + } + if times[1].len() != 0 { + f.end_done_time = Some( + chrono::DateTime::parse_from_rfc3339(times[1])? + .with_timezone(&chrono::Local), + ); + } + } + + _ => { + println!("unknown filter chunk '{}'", filter_chunk_str); + return Err(ParseFilterError::ParseError); + } + } } - Ok(()) + Ok(f) } }