Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
refactor: Improve app context creation
Erik Kundt committed 2 years ago
commit f58c93a36f7a5ba2a57c2f05ce17804411efa0da
parent f4eaf5c17eb2edf1dc10e0a726be22b19acb55a5
12 files changed +146 -172
added src/context.rs
@@ -0,0 +1,109 @@
+
use radicle_term as term;
+

+
use radicle::cob::issue::{Issue, IssueId};
+
use radicle::cob::patch::{Patch, PatchId};
+
use radicle::crypto::ssh::keystore::MemorySigner;
+
use radicle::prelude::{Id, Project, Signer};
+
use radicle::profile::env::RAD_PASSPHRASE;
+
use radicle::Profile;
+

+
use radicle::storage::git::Repository;
+
use radicle::storage::ReadStorage;
+

+
use term::{passphrase, spinner};
+

+
pub struct Context {
+
    profile: Profile,
+
    id: Id,
+
    project: Project,
+
    repository: Repository,
+
    issues: Vec<(IssueId, Issue)>,
+
    patches: Vec<(PatchId, Patch)>,
+
    signer: Box<dyn Signer>,
+
}
+

+
impl Context {
+
    pub fn new(id: Id) -> Result<Self, anyhow::Error> {
+
        use anyhow::Context;
+
        
+
        let profile = profile()?;
+
        let signer = signer(&profile)?;
+
        let payload = &profile
+
            .storage
+
            .get(signer.public_key(), id)?
+
            .context("No project with such `id` exists")?;
+
        let project = payload.project()?;
+

+
        let repository = profile.storage.repository(id).unwrap();
+
        let issues = crate::cob::issue::all(&repository).unwrap_or_default();
+
        let patches = crate::cob::patch::all(&repository).unwrap_or_default();
+

+
        Ok(Self {
+
            id,
+
            profile,
+
            project,
+
            repository,
+
            issues,
+
            patches,
+
            signer,
+
        })
+
    }
+

+
    pub fn profile(&self) -> &Profile {
+
        &self.profile
+
    }
+

+
    pub fn id(&self) -> &Id {
+
        &self.id
+
    }
+

+
    pub fn project(&self) -> &Project {
+
        &self.project
+
    }
+

+
    pub fn repository(&self) -> &Repository {
+
        &self.repository
+
    }
+

+
    pub fn issues(&self) -> &Vec<(IssueId, Issue)> {
+
        &self.issues
+
    }
+

+
    pub fn patches(&self) -> &Vec<(PatchId, Patch)> {
+
        &self.patches
+
    }
+

+
    #[allow(clippy::borrowed_box)]
+
    pub fn signer(&self) -> &Box<dyn Signer> {
+
        &self.signer
+
    }
+

+
    pub fn reload(&mut self) {
+
        self.issues = crate::cob::issue::all(&self.repository).unwrap_or_default();
+
        self.patches = crate::cob::patch::all(&self.repository).unwrap_or_default();
+
    }
+
}
+

+
/// Get the default profile. Fails if there is no profile.
+
fn profile() -> Result<Profile, anyhow::Error> {
+
    match Profile::load() {
+
        Ok(profile) => Ok(profile),
+
        Err(_) => Err(anyhow::anyhow!(
+
            "Could not load radicle profile. To setup your radicle profile, run `rad auth`."
+
        )),
+
    }
+
}
+

+
/// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user.
+
fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
+
    if let Ok(signer) = profile.signer() {
+
        return Ok(signer);
+
    }
+
    let passphrase = passphrase(RAD_PASSPHRASE)?;
+
    let spinner = spinner("Unsealing key...");
+
    let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;
+

+
    spinner.finish();
+

+
    Ok(signer.boxed())
+
}
modified src/issue/app.rs
@@ -5,9 +5,6 @@ mod ui;
use anyhow::Result;

use radicle::cob::issue::IssueId;
-
use radicle::identity::{Id, Project};
-
use radicle::prelude::Signer;
-
use radicle::profile::Profile;

