1use crate::report::{print_check, print_edges, print_related, Format};
2use anyhow::{Context, Result};
3use clap::{Parser, Subcommand, ValueEnum};
4use no_mistakes_core::cli::{init_rayon_threads, resolve_root, JobsArg};
5use no_mistakes_core::queue::{analyze_project, related, RelatedDirection};
6use std::path::PathBuf;
7use std::process::ExitCode;
8
9#[derive(Parser)]
10#[command(author, version, about)]
11pub(crate) struct Cli {
12 #[arg(long, default_value = ".", global = true)]
14 root: PathBuf,
15 #[arg(long, global = true)]
17 tsconfig: Option<PathBuf>,
18 #[arg(long = "filter", global = true)]
20 filters: Vec<String>,
21 #[arg(long, alias = "max-depth", global = true)]
24 depth: Option<usize>,
25 #[arg(long, value_enum, global = true, conflicts_with = "json")]
27 format: Option<Format>,
28 #[arg(long, global = true, conflicts_with = "format")]
30 json: bool,
31 #[arg(long, global = true)]
33 timings: bool,
34 #[command(flatten)]
35 jobs: JobsArg,
36 #[command(subcommand)]
37 command: Command,
38}
39
40#[derive(Subcommand)]
41enum Command {
42 Edges {
44 files: Vec<String>,
46 },
47 Related {
49 #[arg(required = true)]
51 files: Vec<String>,
52 #[arg(long, value_enum, default_value = "both")]
54 direction: DirectionArg,
55 },
56 Check,
58}
59
60#[derive(ValueEnum, Clone, Copy)]
61enum DirectionArg {
62 Deps,
63 Dependents,
64 Both,
65}
66
67pub fn run_cli() -> Result<ExitCode> {
68 let cli = Cli::parse();
69 init_rayon_threads(cli.jobs);
70 let base = std::env::current_dir().context("reading current directory")?;
71 let root = resolve_root(&cli.root, &base);
72 if cli.timings {
73 eprintln!("search: 0.000ms");
74 }
75 let report = analyze_project(&root, cli.tsconfig.as_deref(), &cli.filters)?;
76 let format = cli.format.unwrap_or({
77 if cli.json {
78 Format::Json
79 } else {
80 Format::Human
81 }
82 });
83 match &cli.command {
84 Command::Edges { files } => {
85 print_edges(&report, files, cli.depth, format)?;
86 Ok(ExitCode::SUCCESS)
87 }
88 Command::Related { files, direction } => {
89 let edges = related(&report, files, (*direction).into());
90 print_related(files, &edges, format)?;
91 Ok(ExitCode::SUCCESS)
92 }
93 Command::Check => {
94 print_check(&report, format)?;
95 Ok(if report.check.is_empty() {
96 ExitCode::SUCCESS
97 } else {
98 ExitCode::from(1)
99 })
100 }
101 }
102}
103
104impl From<DirectionArg> for RelatedDirection {
105 fn from(value: DirectionArg) -> Self {
106 match value {
107 DirectionArg::Deps => RelatedDirection::Deps,
108 DirectionArg::Dependents => RelatedDirection::Dependents,
109 DirectionArg::Both => RelatedDirection::Both,
110 }
111 }
112}