Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
bin/ui/items: Refactor notification modules
Erik Kundt committed 6 months ago
commit 5b90f92b5fcc3a9d5a14b5e99d5645dd019d53ca
parent 1a768ecea03c9efef5ab7efdf0620b47e1aac2b2
3 files changed +268 -254
modified bin/commands/inbox/list.rs
@@ -34,7 +34,8 @@ use tui::ui::Column;
use tui::{BoxedAny, Channel, Exit, PageStack};

use crate::cob::inbox;
-
use crate::ui::items::notification::{Notification, NotificationFilter};
+
use crate::ui::items::notification::filter::NotificationFilter;
+
use crate::ui::items::notification::Notification;
use crate::ui::items::Filter;

use self::ui::Browser;
modified bin/commands/inbox/list/ui.rs
@@ -25,7 +25,8 @@ use tui::ui::Column;
use tui::{BoxedAny, Selection};

use crate::tui_inbox::common::{InboxOperation, Mode, RepositoryMode, SelectionMode};
-
use crate::ui::items::notification::{Notification, NotificationFilter, NotificationState};
+
use crate::ui::items::notification::filter::NotificationFilter;
+
use crate::ui::items::notification::{Notification, NotificationState};

use super::{Message, State};

modified bin/ui/items/notification.rs
@@ -1,16 +1,6 @@
-
use std::str::FromStr;
-

-
use nom::branch::alt;
-
use nom::bytes::complete::{tag_no_case, take, take_while1};
-
use nom::character::complete::{char, multispace0};
-
use nom::combinator::{map, value};
-
use nom::multi::{many0, separated_list1};
-
use nom::sequence::{delimited, preceded, tuple};
-
use nom::IResult;
-

use radicle::cob::{ObjectId, Timestamp, Title, TypedId};

-
use radicle::identity::{Did, Identity};
+
use radicle::identity::Identity;

use radicle::issue::Issues;
use radicle::node;
@@ -31,6 +21,20 @@ use tui::ui::ToRow;

use super::{AuthorItem, Filter};

+
#[derive(Clone, Debug, Eq, PartialEq)]
+
pub enum NotificationType {
+
    Patch,
+
    Issue,
+
    Branch,
+
    Unknown,
+
}
+

+
#[derive(Clone, Debug, Eq, PartialEq)]
+
pub enum NotificationState {
+
    Seen,
+
    Unseen,
+
}
+

#[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum NotificationKind {
@@ -287,286 +291,294 @@ impl ToRow<9> for Notification {
    }
}

