use super::PageRangeFormat;
use citum_schema_data::citation::LocatorType;
use std::collections::HashMap;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum LabelForm {
None,
#[default]
Short,
Long,
Symbol,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum LabelRepeat {
#[default]
All,
First,
None,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum TypeClass {
Legal,
Classical,
#[default]
Standard,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub struct LocatorKindConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub label_form: Option<LabelForm>,
#[serde(skip_serializing_if = "Option::is_none")]
pub range_format: Option<PageRangeFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub strip_label_periods: Option<bool>,
#[serde(
flatten,
default,
skip_serializing_if = "std::collections::BTreeMap::is_empty"
)]
#[cfg_attr(feature = "schema", schemars(skip))]
pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub struct LocatorPattern {
pub kinds: Vec<LocatorType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub type_class: Option<TypeClass>,
pub order: Vec<LocatorType>,
#[serde(default = "default_delimiter")]
pub delimiter: String,
#[serde(default)]
pub label_repeat: LabelRepeat,
#[serde(
flatten,
default,
skip_serializing_if = "std::collections::BTreeMap::is_empty"
)]
#[cfg_attr(feature = "schema", schemars(skip))]
pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub struct LocatorConfig {
#[serde(default = "default_label_form")]
pub default_label_form: LabelForm,
#[serde(default)]
pub range_format: PageRangeFormat,
#[serde(skip_serializing_if = "Option::is_none")]
pub strip_label_periods: Option<bool>,
#[serde(default)]
pub kinds: HashMap<LocatorType, LocatorKindConfig>,
#[serde(default)]
pub patterns: Vec<LocatorPattern>,
#[serde(default = "default_delimiter")]
pub fallback_delimiter: String,
#[serde(
flatten,
default,
skip_serializing_if = "std::collections::BTreeMap::is_empty"
)]
#[cfg_attr(feature = "schema", schemars(skip))]
pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
}
impl Default for LocatorConfig {
fn default() -> Self {
Self {
default_label_form: LabelForm::Short,
range_format: PageRangeFormat::Expanded,
strip_label_periods: None,
kinds: HashMap::new(),
patterns: Vec::new(),
fallback_delimiter: ", ".to_string(),
unknown_fields: std::collections::BTreeMap::new(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum LocatorPreset {
Note,
AuthorDate,
}
impl LocatorPreset {
#[must_use]
pub fn config(self) -> LocatorConfig {
match self {
LocatorPreset::Note => LocatorConfig {
default_label_form: LabelForm::Short,
range_format: PageRangeFormat::Expanded,
strip_label_periods: None,
kinds: {
let mut m = HashMap::new();
m.insert(
LocatorType::Page,
LocatorKindConfig {
label_form: Some(LabelForm::None),
range_format: None,
strip_label_periods: None,
unknown_fields: std::collections::BTreeMap::new(),
},
);
m
},
patterns: Vec::new(),
fallback_delimiter: ", ".to_string(),
unknown_fields: std::collections::BTreeMap::new(),
},
LocatorPreset::AuthorDate => LocatorConfig {
default_label_form: LabelForm::Short,
range_format: PageRangeFormat::Expanded,
strip_label_periods: None,
kinds: HashMap::new(),
patterns: Vec::new(),
fallback_delimiter: ", ".to_string(),
unknown_fields: std::collections::BTreeMap::new(),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(untagged)]
pub enum LocatorConfigEntry {
Preset(LocatorPreset),
Explicit(LocatorConfig),
}
impl LocatorConfigEntry {
#[must_use]
pub fn resolve(self) -> LocatorConfig {
match self {
LocatorConfigEntry::Preset(preset) => preset.config(),
LocatorConfigEntry::Explicit(config) => config,
}
}
}
fn default_label_form() -> LabelForm {
LabelForm::Short
}
fn default_delimiter() -> String {
", ".to_string()
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::todo,
clippy::unimplemented,
clippy::unreachable,
clippy::get_unwrap,
reason = "Panicking is acceptable and often desired in tests."
)]
mod tests {
use super::*;
#[test]
fn test_locator_preset_note() {
let config = LocatorPreset::Note.config();
assert_eq!(config.default_label_form, LabelForm::Short);
assert_eq!(config.range_format, PageRangeFormat::Expanded);
}
#[test]
fn test_locator_preset_author_date() {
let config = LocatorPreset::AuthorDate.config();
assert_eq!(config.default_label_form, LabelForm::Short);
assert_eq!(config.range_format, PageRangeFormat::Expanded);
}
#[test]
fn test_locator_config_entry_preset() {
let entry = LocatorConfigEntry::Preset(LocatorPreset::Note);
let config = entry.resolve();
assert_eq!(config.default_label_form, LabelForm::Short);
}
#[test]
fn test_locator_config_entry_explicit() {
let entry = LocatorConfigEntry::Explicit(LocatorConfig {
default_label_form: LabelForm::Long,
..Default::default()
});
let config = entry.resolve();
assert_eq!(config.default_label_form, LabelForm::Long);
}
#[test]
fn test_locator_config_default() {
let config = LocatorConfig::default();
assert_eq!(config.default_label_form, LabelForm::Short);
assert_eq!(config.fallback_delimiter, ", ");
}
}