1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::ffi::OsStr;
use std::fmt::Debug;
use std::path::Path;

use crate::error::*;

#[derive(Debug, Clone)]
pub struct IgnoredSuffixes<'a> {
    values: Vec<&'a str>,
}

impl Default for IgnoredSuffixes<'_> {
    fn default() -> Self {
        Self {
            values: vec![
                // Editor etc backups
                "~",
                ".bak",
                ".old",
                ".orig",
                // Debian and derivatives apt/dpkg/ucf backups
                ".dpkg-dist",
                ".dpkg-old",
                ".ucf-dist",
                ".ucf-new",
                ".ucf-old",
                // Red Hat and derivatives rpm backups
                ".rpmnew",
                ".rpmorig",
                ".rpmsave",
                // Build system input/template files
                ".in",
            ],
        }
    }
}

impl<'a> IgnoredSuffixes<'a> {
    pub fn add_suffix(&mut self, suffix: &'a str) {
        self.values.push(suffix)
    }

    pub fn strip_suffix(&self, file_name: &'a str) -> Option<&'a str> {
        for suffix in self.values.iter() {
            if let Some(stripped_file_name) = file_name.strip_suffix(suffix) {
                return Some(stripped_file_name);
            }
        }
        None
    }

    /// If we find an ignored suffix on the file name, e.g. '~', we strip it and
    /// then try again without it.
    pub fn try_with_stripped_suffix<T, F>(&self, file_name: &'a OsStr, func: F) -> Result<Option<T>>
    where
        F: Fn(&'a OsStr) -> Result<Option<T>>,
    {
        if let Some(file_str) = Path::new(file_name).to_str() {
            if let Some(stripped_file_name) = self.strip_suffix(file_str) {
                return func(OsStr::new(stripped_file_name));
            }
        }
        Ok(None)
    }
}

#[test]
fn internal_suffixes() {
    let ignored_suffixes = IgnoredSuffixes::default();

    let file_names = ignored_suffixes
        .values
        .iter()
        .map(|suffix| format!("test.json{}", suffix));
    for file_name_str in file_names {
        let file_name = OsStr::new(&file_name_str);
        let expected_stripped_file_name = OsStr::new("test.json");
        let stripped_file_name = ignored_suffixes
            .try_with_stripped_suffix(file_name, |stripped_file_name| Ok(Some(stripped_file_name)));
        assert_eq!(
            expected_stripped_file_name,
            stripped_file_name.unwrap().unwrap()
        );
    }
}

#[test]
fn external_suffixes() {
    let mut ignored_suffixes = IgnoredSuffixes::default();
    ignored_suffixes.add_suffix(".development");
    ignored_suffixes.add_suffix(".production");

    let file_names = ignored_suffixes
        .values
        .iter()
        .map(|suffix| format!("test.json{}", suffix));
    for file_name_str in file_names {
        let file_name = OsStr::new(&file_name_str);
        let expected_stripped_file_name = OsStr::new("test.json");
        let stripped_file_name = ignored_suffixes
            .try_with_stripped_suffix(file_name, |stripped_file_name| Ok(Some(stripped_file_name)));
        assert_eq!(
            expected_stripped_file_name,
            stripped_file_name.unwrap().unwrap()
        );
    }
}