diff --git a/Cargo.lock b/Cargo.lock index 967b46d..1ca03e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,6 +216,15 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_complete" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" +dependencies = [ + "clap", +] + [[package]] name = "clap_derive" version = "4.5.4" @@ -234,6 +243,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "clap_mangen" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e" +dependencies = [ + "clap", + "roff", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -1040,6 +1059,8 @@ version = "0.18.0" dependencies = [ "anyhow", "clap", + "clap_complete", + "clap_mangen", "git2", "http", "lazy_static", @@ -1120,6 +1141,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + [[package]] name = "rustc-demangle" version = "0.1.24" diff --git a/Cargo.toml b/Cargo.toml index 6ddd359..c9147b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "prr" description = "Mailing list style code reviews for github" -license-file = "LICENSE" +license = "GPL-2.0-or-later" repository = "https://github.com/danobi/prr" version = "0.18.0" edition = "2021" +build = "build.rs" [dependencies] anyhow = "1.0" @@ -26,6 +27,12 @@ xdg = "2.4" pretty_assertions = "1.4.0" tempfile = "3.8.1" +[build-dependencies] +anyhow = "1.0" +clap = { version = "4.4", features = ["derive"] } +clap_mangen = { version = "0.2.20", optional = true } +clap_complete = { version = "4.5.2", optional = true } + [features] # Statically link a vendored copy OpenSSL. OpenSSL is used by all of `git2`, `reqwest` and # `octocrab`, enabling vendoring for just one of them should be enough. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..abb8992 --- /dev/null +++ b/build.rs @@ -0,0 +1,54 @@ +mod cli { + include!("src/cli.rs"); +} + +const LONG_ABOUT: &str = + "prr is a tool that brings mailing list style code reviews to Github PRs. This \ +means offline reviews and inline comments, more or less. + +To that end, prr introduces a new workflow for reviewing PRs: + 1. Download the PR into a \"review file\" on your filesystem + 2. Mark up the review file using your favorite text editor + 3. Submit the review at your convenience + +For full documentation, please visit https://doc.dxuuu.xyz/prr/."; + +fn main() -> std::io::Result<()> { + if let Some(out_path) = std::env::var_os("GEN_DIR").or(std::env::var_os("OUT_DIR")) { + use clap::CommandFactory; + #[allow(unused_variables)] + let out_dir = std::path::PathBuf::from(out_path); + #[allow(unused_mut, unused_variables)] + let mut cmd = cli::Cli::command() + .author("Daniel Xu ") + .about("Mailing list style code reviews for GitHub") + .long_about(LONG_ABOUT); + + #[cfg(feature = "clap_mangen")] + { + let man_dir = std::path::Path::join(&out_dir, "man"); + std::fs::create_dir_all(&man_dir)?; + clap_mangen::generate_to(cmd.clone(), &man_dir)?; + } + + #[cfg(feature = "clap_complete")] + { + use clap::ValueEnum; + let completions_dir = std::path::Path::join(&out_dir, "completions"); + std::fs::create_dir_all(&completions_dir)?; + for shell in clap_complete::Shell::value_variants() { + clap_complete::generate_to(*shell, &mut cmd, "prr", &completions_dir)?; + } + } + } + + println!( + "cargo:rustc-env=TARGET={}", + std::env::var("TARGET").unwrap() + ); + println!("cargo:rerun-if-env-changed=GEN_DIR"); + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_CLAP_MANGEN"); + println!("cargo:rerun-if-env-changed=CARGO_FEATURE_CLAP_COMPLETE"); + + Ok(()) +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..e812eec --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,61 @@ +use clap::{Parser, Subcommand}; +use std::path::PathBuf; + +#[derive(Subcommand, Debug)] +pub(crate) enum Command { + /// Get a pull request and begin a review + Get { + /// Ignore unsubmitted review checks + #[clap(short, long)] + force: bool, + /// Pull request to review (eg. `danobi/prr/24`) + pr: String, + /// Open review file in $EDITOR after download + #[clap(long)] + open: bool, + }, + /// Open an existing review in $EDITOR + Edit { + /// Pull request to edit (eg. `danobi/prr/24`) + pr: String, + }, + /// Submit a review + Submit { + /// Pull request to review (eg. `danobi/prr/24`) + pr: String, + #[clap(short, long)] + debug: bool, + }, + /// Apply a pull request to the working directory + /// + /// This can be useful for building/testing PRs + Apply { pr: String }, + /// Print a status summary of all known reviews + Status { + /// Hide column titles from output + #[clap(short, long)] + no_titles: bool, + }, + /// Remove a review + Remove { + /// Pull requests to remove (eg. `danobi/prr/24`) + prs: Vec, + /// Ignore unsubmitted review checks + #[clap(short, long)] + force: bool, + /// Remove submitted reviews in addition to provided reviews + #[clap(short, long)] + submitted: bool, + }, +} + +#[derive(Parser, Debug)] +#[clap(version)] +#[command(name = "prr")] +pub struct Cli { + /// Path to config file + #[clap(long)] + pub(crate) config: Option, + #[clap(subcommand)] + pub(crate) command: Command, +} diff --git a/src/main.rs b/src/main.rs index 9c4dcb6..6fb8002 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,75 +3,19 @@ use std::path::{Path, PathBuf}; use std::process; use anyhow::{bail, Context, Result}; -use clap::{Parser, Subcommand}; +use clap::Parser; +mod cli; mod parser; mod prr; mod review; +use cli::*; use prr::Prr; /// The name of the local configuration file pub const LOCAL_CONFIG_FILE_NAME: &str = ".prr.toml"; -#[derive(Subcommand, Debug)] -enum Command { - /// Get a pull request and begin a review - Get { - /// Ignore unsubmitted review checks - #[clap(short, long)] - force: bool, - /// Pull request to review (eg. `danobi/prr/24`) - pr: String, - /// Open review file in $EDITOR after download - #[clap(long)] - open: bool, - }, - /// Open an existing review in $EDITOR - Edit { - /// Pull request to edit (eg. `danobi/prr/24`) - pr: String, - }, - /// Submit a review - Submit { - /// Pull request to review (eg. `danobi/prr/24`) - pr: String, - #[clap(short, long)] - debug: bool, - }, - /// Apply a pull request to the working directory - /// - /// This can be useful for building/testing PRs - Apply { pr: String }, - /// Print a status summary of all known reviews - Status { - /// Hide column titles from output - #[clap(short, long)] - no_titles: bool, - }, - /// Remove a review - Remove { - /// Pull requests to remove (eg. `danobi/prr/24`) - prs: Vec, - /// Ignore unsubmitted review checks - #[clap(short, long)] - force: bool, - /// Remove submitted reviews in addition to provided reviews - #[clap(short, long)] - submitted: bool, - }, -} - -#[derive(Parser, Debug)] -#[clap(version)] -struct Args { - /// Path to config file - #[clap(long)] - config: Option, - #[clap(subcommand)] - command: Command, -} - /// Returns if exists the config file for the current project fn find_project_config_file() -> Option { env::current_dir().ok().and_then(|mut path| loop { @@ -109,7 +53,7 @@ fn open_review(file: &Path) -> Result<()> { #[tokio::main] async fn main() -> Result<()> { - let args = Args::parse(); + let args = Cli::parse(); // Figure out where config file is let config_path = match args.config {