radicle-tui 0.3.0

Radicle terminal user interface
Documentation
use anyhow::Result;

use radicle::cob::patch::{Patch, PatchId};
use radicle::identity::Did;
use radicle::patch::cache::Patches;
use radicle::patch::Status;
use radicle::storage::git::Repository;
use radicle::Profile;

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Filter {
    status: Option<Status>,
    authored: bool,
    authors: Vec<Did>,
}

impl Default for Filter {
    fn default() -> Self {
        Self {
            status: Some(Status::default()),
            authored: false,
            authors: vec![],
        }
    }
}

impl Filter {
    pub fn with_status(mut self, status: Option<Status>) -> Self {
        self.status = status;
        self
    }

    pub fn with_authored(mut self, authored: bool) -> Self {
        self.authored = authored;
        self
    }

    pub fn with_author(mut self, author: Did) -> Self {
        self.authors.push(author);
        self
    }
}

impl ToString for Filter {
    fn to_string(&self) -> String {
        let mut filter = String::new();

        if let Some(state) = &self.status {
            filter.push_str(&format!("is:{}", state));
            filter.push(' ');
        }
        if self.authored {
            filter.push_str("is:authored");
            filter.push(' ');
        }
        if !self.authors.is_empty() {
            filter.push_str("authors:");
            filter.push('[');

            let mut authors = self.authors.iter().peekable();
            while let Some(author) = authors.next() {
                filter.push_str(&author.encode());

                if authors.peek().is_some() {
                    filter.push(',');
                }
            }
            filter.push(']');
        }

        filter
    }
}

pub fn all(profile: &Profile, repository: &Repository) -> Result<Vec<(PatchId, Patch)>> {
    let cache = profile.patches(repository)?;
    let patches = cache.list()?;

    Ok(patches.flatten().collect())
}

pub fn find(profile: &Profile, repository: &Repository, id: &PatchId) -> Result<Option<Patch>> {
    let cache = profile.patches(repository)?;
    Ok(cache.get(id)?)
}

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

    use anyhow::Result;
    use radicle::patch;

    use super::*;

    #[test]
    fn patch_filter_display_with_status_should_succeed() -> Result<()> {
        let actual = Filter::default().with_status(Some(patch::Status::Open));

        assert_eq!(String::from("is:open "), actual.to_string());

        Ok(())
    }

    #[test]
    fn patch_filter_display_with_status_and_authored_should_succeed() -> Result<()> {
        let actual = Filter::default()
            .with_status(Some(patch::Status::Open))
            .with_authored(true);

        assert_eq!(String::from("is:open is:authored "), actual.to_string());

        Ok(())
    }

    #[test]
    fn patch_filter_display_with_status_and_author_should_succeed() -> Result<()> {
        let actual = Filter::default()
            .with_status(Some(patch::Status::Open))
            .with_author(Did::from_str(
                "did:key:z6MkswQE8gwZw924amKatxnNCXA55BMupMmRg7LvJuim2C1V",
            )?);

        assert_eq!(
            String::from(
                "is:open authors:[did:key:z6MkswQE8gwZw924amKatxnNCXA55BMupMmRg7LvJuim2C1V]"
            ),
            actual.to_string()
        );

        Ok(())
    }
}