Skip to main content

atypical_commit/
config.rs

1// The `[commit]` section of atypical.toml: an owned mirror of
2// `Tokens`, defaulting field by field to the standard preset.
3
4use serde::Deserialize;
5
6use crate::{DelimitedBy, EnclosureToken, Sequence, Tokens};
7
8pub const SECTION: &str = "commit";
9
10#[derive(Debug, Clone, PartialEq)]
11#[derive(Deserialize)]
12#[serde(deny_unknown_fields, rename_all = "kebab-case", default)]
13pub struct CommitConfig {
14    pub keywords: Vec<String>,
15    pub modifiers: Vec<String>,
16    pub enclosures: Vec<EnclosureConfig>,
17    pub separator: char,
18    pub modifier_sequence: Sequence,
19}
20
21#[derive(Debug, Clone, PartialEq)]
22#[derive(Deserialize)]
23#[serde(deny_unknown_fields, rename_all = "kebab-case")]
24pub struct EnclosureConfig {
25    pub delimiters: DelimitedBy,
26    /// Restricts the contents to these values; anything goes when omitted.
27    pub allowed: Option<Vec<String>>,
28}
29
30impl Default for CommitConfig {
31    fn default() -> Self {
32        (&Tokens::preset_standard()).into()
33    }
34}
35
36impl From<&Tokens<'_>> for CommitConfig {
37    fn from(tokens: &Tokens<'_>) -> Self {
38        fn owned(v: &[&str]) -> Vec<String> {
39            v.iter().map(ToString::to_string).collect()
40        }
41
42        Self {
43            keywords: owned(&tokens.keywords),
44            modifiers: owned(&tokens.modifiers),
45            enclosures: tokens
46                .enclosures
47                .iter()
48                .map(|enclosure| EnclosureConfig {
49                    delimiters: enclosure.delimiters(),
50                    allowed: match enclosure {
51                        EnclosureToken::Flexible(_) => None,
52                        EnclosureToken::Strict(_, allowed) => {
53                            Some(owned(allowed))
54                        }
55                    },
56                })
57                .collect(),
58            separator: tokens.separator,
59            modifier_sequence: tokens.modifier_sequence,
60        }
61    }
62}
63
64impl<'i> From<&'i CommitConfig> for Tokens<'i> {
65    fn from(config: &'i CommitConfig) -> Self {
66        fn borrowed(v: &[String]) -> Vec<&str> {
67            v.iter().map(String::as_str).collect()
68        }
69
70        Self {
71            keywords: borrowed(&config.keywords),
72            modifiers: borrowed(&config.modifiers),
73            enclosures: config
74                .enclosures
75                .iter()
76                .map(|enclosure| match &enclosure.allowed {
77                    None => EnclosureToken::Flexible(enclosure.delimiters),
78                    Some(allowed) => EnclosureToken::Strict(
79                        enclosure.delimiters,
80                        borrowed(allowed),
81                    ),
82                })
83                .collect(),
84            separator: config.separator,
85            modifier_sequence: config.modifier_sequence,
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn test_default_mirrors_the_standard_preset() {
96        let config = CommitConfig::default();
97
98        assert_eq!(Tokens::from(&config), Tokens::preset_standard());
99    }
100
101    #[test]
102    fn test_partial_section_keeps_preset_defaults() {
103        let config: CommitConfig =
104            toml::from_str("keywords = [\"feat\", \"fix\"]").unwrap();
105
106        assert_eq!(config.keywords, vec!["feat", "fix"]);
107        assert_eq!(config.modifiers, CommitConfig::default().modifiers);
108        assert_eq!(config.separator, ':');
109    }
110
111    #[test]
112    fn test_enclosures_map_to_strict_and_flexible() {
113        let config: CommitConfig = toml::from_str(
114            "\
115            [[enclosures]]\n\
116            delimiters = [\"(\", \")\"]\n\
117            allowed = [\"core\"]\n\
118            \n\
119            [[enclosures]]\n\
120            delimiters = [\"{\", \"}\"]\n\
121            ",
122        )
123        .unwrap();
124
125        assert_eq!(
126            Tokens::from(&config).enclosures,
127            vec![
128                EnclosureToken::Strict(['(', ')'], vec!["core"]),
129                EnclosureToken::Flexible(['{', '}']),
130            ]
131        );
132    }
133
134    #[test]
135    fn test_modifier_sequence_names() {
136        let config: CommitConfig =
137            toml::from_str("modifier-sequence = \"post\"").unwrap();
138
139        assert_eq!(config.modifier_sequence, Sequence::Post);
140
141        assert!(
142            toml::from_str::<CommitConfig>("modifier-sequence = \"sideways\"")
143                .is_err()
144        );
145    }
146
147    #[test]
148    fn test_unknown_fields_are_rejected() {
149        assert!(
150            toml::from_str::<CommitConfig>("keyword = [\"typo\"]").is_err()
151        );
152    }
153}