radicle-cli 0.20.0

Radicle CLI
Documentation
use radicle::{
    cob::{
        self,
        cache::{MigrateCallback, MigrateProgress},
    },
    prelude::NodeId,
    profile,
    storage::ReadRepository,
    Profile,
};
use radicle_term as term;

use crate::terminal;

/// Hint to migrate COB database.
pub const MIGRATION_HINT: &str = "run `rad cob migrate` to update your database";

/// COB migration progress spinner.
pub struct MigrateSpinner {
    spinner: Option<term::Spinner>,
}

impl Default for MigrateSpinner {
    /// Create a new [`MigrateSpinner`].
    fn default() -> Self {
        Self { spinner: None }
    }
}

impl MigrateCallback for MigrateSpinner {
    fn progress(&mut self, progress: MigrateProgress) {
        self.spinner
            .get_or_insert_with(|| term::spinner("Migration in progress.."))
            .message(format!(
                "Migration {}/{} in progress.. ({}%)",
                progress.migration.current(),
                progress.migration.total(),
                progress.rows.percentage()
            ));

        if progress.is_done() {
            if let Some(spinner) = self.spinner.take() {
                spinner.finish()
            }
        }
    }
}

/// Migrate functions.
pub mod migrate {
    use super::MigrateSpinner;

    /// Display migration progress via a spinner.
    #[must_use]
    pub fn spinner() -> MigrateSpinner {
        MigrateSpinner::default()
    }
}

/// Return a read-only handle for the patches cache.
pub fn patches<'a, R>(
    profile: &Profile,
    repository: &'a R,
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreReader>, anyhow::Error>
where
    R: ReadRepository + cob::Store<Namespace = NodeId>,
{
    profile.patches(repository).map_err(with_hint)
}

/// Return a read-write handle for the patches cache.
pub fn patches_mut<'a, R>(
    profile: &Profile,
    repository: &'a R,
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreWriter>, anyhow::Error>
where
    R: ReadRepository + cob::Store<Namespace = NodeId>,
{
    profile.patches_mut(repository).map_err(with_hint)
}

/// Return a read-only handle for the issues cache.
pub fn issues<'a, R>(
    profile: &Profile,
    repository: &'a R,
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreReader>, anyhow::Error>
where
    R: ReadRepository + cob::Store<Namespace = NodeId>,
{
    profile.issues(repository).map_err(with_hint)
}

/// Return a read-write handle for the issues cache.
pub fn issues_mut<'a, R>(
    profile: &Profile,
    repository: &'a R,
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreWriter>, anyhow::Error>
where
    R: ReadRepository + cob::Store<Namespace = NodeId>,
{
    profile.issues_mut(repository).map_err(with_hint)
}

/// Adds a hint to the COB out-of-date database error.
fn with_hint(e: profile::Error) -> anyhow::Error {
    // There are many types that aren't `profile::Error::CobsCache`; specifying them all in an
    // error path seems overly verbose with little value.
    #[allow(clippy::wildcard_enum_match_arm)]
    match e {
        profile::Error::CobsCache(cob::cache::Error::OutOfDate) => {
            terminal::args::Error::with_hint(e, MIGRATION_HINT).into()
        }
        e => anyhow::Error::from(e),
    }
}