#![recursion_limit = "256"]
#![warn(missing_docs)]
mod actions_util;
mod app;
mod cast;
mod config;
mod event;
mod git;
mod github;
mod state;
mod theme;
mod ui;
use anyhow::{Context, Result};
use app::App;
use clap::{Parser, Subcommand};
use crossterm::{
cursor::Show,
event::{DisableMouseCapture, EnableMouseCapture},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::prelude::*;
struct TerminalGuard;
impl Drop for TerminalGuard {
fn drop(&mut self) {
let _ = disable_raw_mode();
let _ = execute!(std::io::stdout(), LeaveAlternateScreen, DisableMouseCapture, Show,);
}
}
#[derive(Debug, clap::Args)]
struct DumpArgs {
kind: Option<String>,
repo: Option<String>,
number: Option<u32>,
}
#[derive(Subcommand, Debug)]
enum DebugCommand {
Dump(DumpArgs),
}
#[derive(Subcommand, Debug)]
enum Commands {
Debug {
#[command(subcommand)]
cmd: DebugCommand,
},
}
#[derive(Parser, Debug)]
#[command(
name = "octopeek",
about = "A fast, keyboard-driven TUI for your GitHub PR and issue inbox.",
version
)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")),
)
.with_writer(std::io::stderr)
.init();
if let Some(Commands::Debug { cmd: DebugCommand::Dump(args) }) = cli.command {
return run_debug_dump(args).await;
}
let config = config::Config::load();
let session = state::AppSession::load();
enable_raw_mode()?;
let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let _guard = TerminalGuard;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let app = App::new(config, session);
app.run(&mut terminal).await
}
async fn run_debug_dump(args: DumpArgs) -> Result<()> {
let token = github::auth::load_token()?;
let client = github::Client::new(token)?;
match args.kind.as_deref() {
None => {
let inbox = client.fetch_inbox().await?;
println!("{}", serde_json::to_string_pretty(&inbox)?);
}
Some("pr") => {
let repo = args.repo.context("`repo` argument is required for `debug dump pr`")?;
let number =
args.number.context("`number` argument is required for `debug dump pr`")?;
let detail = client.fetch_pr_detail(&repo, number).await?;
println!("{}", serde_json::to_string_pretty(&detail)?);
}
Some("issue") => {
let repo = args.repo.context("`repo` argument is required for `debug dump issue`")?;
let number =
args.number.context("`number` argument is required for `debug dump issue`")?;
let detail = client.fetch_issue_detail(&repo, number).await?;
println!("{}", serde_json::to_string_pretty(&detail)?);
}
Some(other) => {
anyhow::bail!(
"unknown dump kind `{other}`: expected `pr` or `issue`, or omit for inbox"
);
}
}
Ok(())
}