pub mod models; use std::io::Write; use self::models::{MenuChoice, PullRequest, ReviewMenuChoice}; #[cfg(test)] use mockall::{automock, predicate::*}; #[cfg_attr(test, automock)] pub trait ReviewBackend { fn get_prs(&self, review_request: Option) -> eyre::Result>; fn present_prs(&self, table: String) -> eyre::Result<()>; fn present_menu(&self) -> eyre::Result; fn present_diff(&self, pr: &PullRequest) -> eyre::Result<()>; fn present_review_menu(&self, pr: &PullRequest) -> eyre::Result; fn approve(&self, pr: &PullRequest) -> eyre::Result<()>; fn pr_open_browser(&self, pr: &PullRequest) -> eyre::Result<()>; fn clear(&self) -> eyre::Result<()>; fn enable_auto_merge(&self, pr: &PullRequest) -> eyre::Result<()>; fn present_pr(&self, pr: &PullRequest) -> eyre::Result<()>; } pub type DynReviewBackend = std::sync::Arc; #[derive(Default)] pub struct DefaultReviewBackend; impl ReviewBackend for DefaultReviewBackend { fn get_prs(&self, review_request: Option) -> eyre::Result> { let raw_prs = util::shell::run_with_input_and_output( &[ "gh", "search", "prs", "--state=open", "--review-requested", "@me", // review_request.unwrap().as_str(), "--label", "dependencies", //"--checks=pending", "--json", "repository,number,title", ], "".into(), )?; let prs_json = std::str::from_utf8(raw_prs.stdout.as_slice())?; let prs: Vec = serde_json::from_str(prs_json)?; Ok(prs) } fn present_prs(&self, table: String) -> eyre::Result<()> { println!("{table}"); Ok(()) } fn present_menu(&self) -> eyre::Result { println!("Menu"); println!("Begin (b), Exit (q), Menu (m), Search (s), List (l)"); print!("> "); std::io::stdout().flush()?; let mut raw_choice = String::new(); std::io::stdin().read_line(&mut raw_choice)?; let choice = match raw_choice.chars().take(1).next() { None => models::MenuChoice::Exit, Some(raw_choice) => match raw_choice { 'b' => models::MenuChoice::Begin, 'q' => models::MenuChoice::Exit, 'm' => self.present_menu()?, 's' => models::MenuChoice::Search, 'l' => models::MenuChoice::List, _ => self.present_menu()?, }, }; Ok(choice) } fn present_diff(&self, pr: &PullRequest) -> eyre::Result<()> { util::shell::run( &[ "gh", "pr", "diff", pr.number.to_string().as_str(), "--repo", pr.repository.name.as_str(), ], None, )?; Ok(()) } fn present_review_menu(&self, pr: &PullRequest) -> eyre::Result { println!(""); println!("Review - Menu"); println!("Approve (a), Merge (m), Approve and auto-merge (c), Skip (s), List (l), Open in browser (o), Exit (q)"); print!("> "); std::io::stdout().flush()?; let mut raw_choice = String::new(); std::io::stdin().read_line(&mut raw_choice)?; let choice = match raw_choice.chars().take(1).next() { None => ReviewMenuChoice::Exit, Some(raw_choice) => match raw_choice { 'q' => ReviewMenuChoice::Exit, 'l' => ReviewMenuChoice::List, 'a' => ReviewMenuChoice::Approve, 'o' => ReviewMenuChoice::Open, 's' | 'n' => ReviewMenuChoice::Skip, 'm' => ReviewMenuChoice::Merge, 'c' => ReviewMenuChoice::ApproveAndMerge, _ => self.present_review_menu(pr)?, }, }; Ok(choice) } fn approve(&self, pr: &PullRequest) -> eyre::Result<()> { util::shell::run( &[ "gh", "pr", "review", pr.number.to_string().as_str(), "--approve", "--repo", pr.repository.name.as_str(), ], None, )?; Ok(()) } fn pr_open_browser(&self, pr: &PullRequest) -> eyre::Result<()> { util::shell::run( &[ "gh", "pr", "view", pr.number.to_string().as_str(), "-w", "--repo", pr.repository.name.as_str(), ], None, )?; Ok(()) } fn clear(&self) -> eyre::Result<()> { print!("{esc}[2J{esc}[1;1H", esc = 27 as char); std::io::stdout().flush()?; Ok(()) } fn enable_auto_merge(&self, pr: &PullRequest) -> eyre::Result<()> { util::shell::run( &[ "gh", "pr", "merge", pr.number.to_string().as_str(), "--auto", "--repo", pr.repository.name.as_str(), ], None, )?; Ok(()) } fn present_pr(&self, pr: &PullRequest) -> eyre::Result<()> { println!("repo: {} - title: {}", pr.repository.name, pr.title); Ok(()) } } impl DefaultReviewBackend { pub fn new() -> Self { Self {} } }