use tuirealm::application::PollStrategy;
use tuirealm::{Application, Frame, NoUserEvent, Sub, SubClause};
@@ -15,7 +12,7 @@ use tuirealm::{Application, Frame, NoUserEvent, Sub, SubClause};
use radicle_tui as tui;

use tui::cob;
-
use tui::ui::context::Context;
+
use tui::context::Context;
use tui::ui::subscription;
use tui::ui::theme::{self, Theme};
use tui::PageStack;
@@ -105,9 +102,9 @@ pub struct App {
/// Creates a new application using a tui-realm-application, mounts all
/// components and sets focus to a default one.
impl App {
-
    pub fn new(profile: Profile, id: Id, project: Project, signer: Box<dyn Signer>) -> Self {
+
    pub fn new(context: Context) -> Self {
        Self {
-
            context: Context::new(profile, id, project, signer),
+
            context,
            pages: PageStack::default(),
            theme: theme::default_dark(),
            quit: false,
modified src/issue/app/page.rs
@@ -9,7 +9,7 @@ use tuirealm::{AttrValue, Attribute, Frame, NoUserEvent, State, StateValue, Sub,
use radicle_tui as tui;

use tui::cob;
-
use tui::ui::context::Context;
+
use tui::context::Context;
use tui::ui::layout;
use tui::ui::theme::Theme;
use tui::ui::widget::context::{Progress, Shortcuts};
modified src/issue/app/ui.rs
@@ -10,9 +10,9 @@ use tuirealm::{AttrValue, Attribute, Frame, MockComponent, Props, State};

use radicle_tui as tui;

+
use tui::context::Context;
use tui::ui::cob;
use tui::ui::cob::IssueItem;
-
use tui::ui::context::Context;
use tui::ui::theme::Theme;
use tui::ui::widget::{Widget, WidgetComponent};

modified src/issue/main.rs
@@ -1,20 +1,19 @@
+
mod app;
+

use std::process;

-
use anyhow::{anyhow, Context};
+
use anyhow::anyhow;
+

+
use radicle::profile;
+

use log::info;
use log::LevelFilter;

-
use radicle::profile;
-
use radicle::{
-
    crypto::ssh::keystore::MemorySigner, prelude::Signer, profile::env::RAD_PASSPHRASE, Profile,
-
};
use radicle_term as term;
-
use radicle_tui::Window;
+
use radicle_tui as tui;

-
use radicle::storage::ReadStorage;
-
use term::{passphrase, spinner};
-

-
mod app;
+
use tui::context;
+
use tui::Window;

pub const NAME: &str = "rad-issue-tui";
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -59,42 +58,12 @@ impl Options {
    }
}

-
/// Get the default profile. Fails if there is no profile.
-
pub fn profile() -> Result<Profile, anyhow::Error> {
-
    match Profile::load() {
-
        Ok(profile) => Ok(profile),
-
        Err(_) => Err(anyhow::anyhow!(
-
            "Could not load radicle profile. To setup your radicle profile, run `rad auth`."
-
        )),
-
    }
-
}
-

-
/// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user.
-
pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
-
    if let Ok(signer) = profile.signer() {
-
        return Ok(signer);
-
    }
-
    let passphrase = passphrase(RAD_PASSPHRASE)?;
-
    let spinner = spinner("Unsealing key...");
-
    let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;
-

-
    spinner.finish();
-

-
    Ok(signer.boxed())
-
}
-

fn execute() -> anyhow::Result<()> {
    let _ = Options::from_env()?;

    let (_, id) = radicle::rad::cwd()
        .map_err(|_| anyhow!("this command must be run in the context of a project"))?;
-
    let profile = profile()?;
-
    let signer = signer(&profile)?;
-
    let payload = &profile
-
        .storage
-
        .get(signer.public_key(), id)?
-
        .context("No project with such `id` exists")?;
-
    let project = payload.project()?;
+
    let context = context::Context::new(id)?;

    let logfile = format!(
        "{}/rad-issue-tui.log",
@@ -104,7 +73,7 @@ fn execute() -> anyhow::Result<()> {
    info!("Launching window...");

    let mut window = Window::default();
-
    window.run(&mut app::App::new(profile, id, project, signer), 1000 / FPS)?;
+
    window.run(&mut app::App::new(context), 1000 / FPS)?;

    Ok(())
}
modified src/lib.rs
@@ -9,9 +9,10 @@ use tuirealm::Frame;
use tuirealm::{Application, EventListenerCfg, NoUserEvent};

pub mod cob;
+
pub mod context;
pub mod ui;

-
use ui::context::Context;
+
use context::Context;
use ui::theme::Theme;

/// Trait that must be implemented by client applications in order to be run
modified src/patch/app.rs
@@ -7,16 +7,14 @@ use std::hash::Hash;
use anyhow::Result;

use radicle::cob::patch::PatchId;
-
use radicle::identity::{Id, Project};
-
use radicle::prelude::Signer;
-
use radicle::profile::Profile;

use tuirealm::application::PollStrategy;
use tuirealm::{Application, Frame, NoUserEvent, Sub, SubClause};

use radicle_tui as tui;
+

use radicle_tui::cob;
-
use radicle_tui::ui::context::Context;
+
use radicle_tui::context::Context;
use radicle_tui::ui::subscription;
use radicle_tui::ui::theme::{self, Theme};
use radicle_tui::PageStack;
@@ -88,9 +86,9 @@ pub struct App {
/// Creates a new application using a tui-realm-application, mounts all
/// components and sets focus to a default one.
impl App {
-
    pub fn new(profile: Profile, id: Id, project: Project, signer: Box<dyn Signer>) -> Self {
+
    pub fn new(context: Context) -> Self {
        Self {
-
            context: Context::new(profile, id, project, signer),
+
            context,
            pages: PageStack::default(),
            theme: theme::default_dark(),
            quit: false,
modified src/patch/app/page.rs
@@ -8,7 +8,7 @@ use tuirealm::{Frame, NoUserEvent, State, StateValue, Sub, SubClause};

use radicle_tui as tui;

-
use tui::ui::context::Context;
+
use tui::context::Context;
use tui::ui::theme::Theme;
use tui::ui::widget::context::{Progress, Shortcuts};
use tui::ui::widget::Widget;
modified src/patch/app/ui.rs
@@ -6,9 +6,9 @@ use tuirealm::{AttrValue, Attribute, Frame, MockComponent, Props, State};

use radicle_tui as tui;

+
use tui::context::Context;
use tui::ui::cob;
use tui::ui::cob::PatchItem;
-
use tui::ui::context::Context;
use tui::ui::layout;
use tui::ui::theme::Theme;
use tui::ui::widget::{Widget, WidgetComponent};
modified src/patch/main.rs
@@ -1,20 +1,19 @@
+
mod app;
+

use std::process;

-
use anyhow::{anyhow, Context};
+
use anyhow::anyhow;
+

+
use radicle::profile;
+

use log::info;
use log::LevelFilter;

-
use radicle::profile;
-
use radicle::{
-
    crypto::ssh::keystore::MemorySigner, prelude::Signer, profile::env::RAD_PASSPHRASE, Profile,
-
};
use radicle_term as term;
-
use radicle_tui::Window;
+
use radicle_tui as tui;

-
use radicle::storage::ReadStorage;
-
use term::{passphrase, spinner};
-

-
mod app;
+
use tui::context;
+
use tui::Window;

pub const NAME: &str = "rad-patch-tui";
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@@ -24,7 +23,7 @@ pub const FPS: u64 = 60;
pub const HELP: &str = r#"
Usage

-
rad-patch-tui [<option>...]
+
    rad-patch-tui [<option>...]

Options

@@ -59,42 +58,12 @@ impl Options {
    }
}

-
/// Get the default profile. Fails if there is no profile.
-
pub fn profile() -> Result<Profile, anyhow::Error> {
-
    match Profile::load() {
-
        Ok(profile) => Ok(profile),
-
        Err(_) => Err(anyhow::anyhow!(
-
            "Could not load radicle profile. To setup your radicle profile, run `rad auth`."
-
        )),
-
    }
-
}
-

-
/// Get the signer. First we try getting it from ssh-agent, otherwise we prompt the user.
-
pub fn signer(profile: &Profile) -> anyhow::Result<Box<dyn Signer>> {
-
    if let Ok(signer) = profile.signer() {
-
        return Ok(signer);
-
    }
-
    let passphrase = passphrase(RAD_PASSPHRASE)?;
-
    let spinner = spinner("Unsealing key...");
-
    let signer = MemorySigner::load(&profile.keystore, Some(passphrase))?;
-

-
    spinner.finish();
-

-
    Ok(signer.boxed())
-
}
-

fn execute() -> anyhow::Result<()> {
    let _ = Options::from_env()?;

    let (_, id) = radicle::rad::cwd()
        .map_err(|_| anyhow!("this command must be run in the context of a project"))?;
-
    let profile = profile()?;
-
    let signer = signer(&profile)?;
-
    let payload = &profile
-
        .storage
-
        .get(signer.public_key(), id)?
-
        .context("No project with such `id` exists")?;
-
    let project = payload.project()?;
+
    let context = context::Context::new(id)?;

    let logfile = format!(
        "{}/rad-patch-tui.log",
@@ -104,7 +73,7 @@ fn execute() -> anyhow::Result<()> {
    info!("Launching window...");

    let mut window = Window::default();
-
    window.run(&mut app::App::new(profile, id, project, signer), 1000 / FPS)?;
+
    window.run(&mut app::App::new(context), 1000 / FPS)?;

    Ok(())
}
modified src/ui.rs
@@ -1,5 +1,4 @@
pub mod cob;
-
pub mod context;
pub mod ext;
pub mod layout;
pub mod state;
@@ -20,7 +19,7 @@ use widget::list::{ColumnWidth, Property, PropertyList, PropertyTable};

use widget::Widget;

-
use crate::ui::context::Context;
+
use super::context::Context;
use crate::ui::theme::Theme;

pub fn global_listener() -> Widget<GlobalListener> {
deleted src/ui/context.rs
@@ -1,68 +0,0 @@
-
use radicle::cob::issue::{Issue, IssueId};
-
use radicle::cob::patch::{Patch, PatchId};
-
use radicle::prelude::{Id, Project, Signer};
-
use radicle::Profile;
-

-
use radicle::storage::git::Repository;
-
use radicle::storage::ReadStorage;
-
pub struct Context {
-
    profile: Profile,
-
    id: Id,
-
    project: Project,
-
    repository: Repository,
-
    issues: Vec<(IssueId, Issue)>,
-
    patches: Vec<(PatchId, Patch)>,
-
    signer: Box<dyn Signer>,
-
}
-

-
impl Context {
-
    pub fn new(profile: Profile, id: Id, project: Project, signer: Box<dyn Signer>) -> Self {
-
        let repository = profile.storage.repository(id).unwrap();
-
        let issues = crate::cob::issue::all(&repository).unwrap_or_default();
-
        let patches = crate::cob::patch::all(&repository).unwrap_or_default();
-

-
        Self {
-
            id,
-
            profile,
-
            project,
-
            repository,
-
            issues,
-
            patches,
-
            signer,
-
        }
-
    }
-

-
    pub fn profile(&self) -> &Profile {
-
        &self.profile
-
    }
-

-
    pub fn id(&self) -> &Id {
-
        &self.id
-
    }
-

-
    pub fn project(&self) -> &Project {
-
        &self.project
-
    }
-

-
    pub fn repository(&self) -> &Repository {
-
        &self.repository
-
    }
-

-
    pub fn issues(&self) -> &Vec<(IssueId, Issue)> {
-
        &self.issues
-
    }
-

-
    pub fn patches(&self) -> &Vec<(PatchId, Patch)> {
-
        &self.patches
-
    }
-

-
    #[allow(clippy::borrowed_box)]
-
    pub fn signer(&self) -> &Box<dyn Signer> {
-
        &self.signer
-
    }
-

-
    pub fn reload(&mut self) {
-
        self.issues = crate::cob::issue::all(&self.repository).unwrap_or_default();
-
        self.patches = crate::cob::patch::all(&self.repository).unwrap_or_default();
-
    }
-
}