#[cfg(any(feature = "runtime-ranges", feature = "ranges"))] use std::env; #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] use std::fs::File; #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] use std::io::{Read, Write}; #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] use std::path::Path; #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] use codegen::{Block, Function, Scope}; #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] use roxmltree::{Document, Node}; #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] const ALLOW_LINTS: &str = r###" #[allow(clippy::unreadable_literal)] #[allow(clippy::match_same_arms)] #[allow(clippy::too_many_lines)] "###; /// EAN.UCC prefix or registration group. #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] struct Group { agency: String, prefix: [u8; 3], registration_group_element: Vec, rules: Vec, } /// Range length rule. #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] struct Rule { min: u32, max: u32, length: usize, } /// Parse registration group and registrant range length rules. #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] fn parse_rules(group: Node) -> Vec { group .descendants() .filter(|n| n.has_tag_name("Rule")) .map(|r| { let range: Vec = r .descendants() .find(|n| n.has_tag_name("Range")) .unwrap() .text() .unwrap() .split('-') .map(|i| i.parse().unwrap()) .collect(); let length = r .descendants() .find(|n| n.has_tag_name("Length")) .unwrap() .text() .unwrap() .parse() .unwrap(); assert!(length < 8, "Segment length can be at most 7."); Rule { min: range[0], max: range[1], length, } }) .collect() } /// Parse EAN.UCC prefix and registration group element. #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] fn parse_group(group: Node) -> Group { let prefix_str = group .descendants() .find(|n| n.has_tag_name("Prefix")) .unwrap() .text() .unwrap(); let mut prefix = [0; 3]; let mut registration_group_element = Vec::new(); for (i, c) in prefix_str.chars().enumerate() { if i < 3 { prefix[i] = c.to_digit(10).unwrap() as u8; } if i >= 4 { registration_group_element.push(c.to_digit(10).unwrap() as u8) } } let agency = group .descendants() .find(|n| n.has_tag_name("Agency")) .unwrap() .text() .unwrap() .to_string(); Group { agency, prefix, registration_group_element, rules: parse_rules(group), } } /// Generate code for EAN.UCC or registration group lookup. #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] fn codegen_find_group(name: &str, groups: Vec, check_registration_group: bool) -> Function { let mut fn_get_group = Function::new(name); fn_get_group.arg("prefix", "u16"); if check_registration_group { fn_get_group.arg("registration_group_element", "u32"); } fn_get_group.arg("segment", "u32"); fn_get_group.ret("Result, IsbnError>"); let mut match_prefix = if check_registration_group { Block::new("match (prefix, registration_group_element)") } else { Block::new("match prefix") }; for group in groups { match_prefix.line(if check_registration_group { format!( "({:#X}, {:#X}) =>", ((group.prefix[0] as u16) << 8) | ((group.prefix[1] as u16) << 4) | (group.prefix[2] as u16), { let mut digits = 0u32; for &digit in &group.registration_group_element { digits = (digits << 4) | digit as u32; } digits } ) } else { format!( "{:#X} =>", ((group.prefix[0] as u16) << 8) | ((group.prefix[1] as u16) << 4) | (group.prefix[2] as u16) ) }); let mut let_length_eq_match_segment = Block::new("let length = match segment"); for rule in &group.rules { let_length_eq_match_segment.line(match rule.length { 0 => format!( "{} ..= {} => Err(IsbnError::UndefinedRange),", rule.min, rule.max ), _ => format!("{} ..= {} => Ok({}),", rule.min, rule.max, rule.length), }); } let_length_eq_match_segment.line("_ => Err(IsbnError::InvalidGroup)"); let_length_eq_match_segment.after(";"); let mut ok_group = Block::new("Ok(Group"); ok_group.line(format!("name: \"{}\",", group.agency)); ok_group.line("segment_length: length?"); ok_group.after(")"); let mut segment_match_block = Block::new(""); segment_match_block.push_block(let_length_eq_match_segment); segment_match_block.push_block(ok_group); match_prefix.push_block(segment_match_block); } match_prefix.line("_ => Err(IsbnError::InvalidGroup)"); fn_get_group.push_block(match_prefix); fn_get_group } #[cfg(any(feature = "runtime-ranges", feature = "ranges"))] fn main() { let mut f = File::open("./isbn-ranges/RangeMessage.xml").unwrap(); let mut text = String::new(); f.read_to_string(&mut text).unwrap(); let options = roxmltree::ParsingOptions { allow_dtd: true, ..Default::default() }; let range_message = Document::parse_with_options(&text, options).unwrap(); let ean_ucc_groups = range_message .descendants() .filter(|d| d.tag_name().name() == "EAN.UCC") .map(parse_group) .collect(); let registration_groups = range_message .descendants() .filter(|d| d.tag_name().name() == "Group") .map(parse_group) .collect(); let mut scope = Scope::new(); let impl_isbn = scope.new_impl("Isbn"); impl_isbn.push_fn(codegen_find_group( "get_ean_ucc_group", ean_ucc_groups, false, )); impl_isbn.push_fn(codegen_find_group( "get_registration_group", registration_groups, true, )); let out_dir = env::var("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("generated.rs"); let mut f = File::create(&dest_path).unwrap(); f.write_all(ALLOW_LINTS.trim_start().as_bytes()).unwrap(); writeln!(f).unwrap(); f.write_all(scope.to_string().as_bytes()).unwrap(); } #[cfg(not(any(feature = "runtime-ranges", feature = "ranges")))] fn main() {}