git-snip 0.2.0

Snip local Git branches that do not exist on the remote.
Documentation
use std::fmt::{Display, Formatter, Result};
use std::str::FromStr;

/// Hook types used in git.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum GitHookType {
    PostMerge,
    PostRewrite,
}

/// Converts HookType to a string. This is used to create the hook file name.
/// For example, HookType::PreCommit will be converted to "pre-commit".
impl Display for GitHookType {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        match self {
            GitHookType::PostMerge => write!(f, "post-merge"),
            GitHookType::PostRewrite => write!(f, "post-rewrite"),
        }
    }
}

impl FromStr for GitHookType {
    type Err = ();

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "post-merge" => Ok(GitHookType::PostMerge),
            "post-rewrite" => Ok(GitHookType::PostRewrite),
            _ => Err(()),
        }
    }
}

/// Representation of a git hook script.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GitHook(String);

impl GitHook {
    /// Convert the hook script to a byte slice.
    pub fn as_bytes(&self) -> &[u8] {
        self.0.as_bytes()
    }

    /// Default git-snip hook script.
    pub fn default() -> Self {
        Self(
            r#"#!/bin/sh
HEAD_BRANCH=$(git rev-parse --abbrev-ref HEAD)
case "$HEAD_BRANCH" in
    'main'|'master'|'develop') ;;
        *) exit ;;
esac

git snip --yes

"#
            .to_string(),
        )
    }
}

impl AsRef<str> for GitHook {
    fn as_ref(&self) -> &str {
        &self.0
    }
}

impl std::ops::Deref for GitHook {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Display for GitHook {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
        self.0.fmt(f)
    }
}

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

    impl GitHook {
        /// Return the hook as a String, consuming self.
        pub fn into_string(self) -> String {
            self.0
        }

        /// Create a new instance of GitHook.
        pub fn new<S: Into<String>>(hook: S) -> Self {
            Self(hook.into())
        }
    }

    #[test]
    fn test_hook_type_display() {
        // GIVEN hook types
        // WHEN converting to string
        // THEN the string representation is correct
        assert_eq!(GitHookType::PostMerge.to_string(), "post-merge");
        assert_eq!(GitHookType::PostRewrite.to_string(), "post-rewrite");
    }

    #[test]
    fn test_hook_type_from_str() {
        // GIVEN string representations of hook types
        // WHEN parsing them
        // THEN the correct hook types are returned
        assert_eq!("post-merge".parse(), Ok(GitHookType::PostMerge));
        assert_eq!("post-rewrite".parse(), Ok(GitHookType::PostRewrite));
        assert!("other-hook".parse::<GitHookType>().is_err());
    }

    #[test]
    fn test_hook_as_bytes() {
        // GIVEN a GitHook instance
        // WHEN converting to bytes
        let hook = GitHook::new("echo 'Hello, world!'");

        // THEN the bytes match the expected string
        assert_eq!(hook.as_bytes(), b"echo 'Hello, world!'");
    }

    #[test]
    fn test_into_string() {
        // GIVEN a GitHook instance
        // WHEN converting to String
        let hook = GitHook::new("echo 'Hello, world!'");
        // THEN the string matches the expected value
        assert_eq!(hook.into_string(), "echo 'Hello, world!'");
    }

    #[test]
    fn test_as_ref_and_deref() {
        // GIVEN a GitHook instance
        let hook = GitHook::new("echo 'Hi'");
        // WHEN using as_ref and deref
        // THEN the results are as expected
        assert_eq!(hook.as_ref(), "echo 'Hi'");
        assert_eq!(&*hook, "echo 'Hi'");
    }

    #[test]
    fn test_display() {
        // GIVEN a GitHook instance
        // WHEN formatting it as a string
        let hook = GitHook::new("echo 'Hi'");

        // THEN the display output is correct
        assert_eq!(hook.to_string(), "echo 'Hi'");
    }

    #[test]
    fn test_default() {
        // GIVEN the default GitHook
        let hook = GitHook::default();

        // THEN it contains the expected script
        assert!(hook.as_ref().contains("git snip --yes"));
    }
}