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(r#"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(indoc::indoc! {r#"
114            [[enclosures]]
115            delimiters = ["(", ")"]
116            allowed = ["core"]
117
118            [[enclosures]]
119            delimiters = ["{", "}"]
120        "#})
121        .unwrap();
122
123        assert_eq!(
124            Tokens::from(&config).enclosures,
125            vec![
126                EnclosureToken::Strict(['(', ')'], vec!["core"]),
127                EnclosureToken::Flexible(['{', '}']),
128            ]
129        );
130    }
131
132    #[test]
133    fn test_modifier_sequence_names() {
134        let config: CommitConfig =
135            toml::from_str(r#"modifier-sequence = "post""#).unwrap();
136
137        assert_eq!(config.modifier_sequence, Sequence::Post);
138
139        assert!(
140            toml::from_str::<CommitConfig>(r#"modifier-sequence = "sideways""#)
141                .is_err()
142        );
143    }
144
145    #[test]
146    fn test_unknown_fields_are_rejected() {
147        assert!(
148            toml::from_str::<CommitConfig>(r#"keyword = ["typo"]"#).is_err()
149        );
150    }
151}