#![allow(missing_docs, reason = "test")]
#![allow(
dead_code,
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 test, benchmark, and example code."
)]
use std::{fs, path::PathBuf};
use citum_engine::Processor;
use citum_io::load_bibliography;
use citum_schema::{
CitationSpec, Style, StyleInfo,
citation::{Citation, CitationItem, CitationMode},
reference::{
Contributor, ContributorList, EdtfString, InputReference as Reference, Monograph,
MonographType, MultilingualString, Serial, SerialComponent, SerialComponentType,
SerialType, StructuredName, Title, WorkRelation,
},
};
pub fn make_book(id: &str, family: &str, given: &str, year: i32, title: &str) -> Reference {
citum_schema::ref_book!(id, family, given, year, title)
}
pub fn make_book_multi_author(
id: &str,
authors: Vec<(&str, &str)>,
year: i32,
title: &str,
) -> Reference {
let author_list: Vec<Contributor> = authors
.into_iter()
.map(|(family, given)| {
Contributor::StructuredName(StructuredName {
family: MultilingualString::Simple(family.to_string()),
given: MultilingualString::Simple(given.to_string()),
suffix: None,
dropping_particle: None,
non_dropping_particle: None,
})
})
.collect();
Reference::Monograph(Box::new(Monograph {
id: Some(id.into()),
r#type: MonographType::Book,
title: Some(Title::Single(title.to_string())),
author: Some(Contributor::ContributorList(ContributorList(author_list))),
issued: EdtfString(year.to_string()),
..Default::default()
}))
}
pub fn make_article(id: &str, family: &str, given: &str, year: i32, title: &str) -> Reference {
citum_schema::ref_article!(id, family, given, year, title)
}
pub fn make_article_multi_author(
id: &str,
authors: Vec<(&str, &str)>,
year: i32,
title: &str,
) -> Reference {
let author_list: Vec<Contributor> = authors
.into_iter()
.map(|(family, given)| {
Contributor::StructuredName(StructuredName {
family: MultilingualString::Simple(family.to_string()),
given: MultilingualString::Simple(given.to_string()),
suffix: None,
dropping_particle: None,
non_dropping_particle: None,
})
})
.collect();
Reference::SerialComponent(Box::new(SerialComponent {
id: Some(id.into()),
r#type: SerialComponentType::Article,
title: Some(Title::Single(title.to_string())),
author: Some(Contributor::ContributorList(ContributorList(author_list))),
issued: EdtfString(year.to_string()),
container: Some(WorkRelation::Embedded(Box::new(Reference::Serial(
Box::new(Serial {
r#type: SerialType::AcademicJournal,
title: Some(Title::Single(String::new())),
..Default::default()
}),
)))),
..Default::default()
}))
}
pub struct MultilingualBookParams<'a> {
pub id: &'a str,
pub original_family: &'a str,
pub original_given: &'a str,
pub lang: &'a str,
pub translit_script: &'a str,
pub translit_family: &'a str,
pub translit_given: &'a str,
pub year: i32,
pub title: &'a str,
}
pub fn make_multilingual_book(params: MultilingualBookParams) -> Reference {
use citum_schema::reference::contributor::MultilingualName;
use std::collections::HashMap;
let mut transliterations = HashMap::new();
transliterations.insert(
params.translit_script.to_string(),
StructuredName {
family: MultilingualString::Simple(params.translit_family.to_string()),
given: MultilingualString::Simple(params.translit_given.to_string()),
suffix: None,
dropping_particle: None,
non_dropping_particle: None,
},
);
Reference::Monograph(Box::new(Monograph {
id: Some(params.id.into()),
r#type: MonographType::Book,
title: Some(Title::Single(params.title.to_string())),
author: Some(Contributor::Multilingual(MultilingualName {
original: StructuredName {
family: MultilingualString::Simple(params.original_family.to_string()),
given: MultilingualString::Simple(params.original_given.to_string()),
..Default::default()
},
lang: Some(params.lang.into()),
transliterations,
translations: HashMap::new(),
})),
issued: EdtfString(params.year.to_string()),
..Default::default()
}))
}
pub fn run_test_case_native(
input: &[Reference],
citation_items: &[Vec<&str>],
expected: &str,
mode: &str,
) {
run_test_case_native_with_options(TestCaseOptions {
input,
citation_items,
expected,
mode,
disambiguate_year_suffix: true,
disambiguate_names: false,
disambiguate_givenname: false,
et_al_min: None,
et_al_use_first: None,
});
}
pub struct TestCaseOptions<'a> {
pub input: &'a [Reference],
pub citation_items: &'a [Vec<&'a str>],
pub expected: &'a str,
pub mode: &'a str,
pub disambiguate_year_suffix: bool,
pub disambiguate_names: bool,
pub disambiguate_givenname: bool,
pub et_al_min: Option<u8>,
pub et_al_use_first: Option<u8>,
}
pub fn run_test_case_native_with_options(options: TestCaseOptions) {
let style = build_author_date_style(
options.disambiguate_year_suffix,
options.disambiguate_names,
options.disambiguate_givenname,
options.et_al_min,
options.et_al_use_first,
);
let mut bibliography = indexmap::IndexMap::new();
for item in options.input {
if let Some(id) = item.id() {
bibliography.insert(id.to_string(), item.clone());
}
}
let processor = Processor::new(style, bibliography);
if options.mode == "citation" {
let mut results = Vec::new();
for batch in options.citation_items {
let items: Vec<CitationItem> = batch
.iter()
.map(|id| CitationItem {
id: (*id).to_string(),
..Default::default()
})
.collect();
let citation = Citation {
items,
mode: CitationMode::NonIntegral,
..Default::default()
};
let res = processor
.process_citation(&citation)
.expect("Failed to process citation");
results.push(res);
}
let actual = results.join("\n");
assert_eq!(
actual.trim(),
options.expected.trim(),
"Citation output mismatch"
);
} else if options.mode == "bibliography" {
if !options.citation_items.is_empty() {
for batch in options.citation_items {
let items: Vec<CitationItem> = batch
.iter()
.map(|id| CitationItem {
id: (*id).to_string(),
..Default::default()
})
.collect();
let citation = Citation {
items,
..Default::default()
};
processor.process_citation(&citation).ok();
}
}
let actual = processor.render_bibliography();
assert_eq!(
actual.trim(),
options.expected.trim(),
"Bibliography output mismatch"
);
}
}
pub fn project_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
}
pub fn load_style(path: &str) -> Style {
let style_path = project_root().join(path);
let yaml = fs::read_to_string(&style_path)
.unwrap_or_else(|err| panic!("failed to read style {}: {err}", style_path.display()));
Style::from_yaml_str(&yaml)
.unwrap_or_else(|err| panic!("failed to parse style {}: {err}", style_path.display()))
}
pub fn load_example_bibliography() -> indexmap::IndexMap<String, Reference> {
load_bibliography(&project_root().join("examples/document-refs.json"))
.expect("example bibliography should parse")
}
pub fn load_example_document(path: &str) -> String {
fs::read_to_string(project_root().join(path))
.unwrap_or_else(|err| panic!("failed to read example document {path}: {err}"))
}
pub fn example_document_processor(style_path: &str) -> Processor {
Processor::new(load_style(style_path), load_example_bibliography())
}
pub fn announce_behavior(summary: &str) {
tracing::debug!("behavior: {summary}");
}
pub fn build_author_date_style(
disambiguate_year_suffix: bool,
disambiguate_names: bool,
disambiguate_givenname: bool,
et_al_min: Option<u8>,
et_al_use_first: Option<u8>,
) -> Style {
use citum_schema::options::{
Config, ContributorConfig, Disambiguation, Processing, ProcessingCustom, ShortenListOptions,
};
use citum_schema::template::WrapPunctuation;
let disambiguate = if disambiguate_year_suffix || disambiguate_names || disambiguate_givenname {
Some(Disambiguation {
year_suffix: disambiguate_year_suffix,
names: disambiguate_names,
add_givenname: disambiguate_givenname,
})
} else {
None
};
let contributors = Some(ContributorConfig {
shorten: if et_al_min.is_some() || et_al_use_first.is_some() {
Some(ShortenListOptions {
min: et_al_min.unwrap_or(3),
use_first: et_al_use_first.unwrap_or(1),
..Default::default()
})
} else {
None
},
initialize_with: Some(" ".to_string()),
name_form: Some(citum_schema::options::NameForm::Initials),
..Default::default()
});
let citation_template = vec![
citum_schema::tc_contributor!(Author, Short),
citum_schema::tc_date!(Issued, Year, wrap = WrapPunctuation::Parentheses),
];
Style {
info: StyleInfo {
title: Some("Author-Date Disambiguation Test".to_string()),
id: Some("http://test.example/disambiguation".into()),
..Default::default()
},
options: Some(Config {
processing: Some(Processing::Custom(ProcessingCustom {
disambiguate,
..Default::default()
})),
contributors,
..Default::default()
}),
citation: Some(CitationSpec {
sort: Some(citum_schema::grouping::GroupSortEntry::Explicit(
citum_schema::grouping::GroupSort {
template: vec![
citum_schema::grouping::GroupSortKey {
key: citum_schema::grouping::SortKey::Author,
ascending: true,
order: None,
sort_order: None,
},
citum_schema::grouping::GroupSortKey {
key: citum_schema::grouping::SortKey::Issued,
ascending: true,
order: None,
sort_order: None,
},
],
},
)),
template: Some(citation_template),
multi_cite_delimiter: Some("; ".to_string()),
..Default::default()
}),
..Default::default()
}
}