#![allow(dead_code)]
use std::path::{Path, PathBuf};
use std::{env, fs};
use std::os::unix::fs::symlink as symlink_unix;
use anyhow::{bail, Context, Result};
use git2::{Repository, RepositoryOpenFlags as Flags};
const ERR_CURRENT_DIR: &str = "Failed to get current directory";
#[derive(Debug, Clone)]
pub struct Filesystem {
git_ai_hook_bin_path: PathBuf,
git_hooks_path: PathBuf
}
#[derive(Debug, Clone)]
pub struct File {
path: PathBuf
}
impl File {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn exists(&self) -> bool {
self.path.exists()
}
pub fn delete(&self) -> Result<()> {
log::debug!("Removing file at {}", self);
fs::remove_file(&self.path).with_context(|| format!("Failed to remove file at {}", self))
}
pub fn symlink(&self, target: &File) -> Result<()> {
log::debug!("Symlinking {} to {}", target, self);
symlink_unix(&target.path, &self.path).with_context(|| format!("Failed to symlink {} to {}", target, self))
}
pub fn relative_path(&self) -> Result<Dir> {
let current_dir = env::current_dir().context(ERR_CURRENT_DIR)?;
let relative = self
.path
.strip_prefix(¤t_dir)
.with_context(|| format!("Failed to strip prefix from {}", self.path.display()))?;
Ok(Dir::new(relative.to_path_buf()))
}
pub fn parent(&self) -> Dir {
Dir::new(self.path.parent().unwrap_or(Path::new("")).to_path_buf())
}
}
impl From<&File> for Dir {
fn from(file: &File) -> Self {
file.parent()
}
}
impl std::fmt::Display for File {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let path = self.relative_path().unwrap_or_else(|_| self.into());
write!(f, "{}", path.path.display())
}
}
impl From<File> for Result<File> {
fn from(file: File) -> Result<File> {
Ok(file)
}
}
#[derive(Debug, Clone)]
pub struct Dir {
path: PathBuf
}
impl Dir {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn exists(&self) -> bool {
self.path.exists()
}
pub fn create_dir_all(&self) -> Result<()> {
log::debug!("Creating directory at {}", self);
fs::create_dir_all(&self.path).with_context(|| format!("Failed to create directory at {}", self))
}
pub fn relative_path(&self) -> Result<Self> {
let current_dir = env::current_dir().context(ERR_CURRENT_DIR)?;
let relative = self
.path
.strip_prefix(¤t_dir)
.with_context(|| format!("Failed to strip prefix from {}", self.path.display()))?;
Ok(Self::new(relative.to_path_buf()))
}
}
impl std::fmt::Display for Dir {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path.display())
}
}
impl From<Dir> for Result<Dir> {
fn from(dir: Dir) -> Result<Dir> {
Ok(dir)
}
}
impl Filesystem {
pub fn new() -> Result<Self> {
let current_dir = env::current_dir().context(ERR_CURRENT_DIR)?;
let git_ai_bin_path = env::current_exe().context("Failed to get current executable")?;
let repo = Repository::open_ext(¤t_dir, Flags::empty(), Vec::<&Path>::new())
.with_context(|| format!("Failed to open repository at {}", current_dir.display()))?;
let git_path = {
let mut path = repo.path().to_path_buf();
if path.is_relative() {
path = current_dir.join(path);
}
path
};
let git_ai_hook_bin_path = {
let hook_path = git_ai_bin_path
.parent()
.with_context(|| format!("Failed to get parent directory of {}", git_ai_bin_path.display()))?
.join("git-ai-hook");
if !hook_path.exists() {
bail!("Hook binary not found at {}", hook_path.display());
}
hook_path
};
Ok(Self {
git_ai_hook_bin_path,
git_hooks_path: git_path.join("hooks")
})
}
pub fn git_ai_hook_bin_path(&self) -> Result<File> {
Ok(File::new(self.git_ai_hook_bin_path.clone()))
}
pub fn git_hooks_path(&self) -> Dir {
Dir::new(self.git_hooks_path.clone())
}
pub fn prepare_commit_msg_path(&self) -> Result<File> {
if !self.git_hooks_path.exists() {
bail!("Hooks directory not found at {}", self.git_hooks_path.display());
}
Ok(File::new(self.git_hooks_path.join("prepare-commit-msg")))
}
}