git-checks 3.5.2

Checks to run against a topic in git to enforce coding standards.
Documentation
// Copyright 2016 Kitware, Inc.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use impl_prelude::*;

use std::ascii::AsciiExt;

#[derive(Debug, Default)]
/// A check which denies commits which adds files containing special characters in their paths.
pub struct InvalidPaths {
    /// Characters not allowed within a filename.
    invalid_characters: String,
}

impl InvalidPaths {
    /// Create a check which rejects paths which contain invalid characters.
    ///
    /// In addition to whitespace and non-ASCII characters, the characters given here are also
    /// disallowed.
    pub fn new<S: ToString>(chars: S) -> Self {
        Self {
            invalid_characters: chars.to_string(),
        }
    }

    /// Whether a string has any invalid characters.
    fn has_invalid_chars(&self, chars: &str) -> bool {
        chars.chars()
            .any(|ref c| {
                !c.is_ascii() || c.is_whitespace() || c.is_control() ||
                self.invalid_characters.chars().any(|ref ic| ic == c)
            })
    }

    /// Whether the string has any invalid bytes.
    fn has_invalid_bytes(&self, bytes: &[u8]) -> bool {
        bytes.iter()
            .any(|&c| !c.is_ascii() || c < 32)
    }
}

impl ContentCheck for InvalidPaths {
    fn name(&self) -> &str {
        "invalid-paths"
    }

    fn check(&self, _: &CheckGitContext, content: &Content) -> Result<CheckResult> {
        let mut result = CheckResult::new();

        for diff in content.diffs() {
            if let StatusChange::Added = diff.status {
                let have_bad_char = self.has_invalid_chars(diff.name.as_str());
                let have_bad_bytes = self.has_invalid_bytes(diff.name.as_bytes());

                if have_bad_char || have_bad_bytes {
                    result.add_error(format!("{}adds the `{}` path which contains at \
                                              least one forbidden character: \
                                              `<non-ASCII><whitespace><control>{}`.",
                                             commit_prefix(content),
                                             diff.name,
                                             self.invalid_characters));
                }
            }
        }

        Ok(result)
    }
}

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

    const BAD_TOPIC: &str = "f536f44cf96b82e479d4973d5ea1cf78058bd1fb";
    const FIX_TOPIC: &str = "8ff69e1834ef2e82c0ed5cfb4ba56f1e4de85d03";

    #[test]
    fn test_invalid_paths() {
        let check = InvalidPaths::new("$");
        let result = run_check("test_invalid_paths", BAD_TOPIC, check);
        test_result_errors(result, &[
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
             `\"control-character-\\003\"` path which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>$`.",
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
             `\"invalid-utf8-\\200\"` path which contains at least one forbidden \
             character: `<non-ASCII><whitespace><control>$`.",
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
             `\"non-ascii-\\303\\251\"` path which contains at least one forbidden \
             character: `<non-ASCII><whitespace><control>$`.",
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `with whitespace` \
             path which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>$`.",
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `with-dollar-$` path \
             which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>$`.",
        ]);
    }

    #[test]
    fn test_invalid_paths_topic() {
        let check = InvalidPaths::new("$");
        let result = run_topic_check("test_invalid_paths_topic", BAD_TOPIC, check);
        test_result_errors(result, &[
            "adds the `\"control-character-\\003\"` path which contains at least one forbidden \
             character: `<non-ASCII><whitespace><control>$`.",
            "adds the `\"invalid-utf8-\\200\"` path which contains at least one forbidden \
             character: `<non-ASCII><whitespace><control>$`.",
            "adds the `\"non-ascii-\\303\\251\"` path which contains at least one forbidden \
             character: `<non-ASCII><whitespace><control>$`.",
            "adds the `with whitespace` path which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>$`.",
            "adds the `with-dollar-$` path which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>$`.",
        ]);
    }

    #[test]
    fn test_invalid_paths_default() {
        let check = InvalidPaths::default();
        let result = run_check("test_invalid_paths_default", BAD_TOPIC, check);
        test_result_errors(result, &[
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the \
             `\"control-character-\\003\"` path which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>`.",
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `\"invalid-utf8-\\200\"` \
             path which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>`.",
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `\"non-ascii-\\303\\251\"` \
             path which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>`.",
            "commit f536f44cf96b82e479d4973d5ea1cf78058bd1fb adds the `with whitespace` path \
             which contains at least one forbidden character: \
             `<non-ASCII><whitespace><control>`.",
        ]);
    }

    #[test]
    fn test_invalid_paths_topic_fixed() {
        let check = InvalidPaths::default();
        run_topic_check_ok("test_invalid_paths_topic_fixed", FIX_TOPIC, check);
    }
}