use std::str::FromStr; pub mod comment; pub mod git; pub mod issue; pub mod issues; pub mod database; #[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<'a> { pub include_states: std::collections::HashSet, pub include_assignees: std::collections::HashSet<&'a str>, pub include_tags: std::collections::HashSet<&'a str>, pub exclude_tags: std::collections::HashSet<&'a str>, } impl<'a> Filter<'a> { 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, State::Backlog, State::New, ]), include_assignees: std::collections::HashSet::<&'a str>::new(), include_tags: std::collections::HashSet::<&'a str>::new(), exclude_tags: std::collections::HashSet::<&'a str>::new(), }; 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(f) } }