|
1 | 1 | use crate::*; |
2 | 2 |
|
| 3 | +/// Sort derive traits in a single line |
| 4 | +/// |
| 5 | +/// # Arguments |
| 6 | +/// |
| 7 | +/// - `&str`: The line containing derive attribute |
| 8 | +/// |
| 9 | +/// # Returns |
| 10 | +/// |
| 11 | +/// - `Option<String>`: Sorted line if derive found, None otherwise |
| 12 | +fn sort_derive_in_line(line: &str) -> Option<String> { |
| 13 | + let captures: Captures<'_> = DERIVE_REGEX.captures(line)?; |
| 14 | + let derive_content: &str = captures.get(1)?.as_str(); |
| 15 | + let mut traits: Vec<String> = derive_content |
| 16 | + .split(',') |
| 17 | + .map(|s: &str| s.trim().to_string()) |
| 18 | + .filter(|s: &String| !s.is_empty()) |
| 19 | + .collect(); |
| 20 | + traits.sort_by_key(|a| a.to_lowercase()); |
| 21 | + let sorted_traits: String = traits.join(", "); |
| 22 | + let result: String = line.replace(derive_content, &sorted_traits); |
| 23 | + Some(result) |
| 24 | +} |
| 25 | + |
| 26 | +/// Format derive attributes in a file |
| 27 | +/// |
| 28 | +/// # Arguments |
| 29 | +/// |
| 30 | +/// - `&Path`: Path to the Rust file |
| 31 | +/// |
| 32 | +/// # Returns |
| 33 | +/// |
| 34 | +/// - `Result<bool, std::io::Error>`: True if file was modified, false otherwise |
| 35 | +async fn format_derive_in_file(file_path: &Path) -> Result<bool, std::io::Error> { |
| 36 | + let content: String = read_to_string(file_path)?; |
| 37 | + let lines: std::str::Lines<'_> = content.lines(); |
| 38 | + let mut modified: bool = false; |
| 39 | + let mut new_content: String = String::new(); |
| 40 | + for line in lines { |
| 41 | + let trimmed: &str = line.trim(); |
| 42 | + let new_line: String = if trimmed.starts_with("#[derive(") { |
| 43 | + if let Some(sorted) = sort_derive_in_line(line) { |
| 44 | + if sorted != line { |
| 45 | + modified = true; |
| 46 | + } |
| 47 | + sorted |
| 48 | + } else { |
| 49 | + line.to_string() |
| 50 | + } |
| 51 | + } else { |
| 52 | + line.to_string() |
| 53 | + }; |
| 54 | + new_content.push_str(&new_line); |
| 55 | + new_content.push('\n'); |
| 56 | + } |
| 57 | + if modified { |
| 58 | + write(file_path, new_content)?; |
| 59 | + } |
| 60 | + Ok(modified) |
| 61 | +} |
| 62 | + |
| 63 | +/// Find all Rust files in workspace |
| 64 | +/// |
| 65 | +/// # Arguments |
| 66 | +/// |
| 67 | +/// - `&Path`: Path to Cargo.toml |
| 68 | +/// |
| 69 | +/// # Returns |
| 70 | +/// |
| 71 | +/// - `Result<Vec<PathBuf>, std::io::Error>`: List of Rust file paths |
| 72 | +async fn find_rust_files(manifest_path: &Path) -> Result<Vec<PathBuf>, std::io::Error> { |
| 73 | + let mut files: Vec<PathBuf> = Vec::new(); |
| 74 | + let workspace_root: &Path = manifest_path.parent().unwrap_or(Path::new(".")); |
| 75 | + let src_dir: PathBuf = workspace_root.join("src"); |
| 76 | + if src_dir.exists() { |
| 77 | + find_rust_files_in_dir(&src_dir, &mut files).await?; |
| 78 | + } |
| 79 | + let content: String = read_to_string(manifest_path)?; |
| 80 | + if let Ok(doc) = toml::from_str::<toml::Value>(&content) { |
| 81 | + if let Some(workspace) = doc.get("workspace") { |
| 82 | + if let Some(members) = workspace |
| 83 | + .get("members") |
| 84 | + .and_then(|m: &toml::Value| m.as_array()) |
| 85 | + { |
| 86 | + for member in members { |
| 87 | + if let Some(pattern) = member.as_str() { |
| 88 | + let member_src: PathBuf = workspace_root.join(pattern).join("src"); |
| 89 | + if member_src.exists() { |
| 90 | + find_rust_files_in_dir(&member_src, &mut files).await?; |
| 91 | + } |
| 92 | + } |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | + Ok(files) |
| 98 | +} |
| 99 | + |
| 100 | +/// Recursively find Rust files in directory |
| 101 | +/// |
| 102 | +/// # Arguments |
| 103 | +/// |
| 104 | +/// - `&Path`: Directory to search |
| 105 | +/// - `&mut Vec<PathBuf>`: Vector to collect file paths |
| 106 | +/// |
| 107 | +/// # Returns |
| 108 | +/// |
| 109 | +/// - `Result<(), std::io::Error>`: Success or error |
| 110 | +async fn find_rust_files_in_dir( |
| 111 | + dir: &Path, |
| 112 | + files: &mut Vec<PathBuf>, |
| 113 | +) -> Result<(), std::io::Error> { |
| 114 | + let mut entries = tokio::fs::read_dir(dir).await?; |
| 115 | + while let Some(entry) = entries.next_entry().await? { |
| 116 | + let path: PathBuf = entry.path(); |
| 117 | + if path.is_file() |
| 118 | + && path |
| 119 | + .extension() |
| 120 | + .is_some_and(|ext: &std::ffi::OsStr| ext == "rs") |
| 121 | + { |
| 122 | + files.push(path); |
| 123 | + } else if path.is_dir() { |
| 124 | + Box::pin(find_rust_files_in_dir(&path, files)).await?; |
| 125 | + } |
| 126 | + } |
| 127 | + Ok(()) |
| 128 | +} |
| 129 | + |
| 130 | +/// Format derive attributes in all workspace files |
| 131 | +/// |
| 132 | +/// # Arguments |
| 133 | +/// |
| 134 | +/// - `&str`: Path to Cargo.toml |
| 135 | +/// |
| 136 | +/// # Returns |
| 137 | +/// |
| 138 | +/// - `Result<(), std::io::Error>`: Success or error |
| 139 | +async fn format_derive_attributes(manifest_path: &str) -> Result<(), std::io::Error> { |
| 140 | + let path: &Path = Path::new(manifest_path); |
| 141 | + let files: Vec<PathBuf> = find_rust_files(path).await?; |
| 142 | + let modified_count: Arc<Mutex<usize>> = Arc::new(Mutex::new(0)); |
| 143 | + let mut handles: Vec<tokio::task::JoinHandle<Result<(), std::io::Error>>> = Vec::new(); |
| 144 | + for file in files { |
| 145 | + let counter: Arc<Mutex<usize>> = Arc::clone(&modified_count); |
| 146 | + let handle: tokio::task::JoinHandle<Result<(), std::io::Error>> = |
| 147 | + tokio::spawn(async move { |
| 148 | + if format_derive_in_file(&file).await? { |
| 149 | + let mut count: tokio::sync::MutexGuard<'_, usize> = counter.lock().await; |
| 150 | + *count += 1; |
| 151 | + } |
| 152 | + Ok(()) |
| 153 | + }); |
| 154 | + handles.push(handle); |
| 155 | + } |
| 156 | + for handle in handles { |
| 157 | + handle.await??; |
| 158 | + } |
| 159 | + let count: usize = *modified_count.lock().await; |
| 160 | + if count > 0 { |
| 161 | + println!("Sorted derive attributes in {count} files"); |
| 162 | + } |
| 163 | + Ok(()) |
| 164 | +} |
| 165 | + |
3 | 166 | /// Check if cargo-clippy is installed |
4 | 167 | /// |
5 | 168 | /// # Returns |
@@ -73,6 +236,13 @@ async fn execute_clippy_fix(args: &Args) -> Result<(), std::io::Error> { |
73 | 236 | /// |
74 | 237 | /// - `Result<(), std::io::Error>`: Success or error |
75 | 238 | pub(crate) async fn execute_fmt(args: &Args) -> Result<(), std::io::Error> { |
| 239 | + let manifest_path: String = args |
| 240 | + .manifest_path |
| 241 | + .clone() |
| 242 | + .unwrap_or_else(|| "Cargo.toml".to_string()); |
| 243 | + if !args.check { |
| 244 | + format_derive_attributes(&manifest_path).await?; |
| 245 | + } |
76 | 246 | let mut cmd: Command = Command::new("cargo"); |
77 | 247 | cmd.arg("fmt"); |
78 | 248 | if args.check { |
|
0 commit comments