use clap::Parser;
use std::io::{self, Read};
use crate::{
diagnostics::{Diagnostic, Severity},
draw::draw,
write::Kirei,
};
#[derive(Parser)]
#[command(name = "kirei", about, version)]
#[command(arg_required_else_help = true)]
struct Args {
#[arg(value_name = "PATH", help_heading = "Input")]
input: String,
#[arg(short = 'c', long = "check")]
check: bool,
#[arg(short = 'w', long = "write")]
write: bool,
#[arg(short = 'l', long = "list-different")]
list_different: bool,
#[arg(
long = "stdin-filepath",
value_name = "PATH",
help_heading = "Other options"
)]
stdin_filepath: Option<String>,
}
pub fn run() {
let args = Args::parse();
let (input, filepath) = if args.input == "-" {
let mut buffer = String::new();
io::stdin()
.read_to_string(&mut buffer)
.unwrap_or_else(|e| io_error(&e, None));
(buffer, args.stdin_filepath.as_deref())
} else {
let content = std::fs::read_to_string(&args.input)
.unwrap_or_else(|e| io_error(&e, Some(&args.input)));
(content, Some(&*args.input))
};
let noted = Kirei::default().write(&input);
let has_errors = noted.diagnostics.iter().any(|d| d.level == Severity::Error);
for diagnostic in ¬ed.diagnostics {
eprint!("{}", draw(diagnostic, &input, filepath));
}
let mut formatted = noted.value;
if has_errors {
std::process::exit(1);
}
if !formatted.ends_with('\n') {
formatted.push('\n');
}
if args.check {
if input != formatted {
file_would_be_updated(filepath)
}
} else if args.list_different {
if input != formatted {
eprintln!("{}", filepath.unwrap_or("missing file path"));
std::process::exit(1);
}
} else if args.write
&& let Some(path) = filepath
{
std::fs::write(path, &formatted).unwrap_or_else(|e| io_error(&e, Some(path)));
} else {
print!("{formatted}");
}
}
fn io_error(error: &std::io::Error, filepath: Option<&str>) -> ! {
use std::io::ErrorKind as IoKind;
let message = match (error.kind(), filepath) {
(IoKind::NotFound, Some(path)) => format!("file `{}` does not exist", path),
(IoKind::NotFound, None) => "file does not exist".to_string(),
(IoKind::PermissionDenied, Some(path)) => {
format!("permission denied when accessing `{}`", path)
}
(IoKind::PermissionDenied, None) => "permission denied".to_string(),
(IoKind::AlreadyExists, Some(path)) => format!("file `{}` already exists", path),
(IoKind::AlreadyExists, None) => "file already exists".to_string(),
(IoKind::InvalidInput, _) => "invalid input".to_string(),
(IoKind::InvalidData, _) => "invalid data".to_string(),
(IoKind::TimedOut, _) => "operation timed out".to_string(),
(IoKind::Interrupted, _) => "operation interrupted".to_string(),
(IoKind::UnexpectedEof, Some(path)) => {
format!("unexpected end of file in `{}`", path)
}
(IoKind::UnexpectedEof, None) => "unexpected end of file".to_string(),
(IoKind::BrokenPipe, _) => "broken pipe".to_string(),
(kind, Some(path)) => format!("IO error when accessing `{}`: {:?}", path, kind),
(kind, None) => format!("IO error: {:?}", kind),
};
let diagnostic = Diagnostic::error(message);
eprintln!("{}", draw(&diagnostic, "", filepath));
std::process::exit(1);
}
fn file_would_be_updated(filepath: Option<&str>) -> ! {
let diagnostic = Diagnostic::error("file would be formatted");
eprintln!("{}", draw(&diagnostic, "", filepath));
std::process::exit(1);
}