mdbook-bib 0.5.3

mdbook plugin allowing to load and present a bibliography in BibLaTex format in your books and cite its references
Documentation
//! Hayagriva CSL style registry and metadata.
//!
//! This module provides:
//! - A registry of common citation styles with short aliases (e.g., "ieee", "apa")
//! - Runtime detection of citation format from any CSL style's metadata
//!
//! ## Style Resolution Strategy
//!
//! 1. **Registry lookup**: Check if the style name matches a known alias
//! 2. **Hayagriva fallback**: Try `ArchivedStyle::by_name()` for full style names
//! 3. **Format detection**: Extract numeric/author-date from the style's metadata
//!
//! The registry provides short aliases and superscript hints (not detectable from CSL).
//! For styles not in the registry, we detect numeric vs author-date from CSL metadata.

use hayagriva::archive::ArchivedStyle;
use hayagriva::citationberg::{
    CitationFormat as CslCitationFormat, IndependentStyle, StyleCategory,
};

/// Content source for citations - mutually exclusive options.
///
/// Determines WHAT to display in citations:
/// - `Numeric`: Sequential index managed by us (e.g., `[1]`, `[2]`)
/// - `Label`: Author-based label generated by hayagriva (e.g., `[Smi24]`)
/// - `AuthorDate`: Full author-date text generated by hayagriva (e.g., `(Smith, 2024)`)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CitationContentType {
    #[default]
    Numeric,
    Label,
    AuthorDate,
}

/// Rendering presentation for citations.
///
/// Determines HOW to display citations (orthogonal to content type):
/// - `Bracketed`: Standard inline format with brackets (e.g., `[1]` or `(Smith, 2024)`)
/// - `Superscript`: Raised format (e.g., `¹`, `²`)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CitationRendering {
    #[default]
    Bracketed,
    Superscript,
}

/// Complete citation format specification.
///
/// Combines content type (WHAT to display) with rendering (HOW to display),
/// making invalid combinations impossible by construction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CitationFormat {
    pub content: CitationContentType,
    pub rendering: CitationRendering,
}

/// Common interface for citation format characteristics.
///
/// Both `StyleInfo` (from registry) and `DetectedStyleFormat` (from CSL metadata)
/// implement this trait to provide uniform access to citation format.
pub trait CitationStyle {
    /// Returns the complete citation format specification.
    fn citation_format(&self) -> CitationFormat;
}

/// Style metadata from the registry: aliases, archived style, and format hints.
///
/// This is the authoritative source for aliased styles, providing both
/// short names and superscript hints that cannot be detected from CSL metadata.
#[derive(Debug, Clone, Copy)]
pub struct StyleInfo {
    /// Short alias(es) for the style (first is canonical)
    pub aliases: &'static [&'static str],
    /// The hayagriva ArchivedStyle variant
    pub archived: ArchivedStyle,
    /// Citation format specification (content type + rendering)
    pub format: CitationFormat,
}

impl CitationStyle for StyleInfo {
    fn citation_format(&self) -> CitationFormat {
        self.format
    }
}

/// Registry of supported style aliases with their metadata.
/// This is the single source of truth for style resolution and classification.
static STYLE_REGISTRY: &[StyleInfo] = &[
    // Numeric bracketed styles
    StyleInfo {
        aliases: &["ieee"],
        archived: ArchivedStyle::InstituteOfElectricalAndElectronicsEngineers,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["vancouver"],
        archived: ArchivedStyle::Vancouver,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["acm", "association-for-computing-machinery"],
        archived: ArchivedStyle::AssociationForComputingMachinery,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["acs", "american-chemical-society"],
        archived: ArchivedStyle::AmericanChemicalSociety,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["ama", "american-medical-association"],
        archived: ArchivedStyle::AmericanMedicalAssociation,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["springer-basic"],
        archived: ArchivedStyle::SpringerBasic,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["cell"],
        archived: ArchivedStyle::Cell,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["elsevier-vancouver"],
        archived: ArchivedStyle::ElsevierVancouver,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Bracketed,
        },
    },
    // Numeric superscript styles
    StyleInfo {
        aliases: &["nature"],
        archived: ArchivedStyle::Nature,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Superscript,
        },
    },
    StyleInfo {
        aliases: &["vancouver-superscript"],
        archived: ArchivedStyle::VancouverSuperscript,
        format: CitationFormat {
            content: CitationContentType::Numeric,
            rendering: CitationRendering::Superscript,
        },
    },
    // Label style (alphanumeric)
    StyleInfo {
        aliases: &["alphanumeric"],
        archived: ArchivedStyle::Alphanumeric,
        format: CitationFormat {
            content: CitationContentType::Label,
            rendering: CitationRendering::Bracketed,
        },
    },
    // Author-date styles
    StyleInfo {
        aliases: &["apa", "american-psychological-association"],
        archived: ArchivedStyle::AmericanPsychologicalAssociation,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["chicago-author-date"],
        archived: ArchivedStyle::ChicagoAuthorDate,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["chicago-notes"],
        archived: ArchivedStyle::ChicagoNotes,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["mla", "modern-language-association"],
        archived: ArchivedStyle::ModernLanguageAssociation,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["mla8", "modern-language-association-8"],
        archived: ArchivedStyle::ModernLanguageAssociation8,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["harvard", "harvard-cite-them-right"],
        archived: ArchivedStyle::HarvardCiteThemRight,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["springer-basic-author-date"],
        archived: ArchivedStyle::SpringerBasicAuthorDate,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
    StyleInfo {
        aliases: &["elsevier-harvard"],
        archived: ArchivedStyle::ElsevierHarvard,
        format: CitationFormat {
            content: CitationContentType::AuthorDate,
            rendering: CitationRendering::Bracketed,
        },
    },
];

