dyd 1.7.0

CLI for daily diffing of git repos
Documentation
use crate::app::{AppResult, Event};
use crate::git;
use crate::manifest::Remote;
use crate::time;

use std::path::{Path, PathBuf};
use std::sync::mpsc;

#[derive(Clone, Debug, Default)]
pub enum RepoStatus {
  #[default]
  Checking,
  Cloning,
  Pulling,
  Failed,
  Log,
  Finished,
}

#[derive(Debug, Default)]
pub struct Repo {
  pub(crate) branch: Option<String>,
  pub(crate) logs: Vec<Log>,
  pub(crate) name: String,
  pub(crate) origin: String,
  pub(crate) status: RepoStatus,
}

impl std::fmt::Display for Repo {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}", self.name)?;
    if let Some(branch) = &self.branch {
      write!(f, " — ({})", branch)
    } else {
      Ok(())
    }
  }
}

impl From<Remote> for Repo {
  fn from(remote: Remote) -> Self {
    Repo {
      branch: remote.branch,
      name: remote.name,
      origin: remote.origin,
      ..Default::default()
    }
  }
}

#[derive(Clone, Debug)]
pub struct Log {
  pub age: String,
  pub author: String,
  pub cdate: String,
  pub commit_datetime: chrono::DateTime<chrono::Utc>,
  pub message: String,
  pub sha: String,
}

impl From<&str> for Log {
  fn from(log_str: &str) -> Self {
    let values: Vec<&str> = log_str.split('\x0B').collect();

    if let &[sha, cdate, age, author, message] = &*values {
      let sha = sha.to_owned();
      let commit_datetime = time::parse_unix(cdate).unwrap();
      let age = age.to_owned();
      let author = author.to_owned();
      let message = message.to_owned();
      Self {
        age,
        author,
        cdate: cdate.to_owned(),
        commit_datetime,
        message,
        sha,
      }
    } else {
      Self {
        age: "".to_owned(),
        author: "".to_owned(),
        cdate: "".to_owned(),
        commit_datetime: time::parse_unix("0").unwrap(),
        message: "".to_owned(),
        sha: "".to_owned(),
      }
    }
  }
}

impl Repo {
  pub fn update(&self, id: String, root_path: &Path, sender: mpsc::Sender<Event>) -> AppResult<()> {
    let path = self.path(root_path)?;
    let origin = self.origin.clone();
    let branch = self.branch.clone();

    std::thread::spawn(move || {
      if path.is_dir() {
        sender
          .send(Event::RepoStatusChange(id.clone(), RepoStatus::Pulling))
          .unwrap();

        git::pull_repo(&path);
      } else {
        sender
          .send(Event::RepoStatusChange(id.clone(), RepoStatus::Cloning))
          .unwrap();
        git::clone_repo(&origin, &path);
      }
      sender
        .send(Event::RepoStatusChange(id.clone(), RepoStatus::Log))
        .unwrap();

      if let Ok(logs) = Repo::logs(&path, &branch) {
        sender
          .send(Event::RepoStatusComplete(id.clone(), logs))
          .unwrap();
      };
    });
    Ok(())
  }

  pub fn path(&self, root: &Path) -> AppResult<PathBuf> {
    if let Some(path) = Path::new(&self.origin).file_name() {
      Ok(root.join(path))
    } else {
      Err(format!("Unable to determine local path for {}", self.name).into())
    }
  }

  fn logs(path: &PathBuf, branch: &Option<String>) -> AppResult<Vec<Log>> {
    let logs = git::logs(path, branch)?;

    Ok(
      std::str::from_utf8(&logs)
        .unwrap()
        .trim()
        .split('\n')
        .map(|l| l.into())
        .collect(),
    )
  }
}