Skip to main content

cmake_tidy/
main.rs

1mod check;
2mod coverage_excluded;
3mod format;
4mod server;
5
6use std::path::PathBuf;
7use std::process::ExitCode;
8
9use anyhow::Result;
10use clap::{Parser, Subcommand};
11use cmake_tidy_config::RuleSelector;
12use cmake_tidy_parser::parse_file;
13
14#[derive(Debug, Parser)]
15#[command(author, version, about = "CMake linter and formatter")]
16struct Cli {
17    #[command(subcommand)]
18    command: Command,
19}
20
21#[derive(Debug, Subcommand)]
22enum Command {
23    Check {
24        #[arg(long, value_delimiter = ',', action = clap::ArgAction::Append)]
25        select: Vec<RuleSelector>,
26        #[arg(long, value_delimiter = ',', action = clap::ArgAction::Append)]
27        ignore: Vec<RuleSelector>,
28        #[arg(long)]
29        fix: bool,
30        paths: Vec<PathBuf>,
31    },
32    Format {
33        paths: Vec<PathBuf>,
34    },
35    Server,
36    Debug {
37        #[command(subcommand)]
38        command: DebugSubcommand,
39    },
40}
41
42#[derive(Debug, Subcommand)]
43enum DebugSubcommand {
44    Ast { filename: PathBuf },
45}
46
47fn main() -> ExitCode {
48    let cli = Cli::parse();
49
50    let result = match cli.command {
51        Command::Check {
52            select,
53            ignore,
54            fix,
55            paths,
56        } => check::run(&paths, select, ignore, fix).map(ExitStatus::from_has_diagnostics),
57        Command::Format { paths } => format::run(&paths).map(|_| ExitStatus::Success),
58        Command::Server => server::run().map(|()| ExitStatus::Success),
59        Command::Debug { command } => match command {
60            DebugSubcommand::Ast { filename } => debug_ast(&filename),
61        },
62    };
63
64    match result {
65        Ok(ExitStatus::Success) => ExitCode::SUCCESS,
66        Ok(ExitStatus::Diagnostics) => ExitCode::from(1),
67        Err(error) => {
68            eprintln!("error: {error:#}");
69            ExitCode::from(2)
70        }
71    }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75enum ExitStatus {
76    Success,
77    Diagnostics,
78}
79
80impl ExitStatus {
81    const fn from_has_diagnostics(has_diagnostics: bool) -> Self {
82        if has_diagnostics {
83            Self::Diagnostics
84        } else {
85            Self::Success
86        }
87    }
88}
89
90fn debug_ast(filename: &std::path::Path) -> Result<ExitStatus> {
91    let source = coverage_excluded::read_cmake_file(filename)?;
92
93    let parsed = parse_file(&source);
94    println!("{:#?}", parsed.syntax);
95
96    if parsed.errors.is_empty() {
97        return Ok(ExitStatus::Success);
98    }
99
100    eprintln!("parse errors:");
101    for error in &parsed.errors {
102        eprintln!(
103            "- {} [{}..{}]",
104            error.message, error.range.start, error.range.end
105        );
106    }
107
108    Ok(ExitStatus::Diagnostics)
109}