use std::io::Read;
use std::process::ExitCode;
fn format_source(path: &str, src: &str) -> Result<String, String> {
match lemon::parse(src) {
Ok(tree) => Ok(lemon::format(&tree)),
Err(e) => Err(format!("{path}:{}:{}: {}", e.line, e.col, e.message)),
}
}
fn main() -> ExitCode {
let mut args = std::env::args().skip(1);
if args.next().as_deref() != Some("fmt") {
eprintln!("usage: lemon fmt [-w] [file...] (no file = read stdin)");
return ExitCode::FAILURE;
}
let mut write = false;
let mut files: Vec<String> = Vec::new();
for a in args {
match a.as_str() {
"-w" | "--write" => write = true,
_ => files.push(a),
}
}
if files.is_empty() {
let mut src = String::new();
if std::io::stdin().read_to_string(&mut src).is_err() {
eprintln!("lemon: failed to read stdin");
return ExitCode::FAILURE;
}
return match format_source("<stdin>", &src) {
Ok(out) => {
println!("{out}");
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("{e}");
ExitCode::FAILURE
}
};
}
let mut failed = false;
for path in &files {
let src = match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
eprintln!("lemon: {path}: {e}");
failed = true;
continue;
}
};
match format_source(path, &src) {
Ok(out) if write => {
if let Err(e) = std::fs::write(path, format!("{out}\n")) {
eprintln!("lemon: {path}: {e}");
failed = true;
}
}
Ok(out) => println!("{out}"),
Err(e) => {
eprintln!("{e}");
failed = true;
}
}
}
if failed {
ExitCode::FAILURE
} else {
ExitCode::SUCCESS
}
}
#[cfg(test)]
mod tests {
use super::format_source;
#[test]
fn formats_valid_and_reports_errors() {
assert_eq!(
format_source("x.lemon", "rebalance(is_largest(sma(close,5),10))").unwrap(),
"rebalance(\n is_largest(\n sma(close, 5),\n 10\n )\n)"
);
let err = format_source("x.lemon", "sma(close,").unwrap_err();
assert!(err.starts_with("x.lemon:"), "got: {err}");
}
}