mod compound;
mod grouping;
#[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::matching::Matcher;
use super::rendering::{CompoundRenderData, Renderer, RendererResources};
use super::run_state::FinalizedRun;
use super::{ProcessedReferences, Processor};
use crate::api::AnnotationStyle;
use crate::reference::Reference;
use crate::render::format::OutputFormat;
use crate::render::{ProcEntry, ProcTemplate};
use crate::values::ProcHints;
use citum_schema::grouping::BibliographyGroup;
use citum_schema::options::{Config, bibliography::BibliographyConfig};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Clone, Default)]
pub(crate) struct RenderedBibliographyGroup {
pub(crate) heading: Option<String>,
pub(crate) body: String,
pub(crate) entries: Vec<crate::render::ProcEntry>,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct DocumentBibliography {
pub(crate) content: String,
pub(crate) entries: Vec<crate::render::ProcEntry>,
}
#[cfg(feature = "parallel")]
pub(crate) const PARALLEL_MIN_ENTRIES: usize = 32;
fn number_sorted_refs<'a>(
sorted_refs: impl Iterator<Item = &'a Reference>,
run: &FinalizedRun,
) -> Vec<(&'a Reference, usize)> {
let numbers = run
.state()
.citation_numbers
.read()
.unwrap_or_else(std::sync::PoisonError::into_inner);
sorted_refs
.enumerate()
.map(|(index, reference)| {
let entry_number = numbers
.get(reference.id().unwrap_or_default().as_str())
.copied()
.unwrap_or(index + 1);
(reference, entry_number)
})
.collect()
}
struct EntryRenderContext<'a> {
style: &'a citum_schema::Style,
hints: &'a HashMap<String, ProcHints>,
config: Arc<Config>,
bibliography_config: Arc<BibliographyConfig>,
run: &'a FinalizedRun,
}
impl Processor {
fn effective_custom_groups(&self) -> Option<&[BibliographyGroup]> {
self.style
.bibliography
.as_ref()
.filter(|bibliography| bibliography.groups_enabled)
.and_then(|bibliography| bibliography.groups.as_deref())
}
fn flat_render_context<'a>(&'a self, run: &'a FinalizedRun) -> EntryRenderContext<'a> {
EntryRenderContext {
style: &self.style,
hints: &self.hints,
config: Arc::new(self.get_bibliography_config().into_owned()),
bibliography_config: Arc::new(self.get_bibliography_options().into_owned()),
run,
}
}
fn entry_renderer<'a>(&'a self, ctx: &EntryRenderContext<'a>) -> Renderer<'a> {
Renderer::new(
RendererResources {
style: ctx.style,
bibliography: &self.bibliography,
locale: &self.locale,
config: ctx.config.clone(),
bibliography_config: Some(ctx.bibliography_config.clone()),
first_note_by_id: None,
},
ctx.hints,
&ctx.run.state().citation_numbers,
CompoundRenderData {
set_by_ref: &self.compound_set_by_ref,
member_index: &self.compound_member_index,
sets: &self.compound_sets,
},
self.show_semantics,
self.inject_ast_indices,
self.abbreviation_map.as_ref(),
)
}
fn render_numbered_refs<'a, F>(
&self,
numbered_refs: &[(&'a Reference, usize)],
ctx: &EntryRenderContext<'_>,
) -> Vec<(&'a Reference, Option<ProcTemplate>)>
where
F: OutputFormat<Output = String>,
{
#[cfg(feature = "parallel")]
if numbered_refs.len() >= PARALLEL_MIN_ENTRIES {
return self.render_numbered_refs_parallel::<F>(numbered_refs, ctx);
}
self.render_numbered_refs_sequential::<F>(numbered_refs, ctx)
}
fn render_numbered_refs_sequential<'a, F>(
&self,
numbered_refs: &[(&'a Reference, usize)],
ctx: &EntryRenderContext<'_>,
) -> Vec<(&'a Reference, Option<ProcTemplate>)>
where
F: OutputFormat<Output = String>,
{
let renderer = self.entry_renderer(ctx);
numbered_refs
.iter()
.map(|&(reference, entry_number)| {
(
reference,
renderer.process_bibliography_entry_with_format::<F>(reference, entry_number),
)
})
.collect()
}
#[cfg(feature = "parallel")]
fn render_numbered_refs_parallel<'a, F>(
&self,
numbered_refs: &[(&'a Reference, usize)],
ctx: &EntryRenderContext<'_>,
) -> Vec<(&'a Reference, Option<ProcTemplate>)>
where
F: OutputFormat<Output = String>,
{
use rayon::prelude::*;
numbered_refs
.par_iter()
.map(|&(reference, entry_number)| {
let renderer = self.entry_renderer(ctx);
(
reference,
renderer.process_bibliography_entry_with_format::<F>(reference, entry_number),
)
})
.collect()
}
fn with_bibliography_renderer<T>(
&self,
run: &FinalizedRun,
render: impl FnOnce(Renderer<'_>) -> T,
) -> T {
let bibliography_shared_config = self.get_bibliography_config();
let bibliography_config = self.get_bibliography_options().into_owned();
let renderer = Renderer::new(
RendererResources {
style: &self.style,
bibliography: &self.bibliography,
locale: &self.locale,
config: Arc::new(bibliography_shared_config.into_owned()),
bibliography_config: Some(Arc::new(bibliography_config)),
first_note_by_id: None,
},
&self.hints,
&run.state().citation_numbers,
CompoundRenderData {
set_by_ref: &self.compound_set_by_ref,
member_index: &self.compound_member_index,
sets: &self.compound_sets,
},
self.show_semantics,
self.inject_ast_indices,
self.abbreviation_map.as_ref(),
);
render(renderer)
}
fn process_sorted_refs<'a, I, F>(&self, sorted_refs: I, run: &FinalizedRun) -> Vec<ProcEntry>
where
I: Iterator<Item = &'a Reference>,
F: OutputFormat<Output = String>,
{
let ctx = self.flat_render_context(run);
let numbered_refs = number_sorted_refs(sorted_refs, run);
let rendered = self.render_numbered_refs::<F>(&numbered_refs, &ctx);
let substitute = ctx
.bibliography_config
.subsequent_author_substitute
.as_ref();
self.apply_substitution_post_pass::<F>(rendered, substitute, &ctx)
}
fn apply_substitution_post_pass<F>(
&self,
rendered: Vec<(&Reference, Option<ProcTemplate>)>,
substitute: Option<&String>,
ctx: &EntryRenderContext<'_>,
) -> Vec<ProcEntry>
where
F: OutputFormat<Output = String>,
{
let renderer = substitute.map(|_| self.entry_renderer(ctx));
let mut bibliography = Vec::with_capacity(rendered.len());
let mut previous_reference: Option<&Reference> = None;
for (reference, processed) in rendered {
let Some(mut processed) = processed else {
continue;
};
if let Some(substitute_string) = substitute
&& let Some(renderer) = renderer.as_ref()
&& let Some(previous) = previous_reference
&& self.contributors_match(previous, reference)
{
renderer
.apply_author_substitution_with_format::<F>(&mut processed, substitute_string);
}
let ref_id = reference.id().unwrap_or_default().to_string();
bibliography.push(ProcEntry {
id: ref_id,
template: processed,
metadata: self.extract_metadata(reference, ctx),
});
previous_reference = Some(reference);
}
bibliography
}
pub fn process_references(&self) -> ProcessedReferences {
let run = self.begin_run().finalize();
self.process_references_with_format::<crate::render::plain::PlainText>(&run)
}
pub fn process_references_with_format<F>(&self, run: &FinalizedRun) -> ProcessedReferences
where
F: OutputFormat<Output = String>,
{
let sorted_refs = self.sort_references(self.bibliography.values().collect());
let bibliography = self.process_sorted_refs::<_, F>(sorted_refs.iter().copied(), run);
ProcessedReferences {
bibliography,
citations: None,
}
}
pub(crate) fn process_selected_references_with_format<F, I>(
&self,
item_ids: I,
run: &FinalizedRun,
) -> ProcessedReferences
where
F: OutputFormat<Output = String>,
I: IntoIterator<Item = String>,
{
let selected: HashSet<String> = item_ids.into_iter().collect();
let sorted_refs = self.sort_references(self.bibliography.values().collect());
let bibliography = self.process_sorted_refs::<_, F>(
sorted_refs
.iter()
.filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
.copied(),
run,
);
ProcessedReferences {
bibliography,
citations: None,
}
}
pub fn process_bibliography_entry(
&self,
reference: &Reference,
entry_number: usize,
run: &FinalizedRun,
) -> Option<ProcTemplate> {
self.with_bibliography_renderer(run, |renderer| {
renderer.process_bibliography_entry(reference, entry_number)
})
}
pub fn process_bibliography_entry_with_format<F>(
&self,
reference: &Reference,
entry_number: usize,
run: &FinalizedRun,
) -> Option<ProcTemplate>
where
F: OutputFormat<Output = String>,
{
self.with_bibliography_renderer(run, |renderer| {
renderer.process_bibliography_entry_with_format::<F>(reference, entry_number)
})
}
pub fn contributors_match(&self, prev: &Reference, current: &Reference) -> bool {
let matcher = Matcher::new(&self.style, &self.default_config);
matcher.contributors_match(prev, current)
}
pub fn render_bibliography_with_format<F>(&self, run: &FinalizedRun) -> String
where
F: OutputFormat<Output = String>,
{
self.render_bibliography_with_format_and_annotations::<F>(None, None, run)
}
pub fn render_bibliography_with_format_and_annotations<F>(
&self,
annotations: Option<&HashMap<String, String>>,
annotation_style: Option<&AnnotationStyle>,
run: &FinalizedRun,
) -> String
where
F: OutputFormat<Output = String>,
{
self.render_selected_bibliography_with_format_and_annotations::<F, _>(
self.bibliography.keys().cloned().collect::<Vec<_>>(),
annotations,
annotation_style,
run,
)
}
pub fn render_selected_bibliography_with_format<F, I>(
&self,
item_ids: I,
run: &FinalizedRun,
) -> String
where
F: OutputFormat<Output = String>,
I: IntoIterator<Item = String>,
{
self.render_selected_bibliography_with_format_and_annotations::<F, _>(
item_ids, None, None, run,
)
}
pub fn render_selected_bibliography_with_format_and_annotations<F, I>(
&self,
item_ids: I,
annotations: Option<&HashMap<String, String>>,
annotation_style: Option<&AnnotationStyle>,
run: &FinalizedRun,
) -> String
where
F: OutputFormat<Output = String>,
I: IntoIterator<Item = String>,
{
let selected: HashSet<String> = item_ids.into_iter().collect();
if let Some(groups) = self.effective_custom_groups() {
let all_entries = self.sorted_id_stubs();
return self.render_with_custom_groups_filtered::<F>(
&all_entries,
groups,
&selected,
annotations,
annotation_style,
run,
);
}
let bibliography_options = self.get_bibliography_options();
if let Some(partitioning) = bibliography_options.sort_partitioning.as_ref()
&& crate::sort_partitioning::should_render_sections(partitioning)
{
let all_sorted = self.sort_references(self.bibliography.values().collect());
let selected_sorted: Vec<&Reference> = all_sorted
.into_iter()
.filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
.collect();
return self.render_with_partition_sections::<F>(
selected_sorted,
partitioning,
annotations,
annotation_style,
run,
);
}
let sorted_refs = self.sort_references(self.bibliography.values().collect());
let bibliography = self.process_sorted_refs::<_, F>(
sorted_refs
.iter()
.filter(|r| r.id().as_deref().is_some_and(|id| selected.contains(id)))
.copied(),
run,
);
let bibliography = self.merge_compound_entries::<F>(bibliography, run);
crate::render::refs_to_string_with_format::<F>(bibliography, annotations, annotation_style)
}
pub fn render_bibliography(&self) -> String {
let run = self.begin_run().finalize();
self.render_bibliography_with_format::<crate::render::plain::PlainText>(&run)
}
pub fn process_references_with_format_standalone<F>(&self) -> ProcessedReferences
where
F: OutputFormat<Output = String>,
{
let run = self.begin_run().finalize();
self.process_references_with_format::<F>(&run)
}
pub fn process_bibliography_entry_standalone(
&self,
reference: &Reference,
entry_number: usize,
) -> Option<ProcTemplate> {
let run = self.begin_run().finalize();
self.process_bibliography_entry(reference, entry_number, &run)
}
pub fn process_bibliography_entry_with_format_standalone<F>(
&self,
reference: &Reference,
entry_number: usize,
) -> Option<ProcTemplate>
where
F: OutputFormat<Output = String>,
{
let run = self.begin_run().finalize();
self.process_bibliography_entry_with_format::<F>(reference, entry_number, &run)
}
pub fn render_bibliography_with_format_standalone<F>(&self) -> String
where
F: OutputFormat<Output = String>,
{
let run = self.begin_run().finalize();
self.render_bibliography_with_format::<F>(&run)
}
pub fn render_bibliography_with_format_and_annotations_standalone<F>(
&self,
annotations: Option<&HashMap<String, String>>,
annotation_style: Option<&AnnotationStyle>,
) -> String
where
F: OutputFormat<Output = String>,
{
let run = self.begin_run().finalize();
self.render_bibliography_with_format_and_annotations::<F>(
annotations,
annotation_style,
&run,
)
}
pub fn render_selected_bibliography_with_format_standalone<F, I>(&self, item_ids: I) -> String
where
F: OutputFormat<Output = String>,
I: IntoIterator<Item = String>,
{
let run = self.begin_run().finalize();
self.render_selected_bibliography_with_format::<F, I>(item_ids, &run)
}
pub fn render_selected_bibliography_with_format_and_annotations_standalone<F, I>(
&self,
item_ids: I,
annotations: Option<&HashMap<String, String>>,
annotation_style: Option<&AnnotationStyle>,
) -> String
where
F: OutputFormat<Output = String>,
I: IntoIterator<Item = String>,
{
let run = self.begin_run().finalize();
self.render_selected_bibliography_with_format_and_annotations::<F, I>(
item_ids,
annotations,
annotation_style,
&run,
)
}
}