git-gamble 2.12.1

blend TDD + TCR to make sure to develop the right thing 😌, baby step by baby step πŸ‘ΆπŸ¦Ά
Documentation
use std::fs;
use std::io;
use std::path::PathBuf;
use std::process;
use std::process::Command;
use std::thread;
use std::time::Duration;

use clap::Parser;

use git_gamble::message::Message;

use git_gamble::git_time_keeper::cli::Args;
use git_gamble::git_time_keeper::cli::Cli;
use git_gamble::git_time_keeper::cli::DEFAULT_TIMEOUT_COMMAND;
use git_gamble::git_time_keeper::path_buf_extension::FolderIsNotEmptyExt;

fn main() {
	#[cfg(feature = "with_log")]
	pretty_env_logger::init();

	human_panic::setup_panic!(
		human_panic::metadata!()
			.support("- Issue: https://gitlab.com/pinage404/git-gamble/-/issues/new")
	);

	let cli = Cli::parse();
	match cli {
		Cli::Start(args) => start(args).unwrap(), // TODO remove unwrap and test the bad case scenario
		Cli::Background(args) => background(args).unwrap(), // TODO remove unwrap and test the bad case scenario
		Cli::Stop => stop().unwrap(), // TODO remove unwrap and test the bad case scenario
	}
}

fn start(args: Args) -> Result<(), io::Error> {
	let git_dir = get_git_dir()?;

	let lock_files_folder = git_dir.get_lock_files_folder();

	if lock_files_folder.exists() && lock_files_folder.folder_is_not_empty()? {
		log::info!("Already started, do nothing");
	} else {
		let iteration_duration = args.iteration_duration.to_string();
		let timeout_command = args.timeout_command;
		let child = Command::new(env!("CARGO_BIN_NAME"))
			.args([
				"background",
				"--iteration-duration",
				iteration_duration.as_str(),
				timeout_command.as_str(),
			])
			.spawn()?;

		let process_id = child.id();

		log::info!(
			"[{process_id}] Started with an iteration duration of {}s",
			args.iteration_duration
		);
	}

	Ok(())
}

fn background(args: Args) -> Result<(), io::Error> {
	let git_dir = get_git_dir()?;

	let process_id = process::id();

	let lock_files_folder = git_dir.get_lock_files_folder();
	fs::create_dir_all(lock_files_folder)?;

	let lock_file = git_dir.get_lock_file_path_for(process_id);

	fs::write(&lock_file, "")?;

	log::info!("[{process_id}] Waiting {}s", args.iteration_duration);
	thread::sleep(Duration::from_secs(args.iteration_duration.into()));

	if lock_file.exists() {
		Message::Error("Timed out!".to_string()).display();

		let timeout_command = split_timeout_command(args.timeout_command.as_str());
		Command::new(&timeout_command[0])
			.args(&timeout_command[1..])
			.status()?;

		if lock_file.exists() {
			fs::remove_file(lock_file)?;
		}

		log::info!("[{process_id}] Stepped back");
	} else {
		log::info!("[{process_id}] Stopped while waiting, do nothing");
	}

	Ok(())
}

fn split_timeout_command(maybe_timeout_command: &str) -> Vec<String> {
	let timeout_command = if maybe_timeout_command.trim().is_empty() {
		DEFAULT_TIMEOUT_COMMAND
	} else {
		maybe_timeout_command
	};
	shlex::split(timeout_command).expect("[{process_id}] the timeout command shouldn't be empty") // TODO remove expect and test the bad case scenario
}

fn stop() -> Result<(), io::Error> {
	let git_dir = get_git_dir()?;

	let lock_files_folder = git_dir.get_lock_files_folder();

	if lock_files_folder.exists() && lock_files_folder.folder_is_not_empty()? {
		fs::remove_dir_all(lock_files_folder)?;

		log::info!("Stopped");
	} else {
		log::info!("Cannot be stopped because it is not currently running, do nothing");
	}

	Ok(())
}

fn get_git_dir() -> Result<PathBuf, io::Error> {
	let output = Command::new("git")
		.args(["rev-parse", "--git-dir"])
		.output()?
		.stdout;

	let git_dir = PathBuf::from(String::from_utf8(output).unwrap().trim()); // TODO remove unwrap and test the bad case scenario

	Ok(git_dir)
}

trait GitDirExt {
	fn get_lock_files_folder(&self) -> PathBuf;

	fn get_lock_file_path_for(&self, process_id: u32) -> PathBuf;
}

impl GitDirExt for PathBuf {
	fn get_lock_files_folder(&self) -> PathBuf {
		self.join(env!("CARGO_BIN_NAME").to_string() + ".lock")
	}

	fn get_lock_file_path_for(&self, process_id: u32) -> PathBuf {
		let lock_files_folder = self.get_lock_files_folder();

		lock_files_folder.join(process_id.to_string() + ".pid")
	}
}