/// Find a style in the registry by alias (case-insensitive).
pub fn find_style_info(name: &str) -> Option<&'static StyleInfo> {
    let name_lower = name.to_lowercase();
    STYLE_REGISTRY
        .iter()
        .find(|info| info.aliases.iter().any(|&alias| alias == name_lower))
}

/// Get all supported style aliases (canonical names only).
pub fn supported_style_aliases() -> impl Iterator<Item = &'static str> {
    STYLE_REGISTRY.iter().map(|info| info.aliases[0])
}

/// Get all registry entries for detailed style information.
#[cfg(test)]
pub fn all_registry_styles() -> impl Iterator<Item = &'static StyleInfo> {
    STYLE_REGISTRY.iter()
}

/// Get the number of styles in the registry.
#[cfg(test)]
pub fn registry_style_count() -> usize {
    STYLE_REGISTRY.len()
}

/// Format a human-readable list of available styles grouped by format.
///
/// Returns a formatted string suitable for CLI help or documentation.
#[cfg(test)]
pub fn format_style_list() -> String {
    let mut numeric = Vec::new();
    let mut superscript = Vec::new();
    let mut label = Vec::new();
    let mut author_date = Vec::new();

    for style in STYLE_REGISTRY {
        let name = style.aliases[0];
        match (style.format.content, style.format.rendering) {
            (CitationContentType::Numeric, CitationRendering::Superscript) => {
                superscript.push(name);
            }
            (CitationContentType::Numeric, CitationRendering::Bracketed) => {
                numeric.push(name);
            }
            (CitationContentType::Label, _) => {
                label.push(name);
            }
            (CitationContentType::AuthorDate, _) => {
                author_date.push(name);
            }
        }
    }

    format!(
        "Numeric styles: {}\n\
         Superscript styles: {}\n\
         Label styles: {}\n\
         Author-date styles: {}",
        numeric.join(", "),
        superscript.join(", "),
        label.join(", "),
        author_date.join(", ")
    )
}

/// Runtime-detected style characteristics for styles not in the registry.
///
/// Unlike `StyleInfo`, this is computed at runtime from CSL metadata.
/// Superscript detection is not possible from CSL alone, so rendering defaults to `Bracketed`.
#[derive(Debug, Clone, Copy)]
pub struct DetectedStyleFormat {
    /// Citation format (content type + rendering) detected from CSL metadata
    format: CitationFormat,
}

impl CitationStyle for DetectedStyleFormat {
    fn citation_format(&self) -> CitationFormat {
        self.format
    }
}

/// Detect citation format characteristics from a CSL style's metadata.
///
/// Examines the style's `info.category` to find the `CitationFormat` and determines
/// whether the style uses numeric, label, or author-date citations. Superscript cannot
/// be detected from CSL metadata alone, so rendering always defaults to `Bracketed`.
///
/// # Arguments
/// * `style` - The loaded CSL IndependentStyle
///
/// # Returns
/// `DetectedStyleFormat` with content type based on CSL metadata.
///
/// # Citation Format Mapping
/// - `CslCitationFormat::Numeric` → `CitationContentType::Numeric` (e.g., IEEE `[1]`)
/// - `CslCitationFormat::Label` → `CitationContentType::Label` (e.g., `[Smi24]`)
/// - `CslCitationFormat::AuthorDate` → `CitationContentType::AuthorDate` (e.g., `(Smith, 2024)`)
/// - `CslCitationFormat::Author` → `CitationContentType::AuthorDate` (e.g., `(Smith)`)
/// - `CslCitationFormat::Note` → `CitationContentType::AuthorDate` (footnote styles)
pub fn detect_style_format(style: &IndependentStyle) -> DetectedStyleFormat {
    let csl_format = style.info.category.iter().find_map(|cat| match cat {
        StyleCategory::CitationFormat { format } => Some(*format),
        StyleCategory::Field { .. } => None,
    });

    let content = match csl_format {
        Some(CslCitationFormat::Numeric) => CitationContentType::Numeric,
        Some(CslCitationFormat::Label) => CitationContentType::Label,
        _ => CitationContentType::AuthorDate,
    };

    DetectedStyleFormat {
        format: CitationFormat {
            content,
            rendering: CitationRendering::Bracketed, // Cannot detect from CSL metadata
        },
    }
}