1use std::path::PathBuf;
7
8use anyhow::Result;
9use console::style;
10
11use crate::cache::Cache;
12
13#[derive(Debug, Clone)]
15pub struct CheckOptions {
16 pub file: PathBuf,
18 pub cache: PathBuf,
20}
21
22pub fn execute_check(options: CheckOptions) -> Result<()> {
24 let cache_data = Cache::from_json(&options.cache)?;
25
26 let file_str = options.file.to_string_lossy().to_string();
28 if file_str == "." {
29 return show_all_constraints(&cache_data);
30 }
31
32 let file_entry = cache_data
34 .files
35 .get(&file_str)
36 .or_else(|| cache_data.files.get(&format!("./{}", file_str)))
37 .or_else(|| {
38 let stripped = file_str.strip_prefix("./").unwrap_or(&file_str);
39 cache_data.files.get(stripped)
40 });
41
42 if let Some(file_entry) = file_entry {
43 println!("{} File found in cache", style("✓").green());
44 println!(" Path: {}", file_entry.path);
45 println!(" Lines: {}", file_entry.lines);
46 println!(" Language: {:?}", file_entry.language);
47
48 if let Some(stability) = &file_entry.stability {
49 println!(" Stability: {:?}", stability);
50 }
51
52 if !file_entry.ai_hints.is_empty() {
53 println!(" AI hints: {}", file_entry.ai_hints.join(", "));
54 }
55
56 if let Some(ref constraints) = cache_data.constraints {
58 let file_constraints = constraints
59 .by_file
60 .get(&file_entry.path)
61 .or_else(|| constraints.by_file.get(&format!("./{}", file_entry.path)))
62 .or_else(|| {
63 let stripped = file_entry
64 .path
65 .strip_prefix("./")
66 .unwrap_or(&file_entry.path);
67 constraints.by_file.get(stripped)
68 });
69
70 if let Some(file_constraints) = file_constraints {
71 if let Some(mutation) = &file_constraints.mutation {
72 println!(" Lock level: {:?}", mutation.level);
73 if mutation.requires_approval {
74 println!(" {} Requires approval", style("⚠").yellow());
75 }
76 if mutation.requires_tests {
77 println!(" {} Requires tests", style("⚠").yellow());
78 }
79 if mutation.requires_docs {
80 println!(" {} Requires documentation", style("⚠").yellow());
81 }
82 }
83 }
84 }
85 } else {
86 eprintln!(
87 "{} File not in cache: {}",
88 style("✗").red(),
89 options.file.display()
90 );
91 }
92
93 Ok(())
94}
95
96fn show_all_constraints(cache_data: &Cache) -> Result<()> {
98 let constraints = match &cache_data.constraints {
99 Some(c) => c,
100 None => {
101 println!("{} No constraints found in cache", style("•").dim());
102 return Ok(());
103 }
104 };
105
106 if constraints.by_file.is_empty() {
107 println!("{} No file constraints defined", style("•").dim());
108 return Ok(());
109 }
110
111 println!("{} Files with constraints:\n", style("→").cyan());
112
113 let mut by_level: std::collections::HashMap<String, Vec<&String>> =
115 std::collections::HashMap::new();
116
117 for (path, file_constraint) in &constraints.by_file {
118 if let Some(ref mutation) = file_constraint.mutation {
119 let level = format!("{:?}", mutation.level);
120 by_level.entry(level).or_default().push(path);
121 }
122 }
123
124 let level_order = [
126 "Frozen",
127 "Restricted",
128 "ApprovalRequired",
129 "TestsRequired",
130 "DocsRequired",
131 "ReviewRequired",
132 "Normal",
133 "Experimental",
134 ];
135
136 for level in level_order {
137 if let Some(files) = by_level.get(level) {
138 let color = match level {
139 "Frozen" => style(level).red().bold(),
140 "Restricted" => style(level).red(),
141 "ApprovalRequired" | "TestsRequired" | "DocsRequired" | "ReviewRequired" => {
142 style(level).yellow()
143 }
144 _ => style(level).dim(),
145 };
146 println!(" {} ({} files)", color, files.len());
147 for path in files.iter().take(10) {
148 println!(" {}", path);
149 }
150 if files.len() > 10 {
151 println!(" ... and {} more", files.len() - 10);
152 }
153 println!();
154 }
155 }
156
157 Ok(())
158}