use clap::{Args, ValueEnum};
use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FixMode {
#[default]
Check,
CheckFix,
Format,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, ValueEnum)]
pub enum FailOn {
#[default]
Any,
Warning,
Error,
Never,
}
#[derive(Args, Debug)]
pub struct SharedCliArgs {
#[arg(short, long, help = "Disable specific rules (comma-separated)")]
pub disable: Option<String>,
#[arg(
short,
long,
visible_alias = "rules",
help = "Enable only specific rules (comma-separated)"
)]
pub enable: Option<String>,
#[arg(long, help = "Extend the list of enabled rules (additive with config)")]
pub extend_enable: Option<String>,
#[arg(long, help = "Extend the list of disabled rules (additive with config)")]
pub extend_disable: Option<String>,
#[arg(long, help = "Only allow these rules to be fixed (comma-separated)")]
pub fixable: Option<String>,
#[arg(long, help = "Prevent these rules from being fixed (comma-separated)")]
pub unfixable: Option<String>,
#[arg(long, help = "Exclude specific files or directories (comma-separated glob patterns)")]
pub exclude: Option<String>,
#[arg(long, help = "Disable all exclude patterns")]
pub no_exclude: bool,
#[arg(
long,
help = "Include only specific files or directories (comma-separated glob patterns)"
)]
pub include: Option<String>,
#[arg(
long,
num_args(0..=1),
require_equals(true),
default_missing_value = "true",
help = "Respect .gitignore files when scanning directories (does not apply to explicitly provided paths)"
)]
pub respect_gitignore: Option<bool>,
#[arg(short, long, help = "Print diagnostics, but suppress summary lines")]
pub quiet: bool,
#[arg(long, help = "Show absolute file paths in output instead of relative paths")]
pub show_full_path: bool,
#[arg(long, help = "Filename to use when reading from stdin (e.g., README.md)")]
pub stdin_filename: Option<String>,
#[arg(long, help = "Output diagnostics to stderr instead of stdout")]
pub stderr: bool,
#[arg(long, help = "Disable caching (re-check all files)")]
pub no_cache: bool,
#[arg(
long,
help = "Directory to store cache files (default: .rumdl_cache, or $RUMDL_CACHE_DIR, or cache-dir in config)"
)]
pub cache_dir: Option<String>,
}
#[derive(Args, Debug)]
pub struct CheckArgs {
#[arg(required = false)]
pub paths: Vec<String>,
#[arg(short, long, default_value = "false")]
pub fix: bool,
#[arg(
long,
alias = "dry-run",
help = "Show diff of what would be fixed instead of fixing files"
)]
pub diff: bool,
#[arg(
long,
hide = true,
help = "Exit with code 1 if any formatting changes would be made (for CI)"
)]
pub check: bool,
#[arg(short, long, default_value = "false")]
pub list_rules: bool,
#[command(flatten)]
pub shared: SharedCliArgs,
#[arg(short, long, help = "Show detailed output")]
pub verbose: bool,
#[arg(long, help = "Show profiling information")]
pub profile: bool,
#[arg(long, help = "Show statistics summary of rule violations")]
pub statistics: bool,
#[arg(long, short = 'o', default_value_t, value_enum, hide = true)]
pub output: Output,
#[arg(long, value_enum)]
pub output_format: Option<OutputFormat>,
#[arg(
long,
value_enum,
help = "Markdown flavor to use: standard (also accepts gfm/github/commonmark), mkdocs, mdx, quarto, obsidian, or kramdown"
)]
pub flavor: Option<Flavor>,
#[arg(long, help = "Read from stdin instead of files")]
pub stdin: bool,
#[arg(short, long, help = "Suppress diagnostics and summaries")]
pub silent: bool,
#[arg(short, long, help = "Run in watch mode by re-running whenever files change")]
pub watch: bool,
#[arg(long, help = "Enforce exclude patterns even for explicitly specified files")]
pub force_exclude: bool,
#[arg(
long,
value_enum,
default_value_t,
help = "Exit code behavior: 'any' (default) exits 1 on any violation, 'warning' on warning+error, 'error' only on errors, 'never' always exits 0"
)]
pub fail_on: FailOn,
#[arg(skip)]
pub fix_mode: FixMode,
#[arg(skip)]
pub fail_on_mode: FailOn,
}
#[derive(Args, Debug)]
pub struct FmtArgs {
#[arg(required = false)]
pub paths: Vec<String>,
#[arg(
long,
alias = "dry-run",
help = "Show diff of what would be formatted instead of rewriting files"
)]
pub diff: bool,
#[arg(long, help = "Exit with code 1 if any formatting changes would be made (for CI)")]
pub check: bool,
#[arg(short, long, hide = true, default_value = "false")]
pub list_rules: bool,
#[command(flatten)]
pub shared: SharedCliArgs,
#[arg(short, long, help = "Show detailed formatter output")]
pub verbose: bool,
#[arg(long, hide = true)]
pub profile: bool,
#[arg(long, hide = true)]
pub statistics: bool,
#[arg(long, short = 'o', default_value_t, value_enum, hide = true)]
pub output: Output,
#[arg(long, value_enum)]
pub output_format: Option<OutputFormat>,
#[arg(
long,
value_enum,
help = "Markdown flavor to use while formatting: standard (also accepts gfm/github/commonmark), mkdocs, mdx, quarto, obsidian, or kramdown"
)]
pub flavor: Option<Flavor>,
#[arg(long, help = "Read Markdown from stdin instead of files")]
pub stdin: bool,
#[arg(
short,
long,
help = "Suppress diagnostics and summaries; only formatted content is emitted in stdin/stdout mode"
)]
pub silent: bool,
#[arg(short, long, help = "Re-run formatting whenever files change")]
pub watch: bool,
#[arg(long, hide = true)]
pub force_exclude: bool,
#[arg(long, value_enum, default_value_t, hide = true)]
pub fail_on: FailOn,
}
impl From<FmtArgs> for CheckArgs {
fn from(args: FmtArgs) -> Self {
Self {
paths: args.paths,
fix: false,
diff: args.diff,
check: args.check,
list_rules: args.list_rules,
shared: args.shared,
verbose: args.verbose,
profile: args.profile,
statistics: args.statistics,
output: args.output,
output_format: args.output_format,
flavor: args.flavor,
stdin: args.stdin,
silent: args.silent,
watch: args.watch,
force_exclude: args.force_exclude,
fail_on: args.fail_on,
fix_mode: FixMode::default(),
fail_on_mode: FailOn::default(),
}
}
}
impl Deref for CheckArgs {
type Target = SharedCliArgs;
fn deref(&self) -> &Self::Target {
&self.shared
}
}
impl DerefMut for CheckArgs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.shared
}
}
impl Deref for FmtArgs {
type Target = SharedCliArgs;
fn deref(&self) -> &Self::Target {
&self.shared
}
}
impl DerefMut for FmtArgs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.shared
}
}
#[derive(Clone, Debug, Default, ValueEnum)]
pub enum Output {
#[default]
Text,
Json,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum OutputFormat {
Text,
Full,
Concise,
Grouped,
Json,
JsonLines,
#[value(name = "github")]
GitHub,
#[value(name = "gitlab")]
GitLab,
Pylint,
Azure,
Sarif,
Junit,
}
impl From<OutputFormat> for rumdl_lib::output::OutputFormat {
fn from(format: OutputFormat) -> Self {
match format {
OutputFormat::Text => Self::Text,
OutputFormat::Full => Self::Full,
OutputFormat::Concise => Self::Concise,
OutputFormat::Grouped => Self::Grouped,
OutputFormat::Json => Self::Json,
OutputFormat::JsonLines => Self::JsonLines,
OutputFormat::GitHub => Self::GitHub,
OutputFormat::GitLab => Self::GitLab,
OutputFormat::Pylint => Self::Pylint,
OutputFormat::Azure => Self::Azure,
OutputFormat::Sarif => Self::Sarif,
OutputFormat::Junit => Self::Junit,
}
}
}
#[derive(Clone, Copy, Debug, ValueEnum)]
#[value(rename_all = "lower")]
pub enum Flavor {
#[value(aliases(["gfm", "github", "commonmark"]))]
Standard,
MkDocs,
#[allow(clippy::upper_case_acronyms)]
MDX,
#[value(aliases(["qmd", "rmd", "rmarkdown"]))]
Quarto,
Obsidian,
#[value(alias("jekyll"))]
Kramdown,
}
impl From<Flavor> for rumdl_lib::config::MarkdownFlavor {
fn from(flavor: Flavor) -> Self {
match flavor {
Flavor::Standard => Self::Standard,
Flavor::MkDocs => Self::MkDocs,
Flavor::MDX => Self::MDX,
Flavor::Quarto => Self::Quarto,
Flavor::Obsidian => Self::Obsidian,
Flavor::Kramdown => Self::Kramdown,
}
}
}