use std::collections::HashMap;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::grouping;
use crate::options::BibliographyOptions;
use crate::template::{
LocalizedTemplateSpec, ResolvedLocalizedTemplate, Template, TemplateReference,
TemplateVariants, matched_localized_template,
};
fn default_true() -> bool {
true
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub struct BibliographySpec {
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<BibliographyOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
pub template_ref: Option<TemplateReference>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub template: Option<Template>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locales: Option<Vec<LocalizedTemplateSpec>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub type_variants: Option<TemplateVariants>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sort: Option<grouping::GroupSortEntry>,
#[serde(default = "default_true")]
pub groups_enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub groups: Option<Vec<grouping::BibliographyGroup>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custom: Option<HashMap<String, serde_json::Value>>,
#[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 BibliographySpec {
fn default() -> Self {
Self {
options: None,
template_ref: None,
template: None,
locales: None,
type_variants: None,
sort: None,
groups_enabled: true,
groups: None,
custom: None,
unknown_fields: std::collections::BTreeMap::new(),
}
}
}
impl BibliographySpec {
pub fn resolve_template(&self) -> Option<Template> {
self.template.clone().or_else(|| {
self.template_ref
.as_ref()
.and_then(TemplateReference::bibliography_template)
})
}
pub fn resolve_localized_template(
&self,
language: Option<&str>,
) -> Option<ResolvedLocalizedTemplate> {
if let Some(matched) = language
.zip(self.locales.as_deref())
.and_then(|(language, locales)| matched_localized_template(locales, language))
{
return Some(matched);
}
self.locales
.as_ref()
.and_then(|locales| {
locales
.iter()
.find(|spec| spec.default.unwrap_or(false))
.map(|spec| ResolvedLocalizedTemplate {
template: spec.template.clone(),
locale: None,
type_variants: spec.type_variants.clone(),
})
})
.or_else(|| {
self.resolve_template()
.map(|template| ResolvedLocalizedTemplate {
template,
locale: None,
type_variants: None,
})
})
}
pub fn resolve_template_for_language(&self, language: Option<&str>) -> Option<Template> {
self.resolve_localized_template(language)
.map(|resolved| resolved.template)
}
pub fn resolve_localized_template_for_type(
&self,
ref_type: &str,
language: Option<&str>,
) -> Option<ResolvedLocalizedTemplate> {
let mut resolved = self.resolve_localized_template(language)?;
if let Some(template) = resolved
.type_variants
.as_ref()
.and_then(|variants| {
variants.iter().find_map(|(selector, template)| {
selector.matches(ref_type).then(|| template.clone())
})
})
.or_else(|| {
self.type_variants.as_ref().and_then(|variants| {
variants.iter().find_map(|(selector, variant)| {
selector
.matches(ref_type)
.then(|| variant.clone().into_template())
.flatten()
})
})
})
{
resolved.template = template;
}
Some(resolved)
}
pub fn resolve_template_for_type(
&self,
ref_type: &str,
language: Option<&str>,
) -> Option<Template> {
self.resolve_localized_template_for_type(ref_type, language)
.map(|resolved| resolved.template)
}
}