use std::path::PathBuf;
use clap::Parser;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AutocorrectMode {
Off,
Safe,
All,
}
#[derive(Parser, Debug)]
#[command(
name = "rrubocop",
version = crate::baseline::VERSION,
about = crate::baseline::ABOUT
)]
pub struct Args {
#[arg(default_value = ".")]
pub paths: Vec<PathBuf>,
#[arg(short, long, value_name = "FILE")]
pub config: Option<PathBuf>,
#[arg(
short,
long,
default_value = "progress",
value_parser = ["progress", "text", "json", "github", "quiet", "files", "emacs", "simple"]
)]
pub format: String,
#[arg(long, value_delimiter = ',')]
pub only: Vec<String>,
#[arg(long, value_delimiter = ',')]
pub except: Vec<String>,
#[arg(long, overrides_with = "no_color")]
pub color: bool,
#[arg(long, overrides_with = "color")]
pub no_color: bool,
#[arg(long)]
pub debug: bool,
#[arg(long)]
pub list_cops: bool,
#[arg(long)]
pub list_autocorrectable_cops: bool,
#[arg(long, value_name = "PATH")]
pub stdin: Option<PathBuf>,
#[arg(
short = 'C',
long,
value_name = "FLAG",
default_value = "true",
value_parser = ["true", "false"]
)]
pub cache: String,
#[arg(long, value_name = "SEVERITY", default_value = "convention")]
pub fail_level: String,
#[arg(short = 'F', long, value_name = "N", default_value_t = 0)]
pub fail_fast: u32,
#[arg(long)]
pub force_exclusion: bool,
#[arg(short = 'L', long)]
pub list_target_files: bool,
#[arg(short = 'D', long, overrides_with = "no_display_cop_names")]
pub display_cop_names: bool,
#[arg(long = "no-display-cop-names", overrides_with = "display_cop_names")]
pub no_display_cop_names: bool,
#[arg(short = 'P', long)]
pub parallel: bool,
#[arg(short = 'r', long = "require")]
pub require_libs: Vec<String>,
#[arg(long)]
pub ignore_disable_comments: bool,
#[arg(long)]
pub force_default_config: bool,
#[arg(long, value_name = "VERSION")]
pub rubocop_version: Option<String>,
#[arg(short = 'a', long = "autocorrect")]
pub autocorrect: bool,
#[arg(short = 'A', long = "autocorrect-all")]
pub autocorrect_all: bool,
#[arg(long)]
pub mcp: bool,
}
impl Args {
pub fn parse_cli() -> Self {
Self::parse_from(normalize_fail_fast(std::env::args_os()))
}
pub fn autocorrect_mode(&self) -> AutocorrectMode {
if self.autocorrect_all {
AutocorrectMode::All
} else if self.autocorrect {
AutocorrectMode::Safe
} else {
AutocorrectMode::Off
}
}
pub fn cache_read_enabled(&self) -> bool {
self.cache != "false"
}
pub fn color_force(&self) -> Option<bool> {
if self.color {
Some(true)
} else if self.no_color {
Some(false)
} else {
None
}
}
pub fn display_cop_names_override(&self) -> Option<bool> {
if self.display_cop_names {
Some(true)
} else if self.no_display_cop_names {
Some(false)
} else {
None
}
}
}
fn is_fail_fast_n(s: &str) -> bool {
!s.is_empty() && s.bytes().all(|b| b.is_ascii_digit())
}
fn push_fail_fast_arg(out: &mut Vec<std::ffi::OsString>, next: Option<std::ffi::OsString>) {
match next {
Some(n) if is_fail_fast_n(&n.to_string_lossy()) => out.push(n),
Some(n) => {
out.push(std::ffi::OsString::from("1"));
out.push(n);
}
None => out.push(std::ffi::OsString::from("1")),
}
}
fn normalize_fail_fast<I>(raw: I) -> Vec<std::ffi::OsString>
where
I: IntoIterator<Item = std::ffi::OsString>,
{
let mut out = Vec::new();
let mut iter = raw.into_iter();
if let Some(bin) = iter.next() {
out.push(bin);
}
while let Some(arg) = iter.next() {
let bare = {
let s = arg.to_string_lossy();
s == "-F" || s == "--fail-fast"
};
out.push(arg);
if bare {
push_fail_fast_arg(&mut out, iter.next());
}
}
out
}
#[cfg(test)]
mod tests {
use super::{normalize_fail_fast, Args};
use clap::Parser;
use std::ffi::OsString;
fn os(args: &[&str]) -> Vec<OsString> {
args.iter().map(OsString::from).collect()
}
fn s(args: Vec<OsString>) -> Vec<String> {
args.into_iter()
.map(|a| a.to_string_lossy().into_owned())
.collect()
}
#[test]
fn bare_f_inserts_one_before_path() {
assert_eq!(
s(normalize_fail_fast(os(&["rr", "-F", "app"]))),
vec!["rr", "-F", "1", "app"]
);
}
#[test]
fn f_with_number_unchanged() {
assert_eq!(
s(normalize_fail_fast(os(&["rr", "-F", "3", "."]))),
vec!["rr", "-F", "3", "."]
);
}
#[test]
fn bare_f_at_end() {
assert_eq!(
s(normalize_fail_fast(os(&["rr", "-F"]))),
vec!["rr", "-F", "1"]
);
}
#[test]
fn cache_flag_defaults_on_and_accepts_false() {
let on = Args::parse_from(["rr"]);
assert!(on.cache_read_enabled());
let off = Args::parse_from(["rr", "--cache", "false"]);
assert!(!off.cache_read_enabled());
let short = Args::parse_from(["rr", "-C", "false"]);
assert!(!short.cache_read_enabled());
}
}