helix_core/compiler/tools/
fmt.rs

1use 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}