git-sha1 1.0.1

Expose the Git-SHA1 to the crate during build-time.
#![allow(clippy::needless_doctest_main)]
//! Provide the current Git commit SHA1 during build.
//!
//! # Example
//!
//! In `build.rs`:
//! ```
//! use git_sha1::GitSHA1;
//!
//! fn main() {
//!     GitSHA1::read().set("GIT_SHA1");
//! }
//! ```
//!
//! In `main.rs`:
//! ```
//! use git_sha1::GitSHA1;
//!
//! // either as static &str:
//! static SHA1: &str = env!("GIT_SHA1");
//!
//! // or during runtime:
//! fn main() {
//!     let sha1 = GitSHA1::from_env("GIT_SHA1");
//!
//!     let long = sha1.long();
//!     assert_eq!(SHA1, long);
//!
//!     let short = sha1.short(10);
//!     assert_eq!(&SHA1[..10], short);
//! }
//! ```
//!

use std::process::Command;

/// Number of hex-characters in the Git SHA1.
///
/// There are half as many __bytes__ in a SHA1.
pub const GIT_SHA1_LENGTH: usize = 40;

pub struct GitSHA1 {
    sha1: String,
}

impl GitSHA1 {
    /// Read current Git SHA1 by running `git rev-parse HEAD`.
    ///
    /// This only retrieves the SHA1 from git. Use
    /// [`set(name)`](struct.GitSHA1.html#method.set) to pass compile-time environment
    /// variable to cargo-build. Afterwards it can be read from your source files.
    pub fn read() -> Self {
        let output = Command::new("git")
            .args(&["rev-parse", "HEAD"])
            .output()
            .unwrap();
        let sha1: String = String::from_utf8(output.stdout).unwrap().trim().into();
        assert_eq!(sha1.len(), GIT_SHA1_LENGTH, "unexpected length of Git SHA1");

        Self { sha1 }
    }

    /// Read the comiple-time environment variable from carto-build. This can be used
    /// during runtime, i.e. in `fn main() {}`.
    ///
    /// # Note:
    ///
    /// You can read the Git SHA1 statically using `env!`:
    ///
    /// ```
    /// // "GIT_SHA1" set in `build.rs`
    /// const GIT_SHA1: &'static str = env!("GIT_SHA1");
    /// ```
    #[allow(dead_code)] // because we never use it in `build.rs`
    pub fn from_env(name: &str) -> Self {
        let sha1: String = std::env::var(name)
            .unwrap_or_else(|_| panic!("failed to fetch environment variable '{}'", name));
        assert_eq!(sha1.len(), GIT_SHA1_LENGTH, "unexpected length of Git SHA1");

        Self { sha1 }
    }

    /// Generate cargo environment variable for long Git SHA1.
    ///
    /// # Example
    ///
    /// ```
    /// # use git_sha1::GitSHA1;
    /// GitSHA1::read().set("GIT_SHA1");
    /// ```
    pub fn set(&self, name: &str) {
        println!("cargo:rustc-env={}={}", name, self.sha1);
    }

    /// Return the complete SHA1 as a String.
    #[allow(dead_code)] // because we never use it in `build.rs`
    pub fn long(&self) -> String {
        self.sha1.clone()
    }

    /// Return a subset of digits (short sha) as a String.
    #[allow(dead_code)] // because we never use it in `build.rs`
    pub fn short(&self, count: usize) -> String {
        if count > self.sha1.len() {
            panic!(
                "number of digits ({}) exceed length of Git SHA1 ({})",
                count,
                self.sha1.len()
            );
        }
        self.sha1.as_str()[..count].into()
    }
}

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

    #[test]
    fn short_length_1() {
        let s = GitSHA1::from_env("GIT_SHA1").short(1);
        assert_eq!(s.len(), 1);
    }

    #[test]
    #[should_panic(expected = "exceed length of Git SHA1")]
    fn short_error() {
        let sha1 = GitSHA1::from_env("GIT_SHA1");
        sha1.short(41);
    }
}