use super::*;
mod args;
pub(crate) use args::{
AnalyseArgs, CompletionArgs, DashboardArgs, InitArgs, ListRulesArgs, ReportArgs, SummaryArgs,
};
const HELP_STYLES: styling::Styles = styling::Styles::styled()
.header(
styling::Style::new()
.fg_color(Some(styling::Color::Ansi(styling::AnsiColor::Yellow)))
.bold(),
)
.usage(
styling::Style::new()
.fg_color(Some(styling::Color::Ansi(styling::AnsiColor::Yellow)))
.bold(),
)
.literal(styling::Style::new().fg_color(Some(styling::Color::Ansi(styling::AnsiColor::Green))))
.placeholder(styling::Style::new().dimmed())
.error(
styling::Style::new()
.fg_color(Some(styling::Color::Ansi(styling::AnsiColor::Red)))
.bold(),
)
.valid(styling::Style::new().fg_color(Some(styling::Color::Ansi(styling::AnsiColor::Green))))
.invalid(
styling::Style::new().fg_color(Some(styling::Color::Ansi(styling::AnsiColor::Yellow))),
);
const HELP_TEMPLATE: &str = "\
{before-help}{name} {version}\n\n\
\x1b[1m\x1b[33mUsage:\x1b[0m\n {usage}\n\n\
\x1b[1m\x1b[33mOptions:\x1b[0m\n{options}\n\n\
\x1b[1m\x1b[33mAvailable commands:\x1b[0m\n{subcommands}{after-help}";
#[derive(Parser)]
#[command(
name = "gruff-rs",
version = VERSION,
about = "Rust project quality analysis.",
styles = HELP_STYLES,
help_template = HELP_TEMPLATE,
subcommand_help_heading = "Available commands",
subcommand_value_name = "command",
arg_required_else_help = true,
)]
pub(crate) struct Cli {
#[command(flatten)]
pub(crate) global: GlobalOptions,
#[command(subcommand)]
pub(crate) command: Commands,
}
#[derive(Args, Clone, Debug, Default)]
pub(crate) struct GlobalOptions {
#[arg(long, global = true)]
silent: bool,
#[arg(short = 'q', long, global = true)]
quiet: bool,
#[arg(long, global = true, conflicts_with = "no_ansi")]
ansi: bool,
#[arg(long = "no-ansi", global = true)]
no_ansi: bool,
#[arg(short = 'v', long, action = clap::ArgAction::Count, global = true)]
verbose: u8,
#[arg(short = 'n', long, global = true)]
no_interaction: bool,
}
impl GlobalOptions {
pub(crate) fn writer(&self) -> OutputWriter {
OutputWriter {
silent: self.silent,
quiet: self.quiet,
}
}
pub(crate) fn is_non_interactive(&self) -> bool {
self.no_interaction || self.silent
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct OutputWriter {
silent: bool,
quiet: bool,
}
impl OutputWriter {
pub(crate) fn emit(self, outcome: RunOutcome, body: &str) {
if self.silent {
return;
}
if self.quiet && !outcome.is_failure() {
return;
}
println!("{body}");
}
pub(crate) fn emit_unconditional(self, body: &str) {
if self.silent {
return;
}
println!("{body}");
}
pub(crate) fn is_silent(self) -> bool {
self.silent
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum RunOutcome {
Success,
ThresholdHit,
DiagnosticsFailed,
}
impl RunOutcome {
pub(crate) fn classify(report: &AnalysisReport, fail_on: FailThreshold) -> Self {
if report.diagnostics.iter().any(RunDiagnostic::is_failure) {
return Self::DiagnosticsFailed;
}
if report
.findings
.iter()
.any(|finding| fail_on.is_triggered_by_severity(finding.severity))
{
return Self::ThresholdHit;
}
Self::Success
}
pub(crate) fn exit_code(self) -> ExitCode {
match self {
Self::Success => ExitCode::SUCCESS,
Self::ThresholdHit => ExitCode::from(1),
Self::DiagnosticsFailed => ExitCode::from(2),
}
}
pub(crate) fn is_failure(self) -> bool {
!matches!(self, Self::Success)
}
}
#[derive(Subcommand)]
pub(crate) enum Commands {
Analyse(AnalyseArgs),
Report(ReportArgs),
#[command(alias = "rules")]
ListRules(ListRulesArgs),
Dashboard(DashboardArgs),
Summary(SummaryArgs),
Completion(CompletionArgs),
Init(InitArgs),
}
#[derive(Clone, Copy, Debug, ValueEnum, Serialize, PartialEq, Eq)]
pub(crate) enum OutputFormat {
Text,
Json,
Sarif,
Html,
Markdown,
Github,
Hotspot,
}
impl OutputFormat {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::Text => "text",
Self::Json => "json",
Self::Sarif => "sarif",
Self::Html => "html",
Self::Markdown => "markdown",
Self::Github => "github",
Self::Hotspot => "hotspot",
}
}
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub(crate) enum SummaryFormat {
Text,
Json,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub(crate) enum ReportFormat {
Html,
Json,
}
#[derive(Clone, Copy, Debug, ValueEnum)]
pub(crate) enum RuleListFormat {
Text,
Json,
}
#[derive(Clone, Copy, Debug, ValueEnum, Serialize)]
pub(crate) enum FailThreshold {
None,
Advisory,
Warning,
Error,
}
impl FailThreshold {
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Self::Advisory => "advisory",
Self::Warning => "warning",
Self::Error => "error",
}
}
pub(crate) fn is_triggered_by_severity(self, severity: Severity) -> bool {
match self {
Self::None => false,
Self::Advisory => true,
Self::Warning => severity == Severity::Warning || severity == Severity::Error,
Self::Error => severity == Severity::Error,
}
}
}