1use {
2 clap::Parser,
3 codesort::*,
4 lazy_regex::*,
5 std::{
6 fs,
7 io,
8 path::PathBuf,
9 },
10 termimad::crossterm::style::Stylize,
11};
12
13static EXCLUDED_DIRS: &[&str] = &[".git", "target", "build"];
15
16static EXCLUDING_KEYWORDS: &[&str] = &["repr", "serde", "PartialOrd", "Ord"];
19
20#[derive(Debug, Parser)]
26#[command(about, version)]
27pub struct Args {
28 #[clap(long, default_value = "")]
30 pub include: Vec<String>,
31
32 #[clap(long, default_value = "")]
34 pub exclude: Vec<String>,
35
36 pub path: PathBuf,
38}
39
40pub fn get_all_rust_files(
41 root: PathBuf,
42 include: &[String],
43 exclude: &[String],
44) -> io::Result<Vec<PathBuf>> {
45 let mut files = Vec::new();
46 if !root.is_dir() {
49 files.push(root);
50 return Ok(files);
51 }
52 let mut dirs = vec![root];
53 while let Some(dir) = dirs.pop() {
54 for entry in fs::read_dir(dir)? {
55 let path = entry?.path();
56 let Some(file_name) = path.file_name().and_then(|s| s.to_str()) else {
57 continue;
58 };
59 if path.is_dir() {
60 if file_name.starts_with('.') {
61 continue;
62 }
63 if exclude.iter().any(|ex| ex == file_name) {
64 eprintln!("{} {:?}", "Excluded".yellow(), path);
65 continue;
66 }
67 if EXCLUDED_DIRS.contains(&file_name) {
68 if !include.iter().any(|inc| inc == file_name) {
69 eprintln!("{} {:?}", "Excluded".yellow(), path);
70 continue;
71 }
72 }
73 dirs.push(path.to_path_buf());
74 continue;
75 }
76 if let Some(ext) = path.extension() {
77 if ext.to_str() == Some("rs") {
78 files.push(path.to_path_buf());
79 }
80 }
81 }
82 }
83 Ok(files)
84}
85
86fn main() -> CsResult<()> {
87 let start = std::time::Instant::now();
88 let args = Args::parse();
89 let files = get_all_rust_files(args.path, &args.include, &args.exclude)?;
90 eprintln!("Found {} rust files", files.len());
91 let mut sorted_enum_count = 0;
92 let mut ok_files_count = 0;
93 let mut invalid_files_count = 0;
94 let mut incomplete_files_count = 0;
95 let mut excluded_enums_count = 0;
96 let mut modified_files_count = 0;
97 let mut empty_files_count = 0;
98 for file in &files {
99 let loc_list = LocList::read_file(file, Language::Rust);
100 let mut loc_list = match loc_list {
101 Ok(loc_list) => loc_list,
102 Err(e) => {
103 eprintln!("{} in {}: {:?}", "ERROR".red(), file.display(), e);
104 invalid_files_count += 1;
105 continue;
106 }
107 };
108 if !loc_list.has_content() {
109 empty_files_count += 1;
110 continue;
111 }
112 if !loc_list.is_complete() {
113 eprintln!(
114 "skipping {} ({})",
115 file.display(),
116 "not consistent enough".yellow()
117 );
118 incomplete_files_count += 1;
119 continue;
120 }
121 let mut modified = false;
122 let mut line_idx = 0;
123 ok_files_count += 1;
124 while line_idx + 2 < loc_list.len() {
125 let loc = &loc_list.locs[line_idx];
126 if !loc.starts_normal {
127 line_idx += 1;
128 continue;
129 }
130 let content = &loc.content;
131 let Some((_, name)) =
132 regex_captures!(r"^[\s\w()]*\benum\s+([^({]+)\s+\{\s**$", content)
133 else {
134 line_idx += 1;
135 continue;
136 };
137
138 let whole_enum_range = loc_list
141 .block_range_of_line_number(LineNumber::from_index(line_idx))
142 .unwrap();
143 let whole_enum_range = loc_list.trimmed_range(whole_enum_range);
144 let excluding_keyword = EXCLUDING_KEYWORDS.iter().find(|&keyword| {
145 loc_list.locs[whole_enum_range.start.to_index()..line_idx]
146 .iter()
147 .any(|loc| loc.sort_key.contains(keyword))
148 });
149 if let Some(excluding_keyword) = excluding_keyword {
150 eprintln!("skipping enum {} ({})", name, excluding_keyword.yellow());
151 excluded_enums_count += 1;
152 line_idx = whole_enum_range.end.to_index() + 1;
153 continue;
154 }
155 loc_list.print_range_debug(
156 &format!(" sorting enum {} ", name.blue()),
157 whole_enum_range,
158 );
159 let range = loc_list.range_around_line_index(line_idx + 1).unwrap();
160 loc_list.sort_range(range).unwrap();
161 line_idx = range.end.to_index() + 2;
162 sorted_enum_count += 1;
163 modified = true;
164 }
165 if modified {
166 loc_list.write_file(file)?;
167 eprintln!("wrote {}", file.display());
168 modified_files_count += 1;
169 }
170 }
171 eprintln!("\nDone in {:.3}s\n", start.elapsed().as_secs_f32());
172 eprintln!("I analyzed {} files", files.len());
173 let mut problems = Vec::new();
174 if empty_files_count > 0 {
175 problems.push(format!("{} empty files", empty_files_count));
176 }
177 if incomplete_files_count > 0 {
178 problems.push(format!("{} incomplete files", incomplete_files_count));
179 }
180 if invalid_files_count > 0 {
181 problems.push(format!("{} invalid files", invalid_files_count));
182 }
183 if problems.is_empty() {
184 eprintln!("All {} files were ok", ok_files_count);
185 } else {
186 eprintln!(
187 "{} files were OK but I encountered {}",
188 ok_files_count,
189 problems.join(", ")
190 );
191 }
192 if excluded_enums_count > 0 {
193 eprintln!(
194 "I excluded {} enums whose annotation contained excluding keywords",
195 excluded_enums_count
196 );
197 }
198 eprintln!(
199 "I sorted {} enums in {} files",
200 sorted_enum_count, modified_files_count
201 );
202 Ok(())
203}