gitty 0.1.5

Interactive command line interface for enhanced workflow when using the Git CLI
Documentation
use std::fmt;

enum FileState {
    ADDED,
    MODIFIED,
    RENAMED,
    REMOVED,
    UNTRACKED,
    UNCHANGED,
    UNKNOWN,
}

pub struct ProjectFile {
    pub file_path: String,
    state: FileState,
}

impl ProjectFile {
    // Documentation: https://git-scm.com/docs/git-status
    pub fn from_line(line: &str) -> ProjectFile {
        let (state_code, file_path_raw) = line.split_at(2);
        let mut state_code_chars = state_code.chars();
        state_code_chars.next();
        let unstaged_state_code = state_code_chars.next().unwrap_or('_');
        let state = match unstaged_state_code {
            'A' => FileState::ADDED,
            'M' => FileState::MODIFIED,
            'R' => FileState::RENAMED,
            'D' => FileState::REMOVED,
            '?' => FileState::UNTRACKED,
            ' ' => FileState::UNCHANGED,
             _  => FileState::UNKNOWN,
        };
        let file_path = String::from(file_path_raw.trim());
        ProjectFile { 
            file_path, 
            state,
        }
    }

    pub fn is_staged(&self) -> bool {
        match self.state {
            FileState::UNCHANGED    => true,
            _                       => false,
        }
    }

    pub fn is_not_staged(&self) -> bool {
        !self.is_staged()
    }
}

impl fmt::Display for ProjectFile {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.file_path)
    }
}

pub struct Commit {
    pub hash: String, 
    date: String,
    message_extract: String,
}

impl Commit {
    pub fn message_extract_width() -> usize {
        60
    }

    pub fn from_line(line: &str) -> Commit {
        let mut split_line = line.splitn(3, " | ");
        let hash: &str = split_line.next().unwrap();
        let date: &str = split_line.next().unwrap();
        let commit_message: &str = split_line.next().unwrap();
        let mut message_extract = String::from(commit_message.split("\n").next().unwrap());
        message_extract.truncate(Commit::message_extract_width());
        message_extract = String::from(message_extract.trim());
        if commit_message.len() > message_extract.len() {
            message_extract.push_str("...");
        }
        Commit {
            hash: String::from(hash),
            date: String::from(date),
            message_extract: String::from(message_extract),
        }
    }
}

impl fmt::Display for Commit {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} | {:^31} | {}", self.hash, self.date, self.message_extract)
    }
}

pub struct Branch {
    pub name: String, 
    pub is_checked_out: bool,
}

impl Branch {
    pub fn from_line(line: &str) -> Branch {
        let (branch_status, branch_name) = line.split_at(2);
        let name = String::from(branch_name.trim_start()); 
        let is_checked_out = branch_status.contains("*");
        Branch {
            name, 
            is_checked_out,
        }
    }

    pub fn new_branch_placeholder() -> Branch {
        Branch {
            name: String::from("[<create new branch>]"), 
            is_checked_out: false,
        }
    }
}

impl fmt::Display for Branch {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{} {}", if self.is_checked_out { "*" } else { " " }, self.name)
    }
}

impl PartialEq for Branch {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

pub struct Action<'a> {
    name: String,
    callback: Box<dyn Fn() + 'a>
}

impl<'a> Action<'a> {
    pub fn new<C>(name: &str, callback_closure: C) -> Action<'a>
    where C: Fn() + 'a {
        Action {
            name: String::from(name),
            callback: Box::new(callback_closure),
        }
    }

    pub fn run_action(&self) {
        (self.callback)();
    }
}

impl fmt::Display for Action<'_> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.name)
    }
}