#![expect(clippy::as_conversions)]
use crate::eyre::eyre;
use clap::Parser;
use color_eyre::Section;
use color_eyre::eyre::{self, Result};
use colored::{ColoredString, Colorize};
use command_run::Command;
use notify::{Event, EventKind, RecursiveMode, Watcher};
use std::{ffi::OsStr, fs, sync::mpsc::Receiver, time::Duration};
use std::{path::Path, sync::mpsc};
static ERRFILE: &str = ".checkpoint.error";
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short, long, value_name = "filetype")]
filetype: String,
name: Vec<String>,
}
fn clear() {
print!("{esc}[2J{esc}[1;1H", esc = 27 as char);
}
fn log(message: &ColoredString) {
let prefix = "🏁 CHECKPOINT: ".blue().bold();
print!("{prefix}");
println!("{message}");
}
fn main() -> Result<()> {
color_eyre::install()?;
let cli = Cli::parse();
let extension = cli.filetype;
let program = cli
.name
.first()
.ok_or_else(|| eyre!("Missing argument: COMMAND"))?;
let args = cli.name.get(1..).ok_or_else(|| eyre!("no program arg"))?;
let (tx, rx) = mpsc::channel::<notify::Result<Event>>();
let mut watcher = notify::recommended_watcher(tx)?;
watcher.watch(Path::new("."), RecursiveMode::Recursive)?;
loop {
clear(); log(&"Running command...".white().bold());
let mut command = Command::with_args(program, args);
command.log_command = false;
let output = command.run();
if output.is_err() {
log(&"Error!".red().bold());
create_errfile()?;
} else {
if fs::exists(ERRFILE)? {
log(&"Autosaving!".green().bold());
#[allow(clippy::expect_used)]
commit("CHECKPOINT SAVED!")?;
rm_errfile()?;
} else {
log(&"OK".green().bold());
}
}
log(&"Monitoring...".white().bold());
blockforfile(&rx, &extension);
}
}
fn blockforfile(rx: &Receiver<Result<Event, notify::Error>>, extension: &str) {
loop {
match rx.recv_timeout(std::time::Duration::from_millis(100)) {
Ok(Ok(Event {
kind: EventKind::Modify(_),
paths,
..
})) if paths.first().map(|p| p.extension()) == Some(Some(OsStr::new(extension))) => {
break;
}
_ => {
}
}
}
while rx.recv_timeout(Duration::from_millis(100)).is_ok() {
}
}
fn commit(msg: &str) -> Result<()> {
let mut command = Command::with_args("git", ["commit", "-am", msg]);
command.log_command = false;
if command.run().is_ok() {
Ok(())
} else {
log(&"Fatal error!".red().bold());
Err(eyre!("Git command error.")
.with_suggestion(|| "Consider manually removing the `.checkpoint.error` file"))
}
}
fn create_errfile() -> Result<()> {
let mut command = Command::with_args("touch", [ERRFILE]);
command.log_command = false;
command.run()?;
Ok(())
}
fn rm_errfile() -> Result<()> {
let mut command = Command::with_args("rm", [ERRFILE]);
command.log_command = false;
command.run()?;
Ok(())
}