diff --git a/src/bin/ent/main.rs b/src/bin/ent/main.rs index 999d949..2a8f1f4 100644 --- a/src/bin/ent/main.rs +++ b/src/bin/ent/main.rs @@ -23,7 +23,11 @@ struct Args { #[derive(clap::Subcommand, Debug)] enum Commands { /// List issues. - List, + List { + /// Filter string, describes issues to include in the list. + #[arg(default_value_t = String::from("state=New,Backlog,Blocked,InProgress"))] + filter: String, + }, /// Create a new issue. New { description: Option }, @@ -64,19 +68,21 @@ enum Commands { fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<()> { match &args.command { - Commands::List => { + Commands::List { filter } => { let issues = entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?; - + let filter = entomologist::parse_filter(filter)?; let mut uuids_by_state = std::collections::HashMap::< entomologist::issue::State, Vec<&entomologist::issue::IssueHandle>, >::new(); for (uuid, issue) in issues.issues.iter() { - uuids_by_state - .entry(issue.state.clone()) - .or_default() - .push(uuid); + if filter.include_states.contains(&issue.state) { + uuids_by_state + .entry(issue.state.clone()) + .or_default() + .push(uuid); + } } use entomologist::issue::State; @@ -89,6 +95,9 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<( State::WontDo, ] { let these_uuids = uuids_by_state.entry(state.clone()).or_default(); + if these_uuids.len() == 0 { + continue; + } these_uuids.sort_by(|a_id, b_id| { let a = issues.issues.get(*a_id).unwrap(); let b = issues.issues.get(*b_id).unwrap(); diff --git a/src/issue.rs b/src/issue.rs index 728e3ee..68cb8f9 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -41,6 +41,8 @@ pub enum IssueError { CommentError(#[from] crate::comment::CommentError), #[error("Failed to parse issue")] IssueParseError, + #[error("Failed to parse state")] + StateParseError, #[error("Failed to run git")] GitError(#[from] crate::git::GitError), #[error("Failed to run editor")] @@ -64,7 +66,7 @@ impl FromStr for State { } else if s == "wontdo" { Ok(State::WontDo) } else { - Err(IssueError::IssueParseError) + Err(IssueError::StateParseError) } } } diff --git a/src/lib.rs b/src/lib.rs index e129eff..77a00d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,40 @@ +use std::str::FromStr; + pub mod comment; pub mod git; pub mod issue; pub mod issues; + +#[derive(Debug, thiserror::Error)] +pub enum ParseFilterError { + #[error("Failed to parse filter")] + ParseError, + #[error(transparent)] + IssueParseError(#[from] crate::issue::IssueError), +} + +// FIXME: It's easy to imagine a full dsl for filtering issues, for now +// i'm starting with obvious easy things. Chumsky looks appealing but +// more research is needed. +#[derive(Debug)] +pub struct Filter { + pub include_states: std::collections::HashSet, +} + +// Parses a filter description matching "state=STATE[,STATE*]" +pub fn parse_filter(filter_str: &str) -> Result { + let tokens: Vec<&str> = filter_str.split("=").collect(); + if tokens.len() != 2 { + return Err(ParseFilterError::ParseError); + } + if tokens[0] != "state" { + return Err(ParseFilterError::ParseError); + } + + let mut include_states = std::collections::HashSet::::new(); + for s in tokens[1].split(",") { + include_states.insert(crate::issue::State::from_str(s)?); + } + + Ok(Filter { include_states }) +}