pub mod auth;
pub mod check;
pub mod doctor;
pub mod init;
pub mod lint_docs;
pub mod render;
use anyhow::Result;
use clap::builder::{PossibleValuesParser, TypedValueParser};
use clap::{Parser, Subcommand, ValueEnum};
use crate::analysis::findings::Severity;
use crate::Exit;
use auth::AuthArgs;
use check::CheckArgs;
use doctor::DoctorArgs;
use init::InitArgs;
use lint_docs::LintDocsArgs;
#[derive(Debug, Parser)]
#[command(
name = "drep",
version,
about = "Run the linters your repo configures, and have an LLM review what changed",
long_about = None,
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Check(CheckArgs),
LintDocs(LintDocsArgs),
Doctor(DoctorArgs),
Init(InitArgs),
Auth(AuthArgs),
}
pub(crate) fn severity_parser() -> impl TypedValueParser<Value = Severity> {
PossibleValuesParser::new(Severity::ALL.map(Severity::as_str))
.map(|name| name.parse::<Severity>().expect("possible values parse"))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum OutputFormat {
Text,
Json,
}
pub async fn run(cli: Cli) -> Result<Exit> {
match cli.command {
Command::Check(args) => check::run(&args, std::path::Path::new(".")).await,
Command::LintDocs(args) => lint_docs::run(&args, std::path::Path::new(".")).await,
Command::Doctor(args) => doctor::run(&args),
Command::Init(args) => init::run(&args).await,
Command::Auth(args) => auth::run(&args),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::analysis::findings::Severity;
use clap::CommandFactory;
fn check_args<const N: usize>(argv: [&str; N]) -> CheckArgs {
let cli = Cli::try_parse_from(argv).expect("should parse");
match cli.command {
Command::Check(args) => args,
other => panic!("expected check, got {other:?}"),
}
}
fn lint_docs_args<const N: usize>(argv: [&str; N]) -> LintDocsArgs {
let cli = Cli::try_parse_from(argv).expect("should parse");
match cli.command {
Command::LintDocs(args) => args,
other => panic!("expected lint-docs, got {other:?}"),
}
}
#[test]
fn lint_docs_gating_is_off_by_default() {
let args = lint_docs_args(["drep", "lint-docs"]);
assert!(!args.strict);
assert_eq!(args.fail_on, None);
assert_eq!(args.threshold(), None);
}
#[test]
fn lint_docs_fail_on_accepts_the_whole_severity_vocabulary() {
for expected in Severity::ALL {
let args = lint_docs_args(["drep", "lint-docs", "--fail-on", expected.as_str()]);
assert_eq!(args.fail_on, Some(expected));
assert_eq!(args.threshold(), Some(expected));
}
assert!(Cli::try_parse_from(["drep", "lint-docs", "--fail-on", "critical"]).is_err());
}
#[test]
fn lint_docs_strict_is_fail_on_info() {
assert_eq!(
lint_docs_args(["drep", "lint-docs", "--strict"]).threshold(),
Some(Severity::Info)
);
}
#[test]
fn lint_docs_strict_and_fail_on_are_mutually_exclusive() {
assert!(
Cli::try_parse_from(["drep", "lint-docs", "--strict", "--fail-on", "error"]).is_err()
);
}
#[test]
fn cli_definition_is_valid() {
Cli::command().debug_assert();
}
#[test]
fn input_modes_are_mutually_exclusive() {
assert!(Cli::try_parse_from(["drep", "check", "--staged", "a.rs"]).is_err());
assert!(Cli::try_parse_from(["drep", "check", "--staged", "--diff", "main"]).is_err());
assert!(Cli::try_parse_from(["drep", "check", "--diff", "main", "a.rs"]).is_err());
}
#[test]
fn each_input_mode_parses_alone() {
assert_eq!(check_args(["drep", "check", "a.rs", "src/"]).paths.len(), 2);
assert!(check_args(["drep", "check", "--staged"]).staged);
assert_eq!(
check_args(["drep", "check", "--diff", "origin/main"])
.diff
.as_deref(),
Some("origin/main")
);
assert!(check_args(["drep", "check"]).paths.is_empty());
}
#[test]
fn format_defaults_to_text_and_fail_on_defaults_to_off() {
let args = check_args(["drep", "check"]);
assert_eq!(args.format, OutputFormat::Text);
assert_eq!(args.fail_on, None);
}
#[test]
fn fail_on_accepts_the_whole_severity_vocabulary() {
for expected in Severity::ALL {
let args = check_args(["drep", "check", "--fail-on", expected.as_str()]);
assert_eq!(args.fail_on, Some(expected));
}
assert!(Cli::try_parse_from(["drep", "check", "--fail-on", "critical"]).is_err());
}
#[test]
fn every_command_dispatches_to_an_implementation() {
let names: Vec<String> = Cli::command()
.get_subcommands()
.map(|c| c.get_name().to_owned())
.collect();
assert_eq!(names, vec!["check", "lint-docs", "doctor", "init", "auth"]);
}
#[test]
fn lint_docs_takes_paths_and_strict() {
let cli = Cli::try_parse_from(["drep", "lint-docs", "--strict", "a.md"]).unwrap();
match cli.command {
Command::LintDocs(args) => {
assert!(args.strict);
assert_eq!(args.paths.len(), 1);
}
other => panic!("expected lint-docs, got {other:?}"),
}
let cli = Cli::try_parse_from(["drep", "lint-docs"]).unwrap();
match cli.command {
Command::LintDocs(args) => {
assert!(!args.strict, "report-only is the default");
assert!(args.paths.is_empty());
}
other => panic!("expected lint-docs, got {other:?}"),
}
}
}