use regex::Regex;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LinkageInfo {
pub tag: String,
pub occurrence: String,
pub script_id: String,
pub is_reverse: bool,
}
impl LinkageInfo {
#[must_use]
pub fn parse(value: &str) -> Option<Self> {
let pattern = Regex::new(r"^(\d{3})-(\d{2,3})(?:/([\(\$][A-Za-z0-9]))?(?:/r)?$").ok()?;
let caps = pattern.captures(value)?;
let tag = caps.get(1)?.as_str().to_string();
let occurrence = caps.get(2)?.as_str().to_string();
let script_id = caps
.get(3)
.map(|m| m.as_str().to_string())
.unwrap_or_default();
let is_reverse = value.ends_with("/r");
Some(LinkageInfo {
tag,
occurrence,
script_id,
is_reverse,
})
}
#[must_use]
pub fn tag(&self) -> &str {
&self.tag
}
#[must_use]
pub fn occurrence(&self) -> &str {
&self.occurrence
}
#[must_use]
pub fn script_id(&self) -> &str {
&self.script_id
}
#[must_use]
pub fn is_reverse(&self) -> bool {
self.is_reverse
}
#[must_use]
pub fn for_reverse_link(&self) -> String {
self.occurrence.clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_basic_linkage() {
let info = LinkageInfo::parse("100-01").unwrap();
assert_eq!(info.tag, "100");
assert_eq!(info.occurrence, "01");
assert_eq!(info.script_id, "");
assert!(!info.is_reverse);
}
#[test]
fn test_parse_with_reverse_flag() {
let info = LinkageInfo::parse("100-01/r").unwrap();
assert_eq!(info.tag, "100");
assert_eq!(info.occurrence, "01");
assert!(info.is_reverse);
}
#[test]
fn test_parse_different_occurrences() {
let info = LinkageInfo::parse("245-02").unwrap();
assert_eq!(info.tag, "245");
assert_eq!(info.occurrence, "02");
let info = LinkageInfo::parse("650-03").unwrap();
assert_eq!(info.tag, "650");
assert_eq!(info.occurrence, "03");
}
#[test]
fn test_parse_880_tag() {
let info = LinkageInfo::parse("880-01").unwrap();
assert_eq!(info.tag, "880");
assert_eq!(info.occurrence, "01");
}
#[test]
fn test_parse_hebrew_script_code() {
let info = LinkageInfo::parse("245-01/(2/r").unwrap();
assert_eq!(info.tag, "245");
assert_eq!(info.occurrence, "01");
assert_eq!(info.script_id, "(2");
assert!(info.is_reverse);
}
#[test]
fn test_parse_arabic_script_code() {
let info = LinkageInfo::parse("100-02/(3/r").unwrap();
assert_eq!(info.tag, "100");
assert_eq!(info.occurrence, "02");
assert_eq!(info.script_id, "(3");
assert!(info.is_reverse);
}
#[test]
fn test_parse_cjk_script_code() {
let info = LinkageInfo::parse("245-01/$1").unwrap();
assert_eq!(info.tag, "245");
assert_eq!(info.occurrence, "01");
assert_eq!(info.script_id, "$1");
assert!(!info.is_reverse);
}
#[test]
fn test_parse_cyrillic_script_code() {
let info = LinkageInfo::parse("245-01/(N").unwrap();
assert_eq!(info.tag, "245");
assert_eq!(info.occurrence, "01");
assert_eq!(info.script_id, "(N");
assert!(!info.is_reverse);
}
#[test]
fn test_parse_greek_script_code() {
let info = LinkageInfo::parse("245-01/(S").unwrap();
assert_eq!(info.tag, "245");
assert_eq!(info.occurrence, "01");
assert_eq!(info.script_id, "(S");
assert!(!info.is_reverse);
}
#[test]
fn test_parse_latin_script_code() {
let info = LinkageInfo::parse("245-01/(B").unwrap();
assert_eq!(info.tag, "245");
assert_eq!(info.occurrence, "01");
assert_eq!(info.script_id, "(B");
assert!(!info.is_reverse);
}
#[test]
fn test_parse_script_code_without_reverse() {
let info = LinkageInfo::parse("260-03/(2").unwrap();
assert_eq!(info.tag, "260");
assert_eq!(info.occurrence, "03");
assert_eq!(info.script_id, "(2");
assert!(!info.is_reverse);
}
#[test]
fn test_parse_invalid_format_no_dash() {
assert!(LinkageInfo::parse("10001").is_none());
}
#[test]
fn test_parse_invalid_format_wrong_digits() {
assert!(LinkageInfo::parse("10-01").is_none());
assert!(LinkageInfo::parse("1000-01").is_none());
}
#[test]
fn test_parse_invalid_format_wrong_occurrence() {
assert!(LinkageInfo::parse("100-1").is_none()); assert!(LinkageInfo::parse("100-").is_none()); }
#[test]
fn test_parse_empty_string() {
assert!(LinkageInfo::parse("").is_none());
}
#[test]
fn test_parse_leading_zeros() {
let info = LinkageInfo::parse("100-01").unwrap();
assert_eq!(info.occurrence, "01");
let info = LinkageInfo::parse("650-99").unwrap();
assert_eq!(info.occurrence, "99");
}
#[test]
fn test_parse_three_digit_occurrence() {
let info = LinkageInfo::parse("100-001").unwrap();
assert_eq!(info.occurrence, "001");
}
#[test]
fn test_for_reverse_link() {
let info = LinkageInfo::parse("100-01").unwrap();
assert_eq!(info.for_reverse_link(), "01");
}
#[test]
fn test_accessors() {
let info = LinkageInfo::parse("245-02/(2/r").unwrap();
assert_eq!(info.tag(), "245");
assert_eq!(info.occurrence(), "02");
assert_eq!(info.script_id(), "(2");
assert!(info.is_reverse());
}
#[test]
fn test_equality() {
let info1 = LinkageInfo::parse("100-01").unwrap();
let info2 = LinkageInfo::parse("100-01").unwrap();
let info3 = LinkageInfo::parse("100-02").unwrap();
assert_eq!(info1, info2);
assert_ne!(info1, info3);
}
#[test]
fn test_clone() {
let info1 = LinkageInfo::parse("100-01/r").unwrap();
let info2 = info1.clone();
assert_eq!(info1, info2);
}
}