-
#[derive(Clone, Debug, Eq, PartialEq)]
-
pub enum NotificationType {
-
    Patch,
-
    Issue,
-
    Branch,
-
    Unknown,
-
}
+
pub mod filter {
+
    use std::str::FromStr;

-
#[derive(Clone, Debug, Eq, PartialEq)]
-
pub enum NotificationState {
-
    Seen,
-
    Unseen,
-
}
+
    use nom::branch::alt;
+
    use nom::bytes::complete::{tag_no_case, take, take_while1};
+
    use nom::character::complete::{char, multispace0};
+
    use nom::combinator::{map, value};
+
    use nom::multi::{many0, separated_list1};
+
    use nom::sequence::{delimited, preceded, tuple};
+
    use nom::IResult;

-
#[derive(Debug, Clone, PartialEq)]
-
pub enum NotificationFilter {
-
    State(NotificationState),
-
    Type(NotificationTypeFilter),
-
    Author(NotificationAuthorFilter),
-
    Search(String),
-
    And(Vec<NotificationFilter>),
-
}
+
    use radicle::prelude::Did;
+

+
    use super::{Filter, Notification, NotificationKind, NotificationState, NotificationType};

-
impl Default for NotificationFilter {
-
    fn default() -> Self {
-
        Self::Type(NotificationTypeFilter::Or(vec![
-
            NotificationType::Issue,
-
            NotificationType::Patch,
-
        ]))
+
    #[derive(Debug, Clone, PartialEq)]
+
    pub enum NotificationFilter {
+
        State(NotificationState),
+
        Type(NotificationTypeFilter),
+
        Author(NotificationAuthorFilter),
+
        Search(String),
+
        And(Vec<NotificationFilter>),
    }
-
}

-
#[derive(Debug, Clone, PartialEq)]
-
pub enum NotificationTypeFilter {
-
    Single(NotificationType),
-
    Or(Vec<NotificationType>),
-
}
+
    impl Default for NotificationFilter {
+
        fn default() -> Self {
+
            Self::Type(NotificationTypeFilter::Or(vec![
+
                NotificationType::Issue,
+
                NotificationType::Patch,
+
            ]))
+
        }
+
    }

-
#[derive(Debug, Clone, PartialEq)]
-
pub enum NotificationAuthorFilter {
-
    Single(Did),
-
    Or(Vec<Did>),
-
}
+
    #[derive(Debug, Clone, PartialEq)]
+
    pub enum NotificationTypeFilter {
+
        Single(NotificationType),
+
        Or(Vec<NotificationType>),
+
    }

-
impl Filter<Notification> for NotificationFilter {
-
    fn matches(&self, notif: &Notification) -> bool {
-
        use fuzzy_matcher::skim::SkimMatcherV2;
-
        use fuzzy_matcher::FuzzyMatcher;
-

-
        let matcher = SkimMatcherV2::default();
-

-
        let match_type = |type_name: &NotificationType| match type_name {
-
            NotificationType::Issue => matches!(&notif.kind, NotificationKind::Cob {
-
                        type_name,
-
                        summary: _,
-
                        status: _,
-
                        id: _,
-
                    } if type_name == "issue"),
-
            NotificationType::Patch => matches!(&notif.kind, NotificationKind::Cob {
-
                        type_name,
-
                        summary: _,
-
                        status: _,
-
                        id: _,
-
                    } if type_name == "patch"),
-
            NotificationType::Branch => {
-
                matches!(notif.kind, NotificationKind::Branch { .. })
-
            }
-
            NotificationType::Unknown => {
-
                matches!(notif.kind, NotificationKind::Unknown { .. })
-
            }
-
        };
+
    #[derive(Debug, Clone, PartialEq)]
+
    pub enum NotificationAuthorFilter {
+
        Single(Did),
+
        Or(Vec<Did>),
+
    }

-
        match self {
-
            NotificationFilter::State(state) => match state {
-
                NotificationState::Seen => notif.seen,
-
                NotificationState::Unseen => !notif.seen,
-
            },
-
            NotificationFilter::Type(type_filter) => match type_filter {
-
                NotificationTypeFilter::Single(type_name) => match_type(type_name),
-
                NotificationTypeFilter::Or(types) => types.iter().any(|other| match_type(other)),
-
            },
-
            NotificationFilter::Author(author_filter) => match author_filter {
-
                NotificationAuthorFilter::Single(author) => notif.author.nid == Some(**author),
-
                NotificationAuthorFilter::Or(authors) => authors
-
                    .iter()
-
                    .any(|other| notif.author.nid == Some(**other)),
-
            },
-
            NotificationFilter::Search(search) => {
-
                let summary = match &notif.kind {
-
                    NotificationKind::Cob {
-
                        type_name: _,
-
                        summary,
-
                        status: _,
-
                        id: _,
-
                    } => summary,
-
                    NotificationKind::Branch {
-
                        name: _,
-
                        summary,
-
                        status: _,
-
                        id: _,
-
                    } => summary,
-
                    NotificationKind::Unknown { refname: _ } => "",
-
                };
-
                match matcher.fuzzy_match(summary, search) {
-
                    Some(score) => score == 0 || score > 60,
-
                    _ => false,
+
    impl Filter<Notification> for NotificationFilter {
+
        fn matches(&self, notif: &Notification) -> bool {
+
            use fuzzy_matcher::skim::SkimMatcherV2;
+
            use fuzzy_matcher::FuzzyMatcher;
+

+
            let matcher = SkimMatcherV2::default();
+

+
            let match_type = |type_name: &NotificationType| match type_name {
+
                NotificationType::Issue => matches!(&notif.kind, NotificationKind::Cob {
+
                            type_name,
+
                            summary: _,
+
                            status: _,
+
                            id: _,
+
                        } if type_name == "issue"),
+
                NotificationType::Patch => matches!(&notif.kind, NotificationKind::Cob {
+
                            type_name,
+
                            summary: _,
+
                            status: _,
+
                            id: _,
+
                        } if type_name == "patch"),
+
                NotificationType::Branch => {
+
                    matches!(notif.kind, NotificationKind::Branch { .. })
+
                }
+
                NotificationType::Unknown => {
+
                    matches!(notif.kind, NotificationKind::Unknown { .. })
+
                }
+
            };
+

+
            match self {
+
                NotificationFilter::State(state) => match state {
+
                    NotificationState::Seen => notif.seen,
+
                    NotificationState::Unseen => !notif.seen,
+
                },
+
                NotificationFilter::Type(type_filter) => match type_filter {
+
                    NotificationTypeFilter::Single(type_name) => match_type(type_name),
+
                    NotificationTypeFilter::Or(types) => {
+
                        types.iter().any(|other| match_type(other))
+
                    }
+
                },
+
                NotificationFilter::Author(author_filter) => match author_filter {
+
                    NotificationAuthorFilter::Single(author) => notif.author.nid == Some(**author),
+
                    NotificationAuthorFilter::Or(authors) => authors
+
                        .iter()
+
                        .any(|other| notif.author.nid == Some(**other)),
+
                },
+
                NotificationFilter::Search(search) => {
+
                    let summary = match &notif.kind {
+
                        NotificationKind::Cob {
+
                            type_name: _,
+
                            summary,
+
                            status: _,
+
                            id: _,
+
                        } => summary,
+
                        NotificationKind::Branch {
+
                            name: _,
+
                            summary,
+
                            status: _,
+
                            id: _,
+
                        } => summary,
+
                        NotificationKind::Unknown { refname: _ } => "",
+
                    };
+
                    match matcher.fuzzy_match(summary, search) {
+
                        Some(score) => score == 0 || score > 60,
+
                        _ => false,
+
                    }
                }
+
                NotificationFilter::And(filters) => filters.iter().all(|f| f.matches(notif)),
            }
-
            NotificationFilter::And(filters) => filters.iter().all(|f| f.matches(notif)),
        }
    }
-
}

-
impl FromStr for NotificationFilter {
-
    type Err = anyhow::Error;
-

-
    fn from_str(filter_exp: &str) -> Result<Self, Self::Err> {
-
        fn parse_did(input: &str) -> IResult<&str, Did> {
-
            match Did::from_str(input) {
-
                Ok(did) => IResult::Ok(("", did)),
-
                Err(_) => IResult::Err(nom::Err::Error(nom::error::Error::new(
-
                    input,
-
                    nom::error::ErrorKind::Verify,
-
                ))),
+
    impl FromStr for NotificationFilter {
+
        type Err = anyhow::Error;
+

+
        fn from_str(filter_exp: &str) -> Result<Self, Self::Err> {
+
            fn parse_did(input: &str) -> IResult<&str, Did> {
+
                match Did::from_str(input) {
+
                    Ok(did) => IResult::Ok(("", did)),
+
                    Err(_) => IResult::Err(nom::Err::Error(nom::error::Error::new(
+
                        input,
+
                        nom::error::ErrorKind::Verify,
+
                    ))),
+
                }
            }
-
        }

-
        fn parse_state(input: &str) -> IResult<&str, NotificationState> {
-
            alt((
-
                value(NotificationState::Seen, tag_no_case("seen")),
-
                value(NotificationState::Unseen, tag_no_case("unseen")),
-
            ))(input)
-
        }
+
            fn parse_state(input: &str) -> IResult<&str, NotificationState> {
+
                alt((
+
                    value(NotificationState::Seen, tag_no_case("seen")),
+
                    value(NotificationState::Unseen, tag_no_case("unseen")),
+
                ))(input)
+
            }

-
        fn parse_state_filter(input: &str) -> IResult<&str, NotificationFilter> {
-
            map(
-
                preceded(
-
                    tuple((
-
                        tag_no_case("state"),
-
                        multispace0,
-
                        tag_no_case("="),
-
                        multispace0,
-
                    )),
-
                    parse_state,
-
                ),
-
                NotificationFilter::State,
-
            )(input)
-
        }
+
            fn parse_state_filter(input: &str) -> IResult<&str, NotificationFilter> {
+
                map(
+
                    preceded(
+
                        tuple((
+
                            tag_no_case("state"),
+
                            multispace0,
+
                            tag_no_case("="),
+
                            multispace0,
+
                        )),
+
                        parse_state,
+
                    ),
+
                    NotificationFilter::State,
+
                )(input)
+
            }

-
        fn parse_type(input: &str) -> IResult<&str, NotificationType> {
-
            alt((
-
                value(NotificationType::Patch, tag_no_case("patch")),
-
                value(NotificationType::Issue, tag_no_case("issue")),
-
                value(NotificationType::Branch, tag_no_case("branch")),
-
                value(NotificationType::Unknown, tag_no_case("unknown")),
-
            ))(input)
-
        }
+
            fn parse_type(input: &str) -> IResult<&str, NotificationType> {
+
                alt((
+
                    value(NotificationType::Patch, tag_no_case("patch")),
+
                    value(NotificationType::Issue, tag_no_case("issue")),
+
                    value(NotificationType::Branch, tag_no_case("branch")),
+
                    value(NotificationType::Unknown, tag_no_case("unknown")),
+
                ))(input)
+
            }

-
        fn parse_type_single(input: &str) -> IResult<&str, NotificationTypeFilter> {
-
            map(parse_type, NotificationTypeFilter::Single)(input)
-
        }
+
            fn parse_type_single(input: &str) -> IResult<&str, NotificationTypeFilter> {
+
                map(parse_type, NotificationTypeFilter::Single)(input)
+
            }

-
        fn parse_type_or(input: &str) -> IResult<&str, NotificationTypeFilter> {
-
            map(
-
                delimited(
-
                    tuple((multispace0, char('('), multispace0)),
-
                    separated_list1(
-
                        delimited(multispace0, tag_no_case("or"), multispace0),
-
                        parse_type,
+
            fn parse_type_or(input: &str) -> IResult<&str, NotificationTypeFilter> {
+
                map(
+
                    delimited(
+
                        tuple((multispace0, char('('), multispace0)),
+
                        separated_list1(
+
                            delimited(multispace0, tag_no_case("or"), multispace0),
+
                            parse_type,
+
                        ),
+
                        tuple((multispace0, char(')'), multispace0)),
                    ),
-
                    tuple((multispace0, char(')'), multispace0)),
-
                ),
-
                NotificationTypeFilter::Or,
-
            )(input)
-
        }
-

-
        fn parse_type_filter(input: &str) -> IResult<&str, NotificationFilter> {
-
            map(
-
                preceded(
-
                    tuple((
-
                        tag_no_case("type"),
-
                        multispace0,
-
                        tag_no_case("="),
-
                        multispace0,
-
                    )),
-
                    alt((parse_type_or, parse_type_single)),
-
                ),
-
                NotificationFilter::Type,
-
            )(input)
-
        }
-

-
        fn parse_author_single(input: &str) -> IResult<&str, NotificationAuthorFilter> {
-
            map(parse_did, NotificationAuthorFilter::Single)(input)
-
        }
+
                    NotificationTypeFilter::Or,
+
                )(input)
+
            }

-
        fn parse_author_or(input: &str) -> IResult<&str, NotificationAuthorFilter> {
-
            map(
-
                delimited(
-
                    tuple((multispace0, char('('), multispace0)),
-
                    separated_list1(
-
                        delimited(multispace0, tag_no_case("or"), multispace0),
-
                        take(56_usize),
+
            fn parse_type_filter(input: &str) -> IResult<&str, NotificationFilter> {
+
                map(
+
                    preceded(
+
                        tuple((
+
                            tag_no_case("type"),
+
                            multispace0,
+
                            tag_no_case("="),
+
                            multispace0,
+
                        )),
+
                        alt((parse_type_or, parse_type_single)),
                    ),
-
                    tuple((multispace0, char(')'), multispace0)),
-
                ),
-
                |dids: Vec<&str>| {
-
                    NotificationAuthorFilter::Or(
-
                        dids.iter()
-
                            .filter_map(|did| Did::from_str(did).ok())
-
                            .collect::<Vec<_>>(),
-
                    )
-
                },
-
            )(input)
-
        }
+
                    NotificationFilter::Type,
+
                )(input)
+
            }

-
        fn parse_author_filter(input: &str) -> IResult<&str, NotificationFilter> {
-
            map(
-
                preceded(
-
                    tuple((
-
                        tag_no_case("author"),
-
                        multispace0,
-
                        tag_no_case("="),
-
                        multispace0,
-
                    )),
-
                    alt((parse_author_single, parse_author_or)),
-
                ),
-
                NotificationFilter::Author,
-
            )(input)
-
        }
+
            fn parse_author_single(input: &str) -> IResult<&str, NotificationAuthorFilter> {
+
                map(parse_did, NotificationAuthorFilter::Single)(input)
+
            }

-
        fn parse_search_filter(input: &str) -> IResult<&str, NotificationFilter> {
-
            map(
-
                take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '-'),
-
                |s: &str| NotificationFilter::Search(s.to_string()),
-
            )(input)
-
        }
+
            fn parse_author_or(input: &str) -> IResult<&str, NotificationAuthorFilter> {
+
                map(
+
                    delimited(
+
                        tuple((multispace0, char('('), multispace0)),
+
                        separated_list1(
+
                            delimited(multispace0, tag_no_case("or"), multispace0),
+
                            take(56_usize),
+
                        ),
+
                        tuple((multispace0, char(')'), multispace0)),
+
                    ),
+
                    |dids: Vec<&str>| {
+
                        NotificationAuthorFilter::Or(
+
                            dids.iter()
+
                                .filter_map(|did| Did::from_str(did).ok())
+
                                .collect::<Vec<_>>(),
+
                        )
+
                    },
+
                )(input)
+
            }

-
        fn parse_single_filter(input: &str) -> IResult<&str, NotificationFilter> {
-
            alt((
-
                parse_state_filter,
-
                parse_type_filter,
-
                parse_author_filter,
-
                parse_search_filter,
-
            ))(input)
-
        }
+
            fn parse_author_filter(input: &str) -> IResult<&str, NotificationFilter> {
+
                map(
+
                    preceded(
+
                        tuple((
+
                            tag_no_case("author"),
+
                            multispace0,
+
                            tag_no_case("="),
+
                            multispace0,
+
                        )),
+
                        alt((parse_author_single, parse_author_or)),
+
                    ),
+
                    NotificationFilter::Author,
+
                )(input)
+
            }

-
        fn parse_filters(input: &str) -> IResult<&str, Vec<NotificationFilter>> {
-
            many0(preceded(multispace0, parse_single_filter))(input)
-
        }
+
            fn parse_search_filter(input: &str) -> IResult<&str, NotificationFilter> {
+
                map(
+
                    take_while1(|c: char| c.is_alphanumeric() || c == '_' || c == '-'),
+
                    |s: &str| NotificationFilter::Search(s.to_string()),
+
                )(input)
+
            }

-
        let parse_filter_expression = |input: &str| -> Result<NotificationFilter, String> {
-
            match parse_filters(input) {
-
                Ok((remaining, filters)) => {
-
                    let remaining = remaining.trim();
-
                    if !remaining.is_empty() {
-
                        return Err(format!("Unparsed input remaining: '{}'", remaining));
-
                    }
+
            fn parse_single_filter(input: &str) -> IResult<&str, NotificationFilter> {
+
                alt((
+
                    parse_state_filter,
+
                    parse_type_filter,
+
                    parse_author_filter,
+
                    parse_search_filter,
+
                ))(input)
+
            }

-
                    if filters.is_empty() {
-
                        return Err("No filters provided".to_string());
-
                    }
+
            fn parse_filters(input: &str) -> IResult<&str, Vec<NotificationFilter>> {
+
                many0(preceded(multispace0, parse_single_filter))(input)
+
            }

-
                    if filters.len() == 1 {
-
                        Ok(filters.into_iter().next().unwrap())
-
                    } else {
-
                        Ok(NotificationFilter::And(filters))
+
            let parse_filter_expression = |input: &str| -> Result<NotificationFilter, String> {
+
                match parse_filters(input) {
+
                    Ok((remaining, filters)) => {
+
                        let remaining = remaining.trim();
+
                        if !remaining.is_empty() {
+
                            return Err(format!("Unparsed input remaining: '{}'", remaining));
+
                        }
+

+
                        if filters.is_empty() {
+
                            return Err("No filters provided".to_string());
+
                        }
+

+
                        if filters.len() == 1 {
+
                            Ok(filters.into_iter().next().unwrap())
+
                        } else {
+
                            Ok(NotificationFilter::And(filters))
+
                        }
                    }
+
                    Err(e) => Err(format!("Parse error: {}", e)),
                }
-
                Err(e) => Err(format!("Parse error: {}", e)),
-
            }
-
        };
+
            };

-
        parse_filter_expression(filter_exp).map_err(|err| anyhow::format_err!(err))
+
            parse_filter_expression(filter_exp).map_err(|err| anyhow::format_err!(err))
+
        }
    }
}

#[cfg(test)]
mod tests {
+
    use std::str::FromStr;
+

    use anyhow::Result;
+
    use radicle::prelude::Did;

+
    use super::filter::*;
    use super::*;

    #[test]