use std::fmt::{Display, Formatter}; use std::path::PathBuf; use crate::builders::{GitFetchBuilder, GitStatusBuilder}; use tabled::Tabled; use crate::errors::AppResult; use crate::git::GitRepository; pub const BOLD: &str = "\x1b[1m"; pub const CLEAN: &str = "\x1b[34m"; pub const DIRTY: &str = "\x1b[33m"; pub const UNKNOWN: &str = "\x1b[37m"; pub const RESET: &str = "\x1b[0m"; #[derive(Debug, Clone, Tabled)] pub enum GitStatus { Clean, Dirty, Unknown, } impl GitStatus { fn color(&self) -> String { match self { Self::Clean => format!("{BOLD}{CLEAN}"), Self::Dirty => format!("{BOLD}{DIRTY}"), Self::Unknown => format!("{BOLD}{UNKNOWN}"), } } } impl Display for GitStatus { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { Self::Clean => write!(f, "✔"), Self::Dirty => write!(f, "✘"), Self::Unknown => write!(f, "?"), } } } #[derive(Debug, Clone)] pub struct RichInfo { output: String, pub push: GitStatus, pub pull: GitStatus, pub dirty: GitStatus, } impl Display for RichInfo { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "{}↑ {}↓ {}*{}", self.push.color(), self.pull.color(), self.dirty.color(), RESET ) } } #[derive(Debug, Clone, Tabled)] pub struct GitInfo { #[tabled(skip)] pub path: PathBuf, #[tabled(rename = "Repository")] pub name: String, #[tabled(rename = "Rich Info")] pub rich: RichInfo, #[tabled(rename = "Commit")] pub commit: String, } impl GitInfo { pub fn new(path: PathBuf) -> AppResult { let output = GitStatusBuilder::new(path.clone()).status()?.output()?; let output = String::from_utf8(output.stdout)?; GitFetchBuilder::new(path.clone()) .fetch()? .arg("--all") .silent()?; let mut repo = Self { path: path.clone(), name: path.file_name().unwrap().to_string_lossy().to_string(), rich: RichInfo { output, push: GitStatus::Unknown, pull: GitStatus::Unknown, dirty: GitStatus::Unknown, }, commit: GitRepository::new(path).hash()?, }; repo.push()?; repo.pull()?; repo.dirty()?; Ok(repo) } fn push(&mut self) -> AppResult<()> { self.rich.push = if self.rich.output.contains("ahead") { GitStatus::Dirty } else { GitStatus::Clean }; Ok(()) } fn pull(&mut self) -> AppResult<()> { self.rich.pull = if self.rich.output.contains("behind") { GitStatus::Dirty } else { GitStatus::Clean }; Ok(()) } fn dirty(&mut self) -> AppResult<()> { self.rich.dirty = if self.rich.output.contains("Untracked files") || self.rich.output.contains("Changes not staged for commit") || self.rich.output.contains("Changes to be committed") { GitStatus::Dirty } else { GitStatus::Clean }; Ok(()) } }