use clap::{Args, Parser, Subcommand};
use tracing::Instrument;
#[path = "../bundle/backend/mod.rs"]
pub mod backend;
mod daemon;
mod log;
mod models;
mod commands;
mod rpc_client;
mod server;
mod store;
mod ui;
#[derive(Subcommand)]
enum Command {
Up(Up),
Down,
Status,
Add(commands::add::AddArgs),
Commit(commands::commit::CommitArgs),
Push(commands::push::PushArgs),
Review(commands::review::ReviewArgs),
Comment(commands::comment::CommentArgs),
Ui(commands::ui::UiArgs),
#[command(hide = true)]
Serve {
#[arg(long, default_value_t = 8080)]
port: u16,
#[arg(long)]
daemonize: bool,
},
}
#[derive(Args)]
struct Up {
#[arg(long, default_value_t = 8080)]
port: u16,
#[arg(long)]
foreground: bool,
#[arg(long)]
no_open: bool,
}
#[derive(Parser)]
#[command(name = "lgtm", version, about)]
struct Cli {
#[arg(long, global = true)]
json: bool,
#[command(subcommand)]
command: Command,
}
fn main() {
let json_output = std::env::args().any(|arg| arg == "--json");
if let Err(error) = run() {
let code = error
.downcast_ref::<connectrpc::ConnectError>()
.map(|connect_err| connect_err.code.as_str())
.or_else(|| {
error
.downcast_ref::<crate::backend::error::Error>()
.map(|domain_err| match domain_err {
crate::backend::error::Error::Invalid(_) => "invalid_argument",
crate::backend::error::Error::NotFound(_) => "not_found",
_ => "command_failed",
})
})
.unwrap_or("command_failed");
if json_output {
println!(
"{}",
serde_json::json!({
"error": {
"code": code,
"message": error.to_string(),
}
})
);
} else {
eprintln!("{error:#}");
}
std::process::exit(1);
}
}
fn run() -> anyhow::Result<()> {
let cli = Cli::parse();
let store = store::Store::new()?;
if let Command::Serve {
daemonize: true, ..
} = cli.command
{
daemon::detach(&store)?;
}
if matches!(cli.command, Command::Serve { .. })
|| matches!(&cli.command, Command::Up(options) if options.foreground)
{
log::init(&store)?;
}
let command = match &cli.command {
Command::Up(_) => "up",
Command::Down => "down",
Command::Status => "status",
Command::Add(_) => "add",
Command::Review(_) => "review",
Command::Commit(_) => "commit",
Command::Push(_) => "push",
Command::Comment(_) => "comment",
Command::Ui(_) => "ui",
Command::Serve { .. } => "serve",
};
tokio::runtime::Runtime::new()?.block_on(
async {
match cli.command {
Command::Up(options) if options.foreground => {
server::serve(options.port, &store, false).await
}
Command::Up(options) => daemon::up(options.port, !options.no_open, &store).await,
Command::Down => daemon::down(&store).await,
Command::Status => daemon::status(&store, cli.json).await,
Command::Add(args) => commands::add::run(args, &store, cli.json).await,
Command::Review(args) => commands::review::run(args, &store, cli.json).await,
Command::Commit(args) => commands::commit::run(args, &store, cli.json).await,
Command::Push(args) => commands::push::run(args, &store, cli.json).await,
Command::Comment(args) => commands::comment::run(args, &store, cli.json).await,
Command::Ui(args) => commands::ui::run(args, &store, cli.json).await,
Command::Serve { port, daemonize } => server::serve(port, &store, daemonize).await,
}
}
.instrument(tracing::info_span!("cli_command", command)),
)
}