use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
const SPEC_TAG: &str = "v7.0.18";
fn spec_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("fixtures/vendored/gedcom-spec")
}
fn spec_file(name: &str) -> String {
let path = spec_root().join(name);
std::fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("{} ({SPEC_TAG}): {error}", path.display()))
}
fn tag_of(uri: &str) -> &str {
let term = uri.rsplit('/').next().unwrap_or(uri);
term.rsplit('-').next().unwrap_or(term)
}
fn specified_tags() -> BTreeSet<String> {
let mut tags = BTreeSet::new();
for line in spec_file("substructures.tsv").lines() {
let mut columns = line.split('\t');
let superstructure = columns.next().unwrap_or_default();
if let Some(tag) = columns.next() {
tags.insert(tag.to_owned());
}
let term = superstructure.rsplit('/').next().unwrap_or_default();
if let Some(record) = term.strip_prefix("record-") {
tags.insert(record.to_owned());
}
}
tags
}
#[test]
fn every_structure_tag_the_specification_defines_has_a_label() {
let missing: Vec<String> = specified_tags()
.into_iter()
.filter(|tag| gedcomkit::tags::label(tag).is_none())
.collect();
assert!(
missing.is_empty(),
"standard {SPEC_TAG} tags without a label: {missing:?}"
);
}
#[test]
fn the_specifications_event_list_matches_the_crates() {
let mut specified = BTreeSet::new();
for line in spec_file("enumerationsets.tsv").lines() {
let mut columns = line.split('\t');
let set = columns.next().unwrap_or_default();
let member = columns.next().unwrap_or_default();
if set.ends_with("/enumset-EVEN") {
specified.insert(tag_of(member).to_owned());
}
}
assert!(
specified.len() >= 30,
"the event set read wrong: {specified:?}"
);
for tag in &specified {
assert!(
gedcomkit::tags::is_event(tag),
"{tag} is an event in {SPEC_TAG} and not here"
);
}
for tag in [
"BIRT", "CHR", "DEAT", "BURI", "CREM", "ADOP", "BAPM", "BARM", "BASM", "BLES", "CHRA",
"CONF", "FCOM", "ORDN", "NATU", "EMIG", "IMMI", "CENS", "PROB", "WILL", "GRAD", "RETI",
"MARR", "DIV", "DIVF", "ANUL", "ENGA", "MARB", "MARC", "MARL", "MARS",
] {
assert!(
specified.contains(tag),
"the crate calls {tag} an event and {SPEC_TAG} does not"
);
}
}
#[test]
fn the_specifications_attribute_list_matches_the_crates() {
let mut events = BTreeSet::new();
let mut all = BTreeSet::new();
for line in spec_file("enumerationsets.tsv").lines() {
let mut columns = line.split('\t');
let set = columns.next().unwrap_or_default();
let member = tag_of(columns.next().unwrap_or_default()).to_owned();
if set.ends_with("/enumset-EVEN") {
events.insert(member);
} else if set.ends_with("/enumset-EVENATTR") {
all.insert(member);
}
}
let attributes: Vec<&String> = all.difference(&events).collect();
assert!(!attributes.is_empty(), "the attribute set read wrong");
for tag in attributes {
assert!(
gedcomkit::tags::is_event(tag) || gedcomkit::tags::is_attribute(tag),
"{tag} is an event or attribute in {SPEC_TAG} and neither here"
);
}
}
#[test]
fn every_enumerated_value_the_specification_defines_reads_back_as_itself_or_words() {
for line in spec_file("enumerationsets.tsv").lines() {
let mut columns = line.split('\t');
let set = columns.next().unwrap_or_default();
let value = tag_of(columns.next().unwrap_or_default());
let read = match set.rsplit('/').next().unwrap_or_default() {
"enumset-PEDI" => gedcomkit::tags::pedigree_label(value),
"enumset-QUAY" => gedcomkit::tags::certainty_label(value),
_ => continue,
};
assert!(
!read.is_empty(),
"{value} from {set} came back empty rather than labelled or kept"
);
}
}
#[test]
fn the_datatype_grammar_the_crate_hand_wrote_is_still_the_specifications() {
let grammar = spec_file("grammar.abnf");
for production in [
"date = [calendar D] [[day D] month D] year [D epoch]",
"dateRange = %s\"BET\" D date D %s\"AND\" D date",
"epoch = %s\"BCE\" / extTag",
"month = stdTag / extTag",
"Time = hour \":\" minute [\":\" second [\".\" fraction]] [%s\"Z\"]",
"Age = [[ageBound D] ageDuration]",
"Latitude = (\"N\" / \"S\") upto90 [ \".\" 1*digit]",
"Longitude = (\"N\" / \"S\") upto180 [ \".\" 1*digit]",
] {
assert!(
grammar.contains(production),
"the {SPEC_TAG} ABNF no longer contains {production:?} — \
re-read the datatype against dates.rs and validate.rs"
);
}
}