use std::future::Future;
use crate::api::{ApiError, GitHub};
use crate::diff_job::GitContext;
use crate::state::{Cmd, State};
pub struct Cx {
pub gh: GitHub,
pub git: GitContext,
}
pub trait Job: Clone + std::fmt::Debug + PartialEq + Eq + Send + Sync + 'static {
type Out: std::fmt::Debug + Send + 'static;
const WRITES: bool = true;
fn wrote(result: &Result<Self::Out, Failure>) -> bool {
Self::WRITES && result.is_ok()
}
fn run(&self, cx: &Cx) -> impl Future<Output = Result<Self::Out, Failure>> + Send;
fn done(self, state: &mut State, result: Result<Self::Out, Failure>) -> Vec<Cmd>;
}
pub fn start(job: impl Into<Work>) -> Vec<Cmd> {
vec![Cmd::Work(job.into())]
}
#[derive(Debug)]
pub struct Failure(anyhow::Error);
impl Failure {
pub fn msg(text: impl std::fmt::Display + std::fmt::Debug + Send + Sync + 'static) -> Self {
Self(anyhow::Error::msg(text))
}
pub fn api(&self) -> Option<&ApiError> {
self.0.downcast_ref()
}
}
impl<E: std::error::Error + Send + Sync + 'static> From<E> for Failure {
fn from(err: E) -> Self {
Self(err.into())
}
}
impl std::fmt::Display for Failure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.api() {
Some(ApiError::GraphQl(messages)) => write!(f, "{}", messages.join("; ")),
Some(ApiError::Http { message, .. }) => write!(f, "{message}"),
_ => write!(f, "{:#}", self.0),
}
}
}
macro_rules! jobs {
($($(#[$meta:meta])* $name:ident($job:ty),)*) => {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Work {
$($(#[$meta])* $name($job),)*
}
#[derive(Debug)]
pub enum Done {
$($name($job, Result<<$job as Job>::Out, Failure>),)*
}
$(impl From<$job> for Work {
fn from(job: $job) -> Self {
Work::$name(job)
}
})*
impl Work {
pub async fn run(self, cx: &Cx) -> Done {
match self {
$(Work::$name(job) => {
let result = job.run(cx).await;
Done::$name(job, result)
})*
}
}
pub fn failed(&self) -> Done {
match self {
$(Work::$name(job) => {
Done::$name(job.clone(), Err(Failure::msg("ghtui hit a bug")))
})*
}
}
}
impl Done {
pub fn wrote(&self) -> bool {
match self {
$(Done::$name(_, result) => <$job as Job>::wrote(result),)*
}
}
#[must_use]
pub fn apply(self, state: &mut State) -> Vec<Cmd> {
match self {
$(Done::$name(job, result) => job.done(state, result),)*
}
}
}
$(#[cfg(test)]
impl Sent for $job {
fn of(work: &Work) -> Option<&Self> {
match work {
Work::$name(job) => Some(job),
_ => None,
}
}
fn answered(self, result: Result<Self::Out, Failure>) -> Done {
Done::$name(self, result)
}
})*
};
}
#[cfg(test)]
pub trait Sent: Job {
fn of(work: &Work) -> Option<&Self>;
fn answered(self, result: Result<Self::Out, Failure>) -> Done;
}
jobs! {
Change(crate::act::Changing),
Subscribe(crate::watch::Subscribe),
SetLists(crate::site::lists::SetLists),
DeleteList(crate::site::lists::DeleteList),
DeleteVersion(crate::site::packages::DeleteVersion),
Label(crate::housekeeping::labels::Save),
Milestone(crate::housekeeping::milestones::Save),
Ref(crate::housekeeping::refs::Save),
Fork(crate::housekeeping::repos::Fork),
NewRepo(crate::housekeeping::repos::Make),
CloneTo(crate::housekeeping::repos::CloneTo),
Commit(crate::housekeeping::files::Commit),
Release(crate::release::Save),
DeleteRelease(crate::release::Delete),
DeleteTag(crate::release::DeleteTag),
Notes(crate::release::Generate),
SaveAsset(crate::release::assets::Save),
Upload(crate::release::assets::Upload),
DeleteAsset(crate::release::assets::Delete),
NewGist(crate::gist::Create),
Gist(crate::gist::Update),
DeleteGist(crate::gist::Delete),
Wiki(crate::wiki::form::Save),
Revert(crate::manage::Revert),
Transfer(crate::manage::Transfer),
StartDiscussion(crate::manage::Start),
Checkout(crate::clone::Checkout),
Push(crate::create::Push),
Submit(crate::create::Submit),
SaveArtifact(crate::workflows::SaveArtifact),
EditConfig(crate::home::EditConfig),
SetViewed(crate::review::SetViewed),
Resolve(crate::review::Resolve),
SubmitReview(crate::review::Submit),
Dispatch(crate::workflows::Dispatch),
}