use clap::{Args, Parser, Subcommand, ValueEnum};
#[derive(Parser)]
#[command(
name = "prlens",
version,
about = "Aggregate PR review queues from GitHub and Bitbucket",
long_about = "prlens shows all pull requests waiting for your review across providers.\nConfigured via ~/.config/prlens/config.toml",
)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand)]
pub enum Commands {
List(ListArgs),
Open(OpenArgs),
}
#[derive(Args)]
pub struct OpenArgs {
pub number: u64,
}
#[derive(Clone, Debug, ValueEnum)]
pub enum StatusFilter {
Pending,
Approved,
#[value(name = "changes-requested")]
ChangesRequested,
}
#[derive(Clone, Debug, ValueEnum)]
pub enum SortKey {
Age,
Author,
Status,
}
#[derive(Args, Clone)]
pub struct ListArgs {
#[arg(long)]
pub repo: Option<String>,
#[arg(long)]
pub org: Option<String>,
#[arg(long, value_enum)]
pub status: Option<StatusFilter>,
#[arg(long, value_enum, default_value = "age")]
pub sort: SortKey,
#[arg(short = 'p', long)]
pub profile: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn parse_status_pending() {
let cli = Cli::try_parse_from(["prlens", "list", "--status", "pending"]).unwrap();
match cli.command {
Some(Commands::List(args)) => {
assert!(matches!(args.status, Some(StatusFilter::Pending)));
}
_ => panic!("expected List subcommand"),
}
}
#[test]
fn parse_status_changes_requested() {
let cli = Cli::try_parse_from(["prlens", "list", "--status", "changes-requested"]).unwrap();
match cli.command {
Some(Commands::List(args)) => {
assert!(matches!(args.status, Some(StatusFilter::ChangesRequested)));
}
_ => panic!("expected List subcommand"),
}
}
#[test]
fn parse_sort_author() {
let cli = Cli::try_parse_from(["prlens", "list", "--sort", "author"]).unwrap();
match cli.command {
Some(Commands::List(args)) => {
assert!(matches!(args.sort, SortKey::Author));
}
_ => panic!("expected List subcommand"),
}
}
#[test]
fn parse_default_sort_is_age() {
let cli = Cli::try_parse_from(["prlens", "list"]).unwrap();
match cli.command {
Some(Commands::List(args)) => {
assert!(matches!(args.sort, SortKey::Age));
}
_ => panic!("expected List subcommand"),
}
}
#[test]
fn parse_open_with_number_succeeds() {
let cli = Cli::try_parse_from(["prlens", "open", "42"]).unwrap();
match cli.command {
Some(Commands::Open(args)) => {
assert_eq!(args.number, 42);
}
_ => panic!("expected Open subcommand with number 42"),
}
}
#[test]
fn parse_open_without_number_returns_err() {
let result = Cli::try_parse_from(["prlens", "open"]);
assert!(result.is_err(), "open without a number should fail to parse");
}
#[test]
fn parse_profile_short_flag() {
let cli = Cli::try_parse_from(["prlens", "list", "-p", "work"]).unwrap();
match cli.command {
Some(Commands::List(args)) => assert_eq!(args.profile.as_deref(), Some("work")),
_ => panic!("expected List subcommand"),
}
}
#[test]
fn parse_profile_long_flag() {
let cli = Cli::try_parse_from(["prlens", "list", "--profile", "oss"]).unwrap();
match cli.command {
Some(Commands::List(args)) => assert_eq!(args.profile.as_deref(), Some("oss")),
_ => panic!("expected List subcommand"),
}
}
#[test]
fn parse_profile_default_is_none() {
let cli = Cli::try_parse_from(["prlens", "list"]).unwrap();
match cli.command {
Some(Commands::List(args)) => assert_eq!(args.profile, None),
_ => panic!("expected List subcommand"),
}
}
}