use std::fs;
use std::path::{Path, PathBuf};
use logicaffeine_language::source_format::format_source;
use crate::commands::require_project_root;
use crate::project::manifest::Manifest;
use crate::ui::{self, CliError};
pub(crate) fn cmd_fmt(paths: Vec<PathBuf>, check: bool) -> Result<(), Box<dyn std::error::Error>> {
let files = if paths.is_empty() {
project_source_files()?
} else {
for p in &paths {
if !p.exists() {
return Err(CliError::new(format!("no such file: {}", p.display())).into());
}
}
paths
};
let mut dirty = Vec::new();
for file in &files {
let source = fs::read_to_string(file)
.map_err(|e| CliError::new(format!("cannot read {}: {e}", file.display())))?;
let formatted = format_source(&source);
if formatted != source {
if !check {
fs::write(file, &formatted)
.map_err(|e| CliError::new(format!("cannot write {}: {e}", file.display())))?;
}
dirty.push(file.clone());
}
}
if check {
if dirty.is_empty() {
ui::info("All files formatted");
return Ok(());
}
for file in &dirty {
println!("{}", file.display());
}
return Err(CliError::with_hint(
format!("{} file(s) need formatting", dirty.len()),
"run `largo fmt` to fix them",
)
.into());
}
ui::info(format!("Formatted {} file(s)", dirty.len()));
Ok(())
}
fn project_source_files() -> Result<Vec<PathBuf>, Box<dyn std::error::Error>> {
let root = require_project_root()?;
let mut files = Vec::new();
collect_sources(&root.join("src"), &mut files)?;
let manifest = Manifest::load(&root)?;
let entry = root.join(&manifest.package.entry);
if entry.exists() && !files.contains(&entry) {
files.push(entry);
}
files.sort();
files.dedup();
Ok(files)
}
fn collect_sources(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), std::io::Error> {
if !dir.exists() {
return Ok(());
}
for entry in fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
collect_sources(&path, files)?;
} else if matches!(
path.extension().and_then(|e| e.to_str()),
Some("lg") | Some("md")
) {
files.push(path);
}
}
Ok(())
}