atypical_commit/
config.rs1use 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 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}