harvest 0.2.2

CLI application that does your git stashes finds
Documentation
use std::error::Error;
use std::fmt;
use std::process::Command;
use std::str;

pub trait GitStashExecutable {
    fn execute_git_stash_command(&self, command: &str) -> Result<Vec<u8>, GitError>;
    fn stash_show(&self, n: usize) -> Result<Vec<u8>, GitError>;
    fn stash_patch_show(&self, n: usize) -> Result<Vec<u8>, GitError>;
}

pub struct GitStashExecutor;

impl GitStashExecutor {
    pub fn new() -> Self {
        GitStashExecutor
    }
}

impl GitStashExecutable for GitStashExecutor {
    fn execute_git_stash_command(&self, command: &str) -> Result<Vec<u8>, GitError> {
        let git_command = format!("git stash {}", command);
        let result = Command::new("sh")
            .arg("-c")
            .arg(git_command)
            .output()
            .unwrap();
        if result.status.success() {
            Ok(result.stdout)
        } else {
            Err(GitError {
                msg: String::from_utf8(result.stderr).unwrap(),
            })
        }
    }

    fn stash_show(&self, n: usize) -> Result<Vec<u8>, GitError> {
        let show_command = format!("show stash@{{{}}}", n);
        self.execute_git_stash_command(&show_command)
    }

    fn stash_patch_show(&self, n: usize) -> Result<Vec<u8>, GitError> {
        let patch_show_command = format!("show -p stash@{{{}}}", n);
        self.execute_git_stash_command(&patch_show_command)
    }
}

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct GitStash {
    pub number: usize,
    pub list_output: String,
    pub show_output: String,
    pub patch_show_output: String,
}

impl GitStash {
    fn new(
        executor: &impl GitStashExecutable,
        number: usize,
        list_output: String,
    ) -> Result<GitStash, GitError> {
        let show_output = String::from_utf8(executor.stash_show(number)?).unwrap();
        let patch_show_output = String::from_utf8(executor.stash_patch_show(number)?).unwrap();
        Ok(GitStash {
            number,
            list_output,
            show_output,
            patch_show_output,
        })
    }
}

impl fmt::Display for GitStash {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}\n{}", self.list_output, self.show_output)
    }
}

#[derive(Debug)]
pub struct GitError {
    msg: String,
}

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

impl Error for GitError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(self)
    }
}

pub fn get_stashes(executor: impl GitStashExecutable) -> Result<Vec<GitStash>, Box<dyn Error>> {
    let stash_lists = String::from_utf8(executor.execute_git_stash_command("list")?).unwrap();

    let stashes = stash_lists
        .lines()
        .enumerate()
        .map(|(number, list_output)| {
            GitStash::new(&executor, number, String::from(list_output)).unwrap()
        })
        .collect();
    Ok(stashes)
}

#[cfg(test)]
mod tests {
    use super::*;

    struct MockStashExecutor;
    impl MockStashExecutor {
        fn new() -> MockStashExecutor {
            MockStashExecutor {}
        }
    }
    impl GitStashExecutable for MockStashExecutor {
        fn execute_git_stash_command(&self, _command: &str) -> Result<Vec<u8>, GitError> {
            let sample =
                b"stash@{0}: WIP on refactor: cddaae4 refactor create git_stash module".to_vec();
            Ok(sample)
        }
        fn stash_show(&self, _n: usize) -> Result<Vec<u8>, GitError> {
            let sample = br#" Cargo.lock   | 21 +++++++++++++++++++++
             Cargo.toml       |  1 +
             src/git_stash.rs |  8 ++++++++
             3 files changed, 30 insertions(+)
            "#
            .to_vec();
            Ok(sample)
        }
        fn stash_patch_show(&self, _n: usize) -> Result<Vec<u8>, GitError> {
            let sample = br#"diff --git a/Cargo.lock b/Cargo.lock
            index 295fdbb..6d9592b 100644
            --- a/Cargo.lock
            +++ b/Cargo.lock
            @@ -87,6 +87,7 @@ name = "harvest"
             version = "0.1.2"
             dependencies = [
              "clap",
            + "mocktopus",
              "pager",
             ]
            "#
            .to_vec();

            Ok(sample)
        }
    }

    #[test]
    fn test_git_stash_new() {
        let mock_executor = MockStashExecutor::new();
        let list_output =
            String::from_utf8(mock_executor.execute_git_stash_command("list").unwrap()).unwrap();
        let git_stash = GitStash::new(&mock_executor, 0, list_output).unwrap();

        assert_eq!(
            GitStash {
                number: 0,
                list_output: "stash@{0}: WIP on refactor: cddaae4 refactor create git_stash module"
                    .to_string(),
                show_output: String::from_utf8(mock_executor.stash_show(0).unwrap()).unwrap(),
                patch_show_output: String::from_utf8(mock_executor.stash_patch_show(0).unwrap())
                    .unwrap(),
            },
            git_stash
        )
    }

    #[test]
    fn test_get_stashes() {
        let mock_executor = MockStashExecutor::new();
        let v = vec![GitStash {
            number: 0,
            list_output: "stash@{0}: WIP on refactor: cddaae4 refactor create git_stash module"
                .to_string(),
            show_output: String::from_utf8(mock_executor.stash_show(0).unwrap()).unwrap(),
            patch_show_output: String::from_utf8(mock_executor.stash_patch_show(0).unwrap())
                .unwrap(),
        }];

        assert_eq!(v, get_stashes(mock_executor).unwrap())
    }
}