Skip to main content

citum_schema_style/template/
reference.rs

1/*
2SPDX-License-Identifier: MIT OR Apache-2.0
3SPDX-FileCopyrightText: © 2023-2026 Bruce D'Arcus and Citum contributors
4*/
5
6//! Template presets, references, and locale-scoped template overrides.
7
8#[cfg(feature = "schema")]
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12use crate::embedded;
13use crate::template::Template;
14
15/// Available embedded template presets.
16///
17/// These reference battle-tested templates for common citation styles.
18/// See `citum_schema::embedded` for the actual template implementations.
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[cfg_attr(feature = "schema", derive(JsonSchema))]
21#[serde(rename_all = "kebab-case")]
22pub enum TemplatePreset {
23    /// APA 7th edition (author-date)
24    Apa,
25    /// Chicago Manual of Style (author-date)
26    ChicagoAuthorDate,
27    /// Vancouver (numeric)
28    Vancouver,
29    /// IEEE (numeric)
30    Ieee,
31    /// Harvard/Elsevier (author-date)
32    Harvard,
33    /// Numeric citation number only (citation-focused preset)
34    NumericCitation,
35}
36
37/// A reference to a template, which can be either a named builtin preset
38/// or a URI (e.g., `file://...`, `@hub/...`, `https://...`).
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
40#[cfg_attr(feature = "schema", derive(JsonSchema))]
41#[serde(untagged)]
42pub enum TemplateReference {
43    /// A named builtin template preset.
44    Preset(TemplatePreset),
45    /// A URI reference to a remote or local template.
46    Uri(String),
47}
48
49impl From<TemplatePreset> for TemplateReference {
50    fn from(preset: TemplatePreset) -> Self {
51        TemplateReference::Preset(preset)
52    }
53}
54
55impl TemplateReference {
56    /// Resolve this reference to a citation template when it names a built-in preset.
57    #[must_use]
58    pub fn citation_template(&self) -> Option<Template> {
59        match self {
60            Self::Preset(preset) => Some(preset.citation_template()),
61            Self::Uri(_) => None,
62        }
63    }
64
65    /// Resolve this reference to a bibliography template when it names a built-in preset.
66    #[must_use]
67    pub fn bibliography_template(&self) -> Option<Template> {
68        match self {
69            Self::Preset(preset) => Some(preset.bibliography_template()),
70            Self::Uri(_) => None,
71        }
72    }
73}
74
75impl TemplatePreset {
76    /// Resolve this preset to a citation template.
77    #[must_use]
78    pub fn citation_template(&self) -> Template {
79        match self {
80            TemplatePreset::Apa => embedded::apa_citation(),
81            TemplatePreset::ChicagoAuthorDate => embedded::chicago_author_date_citation(),
82            TemplatePreset::Vancouver => embedded::vancouver_citation(),
83            TemplatePreset::Ieee => embedded::ieee_citation(),
84            TemplatePreset::Harvard => embedded::harvard_citation(),
85            TemplatePreset::NumericCitation => embedded::numeric_citation(),
86        }
87    }
88
89    /// Resolve this preset to a bibliography template.
90    #[must_use]
91    pub fn bibliography_template(&self) -> Template {
92        match self {
93            TemplatePreset::Apa => embedded::apa_bibliography(),
94            TemplatePreset::ChicagoAuthorDate => embedded::chicago_author_date_bibliography(),
95            TemplatePreset::Vancouver => embedded::vancouver_bibliography(),
96            TemplatePreset::Ieee => embedded::ieee_bibliography(),
97            TemplatePreset::Harvard => embedded::harvard_bibliography(),
98            // Citation-focused preset; Vancouver bibliography is the closest numeric fallback.
99            TemplatePreset::NumericCitation => embedded::vancouver_bibliography(),
100        }
101    }
102}
103
104/// Locale-scoped template override with optional fallback behavior.
105#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Default)]
106#[cfg_attr(feature = "schema", derive(JsonSchema))]
107#[serde(rename_all = "kebab-case")]
108pub struct LocalizedTemplateSpec {
109    /// Language tags that should select this template override.
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub locale: Option<Vec<String>>,
112    /// Whether this override is the fallback when no locale matches.
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub default: Option<bool>,
115    /// Template used when this localized override is selected.
116    pub template: Template,
117    /// Forward-compat: captures unknown keys when an older engine reads a
118    /// style produced by a newer schema. Empty by default; treated as a
119    /// SoftDegrade signal. See `docs/specs/FORWARD_COMPATIBILITY.md`.
120    #[serde(
121        flatten,
122        default,
123        skip_serializing_if = "std::collections::BTreeMap::is_empty"
124    )]
125    #[cfg_attr(feature = "schema", schemars(skip))]
126    pub unknown_fields: std::collections::BTreeMap<String, serde_yaml::Value>,
127}
128
129pub(crate) fn locale_matches(targets: &[String], language: &str) -> bool {
130    let primary = language.split('-').next().unwrap_or(language);
131    targets.iter().any(|candidate| {
132        candidate == language || candidate.split('-').next().unwrap_or(candidate) == primary
133    })
134}