helix_core/compiler/tools/
fmt.rs1use std::path::PathBuf;
2use std::fs;
3use anyhow::{Result, Context};
4pub fn format_files(files: Vec<PathBuf>, check: bool, verbose: bool) -> Result<()> {
5 if files.is_empty() {
6 format_project(check, verbose)
7 } else {
8 format_specific_files(files, check, verbose)
9 }
10}
11fn format_project(check: bool, verbose: bool) -> Result<()> {
12 let project_dir = find_project_root()?;
13 if verbose {
14 println!("šØ Formatting HELIX project:");
15 println!(" Project: {}", project_dir.display());
16 println!(" Check mode: {}", check);
17 }
18 let mut helix_files = Vec::new();
19 find_helix_files(&project_dir, &mut helix_files)?;
20 if helix_files.is_empty() {
21 println!("ā¹ļø No HELIX files found to format.");
22 return Ok(());
23 }
24 println!("š Found {} HELIX files to format", helix_files.len());
25 let mut formatted_count = 0;
26 let mut unchanged_count = 0;
27 for file in helix_files {
28 match format_single_file(&file, check, verbose) {
29 Ok(FormatResult::Formatted) => {
30 formatted_count += 1;
31 if !check {
32 println!("ā
Formatted: {}", file.display());
33 }
34 }
35 Ok(FormatResult::Unchanged) => {
36 unchanged_count += 1;
37 if verbose {
38 println!("ā¹ļø Unchanged: {}", file.display());
39 }
40 }
41 Err(e) => {
42 eprintln!("ā Failed to format {}: {}", file.display(), e);
43 }
44 }
45 }
46 if check {
47 if formatted_count > 0 {
48 println!("ā {} files need formatting", formatted_count);
49 std::process::exit(1);
50 } else {
51 println!("ā
All files are properly formatted");
52 }
53 } else {
54 println!("\nš Formatting Results:");
55 println!(" Formatted: {}", formatted_count);
56 println!(" Unchanged: {}", unchanged_count);
57 }
58 Ok(())
59}
60fn format_specific_files(files: Vec<PathBuf>, check: bool, verbose: bool) -> Result<()> {
61 if verbose {
62 println!("šØ Formatting specific files:");
63 println!(" Files: {}", files.len());
64 println!(" Check mode: {}", check);
65 }
66 let mut formatted_count = 0;
67 let mut unchanged_count = 0;
68 for file in files {
69 if !file.exists() {
70 eprintln!("ā File not found: {}", file.display());
71 continue;
72 }
73 if !file.extension().map_or(false, |ext| ext == "hlx") {
74 eprintln!("ā ļø Skipping non-HELIX file: {}", file.display());
75 continue;
76 }
77 match format_single_file(&file, check, verbose) {
78 Ok(FormatResult::Formatted) => {
79 formatted_count += 1;
80 if !check {
81 println!("ā
Formatted: {}", file.display());
82 }
83 }
84 Ok(FormatResult::Unchanged) => {
85 unchanged_count += 1;
86 if verbose {
87 println!("ā¹ļø Unchanged: {}", file.display());
88 }
89 }
90 Err(e) => {
91 eprintln!("ā Failed to format {}: {}", file.display(), e);
92 }
93 }
94 }
95 if check {
96 if formatted_count > 0 {
97 println!("ā {} files need formatting", formatted_count);
98 std::process::exit(1);
99 } else {
100 println!("ā
All files are properly formatted");
101 }
102 } else {
103 println!("\nš Formatting Results:");
104 println!(" Formatted: {}", formatted_count);
105 println!(" Unchanged: {}", unchanged_count);
106 }
107 Ok(())
108}
109#[derive(Debug)]
110enum FormatResult {
111 Formatted,
112 Unchanged,
113}
114fn format_single_file(
115 file: &PathBuf,
116 check: bool,
117 _verbose: bool,
118) -> Result<FormatResult> {
119 let content = fs::read_to_string(file).context("Failed to read file")?;
120 let formatted_content = format_helix_content(&content)?;
121 if content == formatted_content {
122 return Ok(FormatResult::Unchanged);
123 }
124 if !check {
125 fs::write(file, formatted_content).context("Failed to write formatted content")?;
126 }
127 Ok(FormatResult::Formatted)
128}
129fn format_helix_content(content: &str) -> Result<String> {
130 let mut formatted = String::new();
131 let mut indent_level: i32 = 0;
132 let indent_str = " ";
133 for line in content.lines() {
134 let trimmed = line.trim();
135 if trimmed.is_empty() {
136 formatted.push('\n');
137 continue;
138 }
139 if trimmed.starts_with('}') {
140 indent_level = indent_level.saturating_sub(1);
141 }
142 for _ in 0..indent_level {
143 formatted.push_str(indent_str);
144 }
145 formatted.push_str(trimmed);
146 formatted.push('\n');
147 if trimmed.ends_with('{') {
148 indent_level += 1;
149 }
150 }
151 while formatted.ends_with('\n') {
152 formatted.pop();
153 }
154 Ok(formatted)
155}
156fn find_helix_files(dir: &PathBuf, files: &mut Vec<PathBuf>) -> Result<()> {
157 let entries = fs::read_dir(dir).context("Failed to read directory")?;
158 for entry in entries {
159 let entry = entry.context("Failed to read directory entry")?;
160 let path = entry.path();
161 if path.is_file() {
162 if let Some(extension) = path.extension() {
163 if extension == "hlx" {
164 files.push(path);
165 }
166 }
167 } else if path.is_dir() {
168 if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) {
169 if dir_name == "target" || dir_name == "lib" {
170 continue;
171 }
172 }
173 find_helix_files(&path, files)?;
174 }
175 }
176 Ok(())
177}
178fn find_project_root() -> Result<PathBuf> {
179 let mut current_dir = std::env::current_dir()
180 .context("Failed to get current directory")?;
181 loop {
182 let manifest_path = current_dir.join("project.hlx");
183 if manifest_path.exists() {
184 return Ok(current_dir);
185 }
186 if let Some(parent) = current_dir.parent() {
187 current_dir = parent.to_path_buf();
188 } else {
189 break;
190 }
191 }
192 Err(anyhow::anyhow!("No HELIX project found. Run 'helix init' first."))
193}