citum_schema_style/options/
locators.rs1use super::PageRangeFormat;
12use citum_schema_data::citation::LocatorType;
13use std::collections::HashMap;
14
15#[cfg(feature = "schema")]
16use schemars::JsonSchema;
17use serde::{Deserialize, Serialize};
18
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
21#[cfg_attr(feature = "schema", derive(JsonSchema))]
22#[serde(rename_all = "kebab-case")]
23pub enum LabelForm {
24 None,
26 #[default]
28 Short,
29 Long,
31 Symbol,
33}
34
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
37#[cfg_attr(feature = "schema", derive(JsonSchema))]
38#[serde(rename_all = "kebab-case")]
39pub enum LabelRepeat {
40 #[default]
42 All,
43 First,
45 None,
47}
48
49#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
51#[cfg_attr(feature = "schema", derive(JsonSchema))]
52#[serde(rename_all = "kebab-case")]
53pub enum TypeClass {
54 Legal,
56 Classical,
58 #[default]
60 Standard,
61}
62
63#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
65#[cfg_attr(feature = "schema", derive(JsonSchema))]
66#[serde(rename_all = "kebab-case")]
67pub struct LocatorKindConfig {
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub label_form: Option<LabelForm>,
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub range_format: Option<PageRangeFormat>,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub strip_label_periods: Option<bool>,
77 #[serde(
81 flatten,
82 default,
83 skip_serializing_if = "std::collections::BTreeMap::is_empty"
84 )]
85 #[cfg_attr(feature = "schema", schemars(skip))]
86 pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
87}
88
89#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93#[cfg_attr(feature = "schema", derive(JsonSchema))]
94#[serde(rename_all = "kebab-case")]
95pub struct LocatorPattern {
96 pub kinds: Vec<LocatorType>,
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub type_class: Option<TypeClass>,
101 pub order: Vec<LocatorType>,
103 #[serde(default = "default_delimiter")]
105 pub delimiter: String,
106 #[serde(default)]
108 pub label_repeat: LabelRepeat,
109 #[serde(
113 flatten,
114 default,
115 skip_serializing_if = "std::collections::BTreeMap::is_empty"
116 )]
117 #[cfg_attr(feature = "schema", schemars(skip))]
118 pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
119}
120
121#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123#[cfg_attr(feature = "schema", derive(JsonSchema))]
124#[serde(rename_all = "kebab-case")]
125pub struct LocatorConfig {
126 #[serde(default = "default_label_form")]
128 pub default_label_form: LabelForm,
129 #[serde(default)]
131 pub range_format: PageRangeFormat,
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub strip_label_periods: Option<bool>,
135 #[serde(default)]
137 pub kinds: HashMap<LocatorType, LocatorKindConfig>,
138 #[serde(default)]
140 pub patterns: Vec<LocatorPattern>,
141 #[serde(default = "default_delimiter")]
143 pub fallback_delimiter: String,
144 #[serde(
148 flatten,
149 default,
150 skip_serializing_if = "std::collections::BTreeMap::is_empty"
151 )]
152 #[cfg_attr(feature = "schema", schemars(skip))]
153 pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
154}
155
156impl Default for LocatorConfig {
157 fn default() -> Self {
158 Self {
159 default_label_form: LabelForm::Short,
160 range_format: PageRangeFormat::Expanded,
161 strip_label_periods: None,
162 kinds: HashMap::new(),
163 patterns: Vec::new(),
164 fallback_delimiter: ", ".to_string(),
165 unknown_fields: std::collections::BTreeMap::new(),
166 }
167 }
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
172#[cfg_attr(feature = "schema", derive(JsonSchema))]
173#[serde(rename_all = "kebab-case")]
174pub enum LocatorPreset {
175 Note,
177 AuthorDate,
179}
180
181impl LocatorPreset {
182 #[must_use]
184 pub fn config(self) -> LocatorConfig {
185 match self {
186 LocatorPreset::Note => LocatorConfig {
187 default_label_form: LabelForm::Short,
188 range_format: PageRangeFormat::Expanded,
189 strip_label_periods: None,
190 kinds: {
191 let mut m = HashMap::new();
192 m.insert(
194 LocatorType::Page,
195 LocatorKindConfig {
196 label_form: Some(LabelForm::None),
197 range_format: None,
198 strip_label_periods: None,
199 unknown_fields: std::collections::BTreeMap::new(),
200 },
201 );
202 m
203 },
204 patterns: Vec::new(),
205 fallback_delimiter: ", ".to_string(),
206 unknown_fields: std::collections::BTreeMap::new(),
207 },
208 LocatorPreset::AuthorDate => LocatorConfig {
209 default_label_form: LabelForm::Short,
210 range_format: PageRangeFormat::Expanded,
211 strip_label_periods: None,
212 kinds: HashMap::new(),
213 patterns: Vec::new(),
214 fallback_delimiter: ", ".to_string(),
215 unknown_fields: std::collections::BTreeMap::new(),
216 },
217 }
218 }
219}
220
221#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223#[cfg_attr(feature = "schema", derive(JsonSchema))]
224#[serde(untagged)]
225pub enum LocatorConfigEntry {
226 Preset(LocatorPreset),
228 Explicit(LocatorConfig),
230}
231
232impl LocatorConfigEntry {
233 #[must_use]
235 pub fn resolve(self) -> LocatorConfig {
236 match self {
237 LocatorConfigEntry::Preset(preset) => preset.config(),
238 LocatorConfigEntry::Explicit(config) => config,
239 }
240 }
241}
242
243fn default_label_form() -> LabelForm {
245 LabelForm::Short
246}
247
248fn default_delimiter() -> String {
250 ", ".to_string()
251}
252
253#[cfg(test)]
254#[allow(
255 clippy::unwrap_used,
256 clippy::expect_used,
257 clippy::panic,
258 clippy::indexing_slicing,
259 clippy::todo,
260 clippy::unimplemented,
261 clippy::unreachable,
262 clippy::get_unwrap,
263 reason = "Panicking is acceptable and often desired in tests."
264)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_locator_preset_note() {
270 let config = LocatorPreset::Note.config();
271 assert_eq!(config.default_label_form, LabelForm::Short);
272 assert_eq!(config.range_format, PageRangeFormat::Expanded);
273 }
274
275 #[test]
276 fn test_locator_preset_author_date() {
277 let config = LocatorPreset::AuthorDate.config();
278 assert_eq!(config.default_label_form, LabelForm::Short);
279 assert_eq!(config.range_format, PageRangeFormat::Expanded);
280 }
281
282 #[test]
283 fn test_locator_config_entry_preset() {
284 let entry = LocatorConfigEntry::Preset(LocatorPreset::Note);
285 let config = entry.resolve();
286 assert_eq!(config.default_label_form, LabelForm::Short);
287 }
288
289 #[test]
290 fn test_locator_config_entry_explicit() {
291 let entry = LocatorConfigEntry::Explicit(LocatorConfig {
292 default_label_form: LabelForm::Long,
293 ..Default::default()
294 });
295 let config = entry.resolve();
296 assert_eq!(config.default_label_form, LabelForm::Long);
297 }
298
299 #[test]
300 fn test_locator_config_default() {
301 let config = LocatorConfig::default();
302 assert_eq!(config.default_label_form, LabelForm::Short);
303 assert_eq!(config.fallback_delimiter, ", ");
304 }
305}