use std::fs;
use std::path::Path;
use std::time::Instant;
use clap::ArgMatches;
use hsml::formatter::{FormatOptions, format};
use hsml::parser::{Span, parse::parse};
use super::common::{resolve_ignore_patterns, resolve_path, validate_hsml_extension};
use super::diagnostics::{DimCodes, format_duration, resolve_colors};
use super::walker::walk_hsml_files;
pub fn exec_format(matches: &ArgMatches) -> Result<(), String> {
let path = resolve_path(matches)?;
let check = matches.get_flag("check");
let debug = matches.get_flag("debug");
let (_, dim) = resolve_colors(matches);
let ignore_patterns = resolve_ignore_patterns(matches);
if !path.exists() {
return Err("Path does not exist".to_string());
}
let options = FormatOptions::default();
let mut has_diff = false;
let mut has_errors = false;
let file_count;
let total_start = Instant::now();
if path.is_dir() {
let result = walk_hsml_files(&path, &ignore_patterns)?;
if debug {
let action = if check { "Checking" } else { "Formatting" };
println!(
"{}{action} {} file(s) from {}{}",
dim.0,
result.files.len(),
path.display(),
dim.1
);
}
if !result.errors.is_empty() {
has_errors = true;
for error in &result.errors {
eprintln!("{error}");
}
}
file_count = result.files.len();
for file in &result.files {
if let Err(e) = format_file(file, check, debug, dim, &options, &mut has_diff) {
has_errors = true;
eprintln!("{e}");
}
}
} else if path.is_file() {
validate_hsml_extension(&path)?;
file_count = 1;
format_file(&path, check, debug, dim, &options, &mut has_diff)?;
} else {
return Err("Path must be a file or directory".to_string());
}
if debug {
let timing = format_duration(total_start.elapsed());
let files_word = if file_count == 1 { "file" } else { "files" };
let verb = if check { "checked" } else { "formatted" };
println!(
"\n{}{file_count} {files_word} {verb} in {timing}{}",
dim.0, dim.1
);
}
if has_errors {
Err("Some files could not be processed".to_string())
} else if check && has_diff {
Err(String::new())
} else {
Ok(())
}
}
fn format_file(
path: &Path,
check: bool,
debug: bool,
dim: DimCodes,
options: &FormatOptions,
has_diff: &mut bool,
) -> Result<(), String> {
let content = fs::read_to_string(path)
.map_err(|e| format!("Unable to read file {}: {e}", path.display()))?;
let span = Span::new(&content);
let (rest, ast) = parse(span).map_err(|e| format!("Parse error in {}: {e}", path.display()))?;
if !rest.fragment().is_empty() {
return Err(format!(
"Parse error in {}: unconsumed input at line {}, column {}",
path.display(),
rest.location_line(),
rest.get_column()
));
}
let start = Instant::now();
let formatted = format(&ast, options);
if content == formatted {
if debug {
let timing = format_duration(start.elapsed());
println!("{}{} {timing} (unchanged){}", dim.0, path.display(), dim.1);
}
return Ok(());
}
if check {
if debug {
let timing = format_duration(start.elapsed());
println!(
"{}{} {timing} (needs formatting){}",
dim.0,
path.display(),
dim.1
);
} else {
eprintln!("{}", path.display());
}
*has_diff = true;
} else {
fs::write(path, &formatted)
.map_err(|e| format!("Unable to write file {}: {e}", path.display()))?;
if debug {
let timing = format_duration(start.elapsed());
println!("{}{} {timing}{}", dim.0, path.display(), dim.1);
}
}
Ok(())
}