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}