add ent list filter by assignee
I'm not sure about the filter format... There are two independent filters: "state" and "assignee". The "state" filter defaults to including issues whose state is InProgress, Blocked, Backlog, or New. The "assignee" filter defaults to including all issues, assigned or not. The two filters can be independently overridden by the `ent list FILTER` command. FILTER is a string containing chunks separated by ":", like the PATH environment variable. Each chunk is of the form "name=value[,value...]". "name" can be either "state" or "assignee". The "value" arguments to the "state" filter must be one of the valid states, or it's a parse error. The "value" arguments to the "assignee" filter are used to string-compare against the issues "assignee" field, exact matches are accepted and everything else is rejected. A special assignee filter of the empty string matches issues that don't have an assignee. Some examples: * `ent list` shows issues in the states listed above, and don't filter based on assignee at all. * `ent list assignee=seb` shows issues in the states listed above, but only if the assignee is "seb". * `ent list assignee=seb,` shows issues in the states listed above, but only if the assignee is "seb" or if there is no assignee. * `ent list state=done` shows all issues in the Done state. * `ent list state=done:assignee=seb` shows issues in the Done state that are assigned to "seb".
This commit is contained in:
parent
86a22f88f3
commit
df7b5c6aa4
2 changed files with 59 additions and 21 deletions
|
|
@ -71,18 +71,30 @@ fn handle_command(args: &Args, issues_dir: &std::path::Path) -> anyhow::Result<(
|
||||||
Commands::List { filter } => {
|
Commands::List { filter } => {
|
||||||
let issues =
|
let issues =
|
||||||
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?;
|
entomologist::issues::Issues::new_from_dir(std::path::Path::new(issues_dir))?;
|
||||||
let filter = entomologist::parse_filter(filter)?;
|
let filter = entomologist::Filter::new_from_str(filter)?;
|
||||||
let mut uuids_by_state = std::collections::HashMap::<
|
let mut uuids_by_state = std::collections::HashMap::<
|
||||||
entomologist::issue::State,
|
entomologist::issue::State,
|
||||||
Vec<&entomologist::issue::IssueHandle>,
|
Vec<&entomologist::issue::IssueHandle>,
|
||||||
>::new();
|
>::new();
|
||||||
for (uuid, issue) in issues.issues.iter() {
|
for (uuid, issue) in issues.issues.iter() {
|
||||||
if filter.include_states.contains(&issue.state) {
|
if !filter.include_states.contains(&issue.state) {
|
||||||
uuids_by_state
|
continue;
|
||||||
.entry(issue.state.clone())
|
|
||||||
.or_default()
|
|
||||||
.push(uuid);
|
|
||||||
}
|
}
|
||||||
|
if filter.include_assignees.len() > 0 {
|
||||||
|
let assignee = match &issue.assignee {
|
||||||
|
Some(assignee) => assignee,
|
||||||
|
None => "",
|
||||||
|
};
|
||||||
|
if !filter.include_assignees.contains(assignee) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This issue passed all the filters, include it in list.
|
||||||
|
uuids_by_state
|
||||||
|
.entry(issue.state.clone())
|
||||||
|
.or_default()
|
||||||
|
.push(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
use entomologist::issue::State;
|
use entomologist::issue::State;
|
||||||
|
|
|
||||||
56
src/lib.rs
56
src/lib.rs
|
|
@ -17,24 +17,50 @@ pub enum ParseFilterError {
|
||||||
// i'm starting with obvious easy things. Chumsky looks appealing but
|
// i'm starting with obvious easy things. Chumsky looks appealing but
|
||||||
// more research is needed.
|
// more research is needed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Filter {
|
pub struct Filter<'a> {
|
||||||
pub include_states: std::collections::HashSet<crate::issue::State>,
|
pub include_states: std::collections::HashSet<crate::issue::State>,
|
||||||
|
pub include_assignees: std::collections::HashSet<&'a str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses a filter description matching "state=STATE[,STATE*]"
|
impl<'a> Filter<'a> {
|
||||||
pub fn parse_filter(filter_str: &str) -> Result<Filter, ParseFilterError> {
|
pub fn new_from_str(filter_str: &'a str) -> Result<Filter<'a>, ParseFilterError> {
|
||||||
let tokens: Vec<&str> = filter_str.split("=").collect();
|
use crate::issue::State;
|
||||||
if tokens.len() != 2 {
|
let mut f = Filter {
|
||||||
return Err(ParseFilterError::ParseError);
|
include_states: std::collections::HashSet::<crate::issue::State>::from([
|
||||||
}
|
State::InProgress,
|
||||||
if tokens[0] != "state" {
|
State::Blocked,
|
||||||
return Err(ParseFilterError::ParseError);
|
State::Backlog,
|
||||||
}
|
State::New,
|
||||||
|
]),
|
||||||
|
include_assignees: std::collections::HashSet::<&'a str>::new(),
|
||||||
|
};
|
||||||
|
|
||||||
let mut include_states = std::collections::HashSet::<crate::issue::State>::new();
|
for filter_chunk_str in filter_str.split(":") {
|
||||||
for s in tokens[1].split(",") {
|
let tokens: Vec<&str> = filter_chunk_str.split("=").collect();
|
||||||
include_states.insert(crate::issue::State::from_str(s)?);
|
if tokens.len() != 2 {
|
||||||
}
|
return Err(ParseFilterError::ParseError);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Filter { include_states })
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("unknown filter chunk '{}'", filter_chunk_str);
|
||||||
|
return Err(ParseFilterError::ParseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue