cooklang_reports/parser.rs
1//! Global parser instance for efficient recipe parsing.
2//!
3//! This module provides a singleton `CooklangParser` instance that is initialized once
4//! and reused throughout the application, improving performance by avoiding repeated
5//! parser initialization.
6
7use cooklang::{Converter, CooklangParser};
8use std::sync::OnceLock;
9
10/// Global `CooklangParser` instance that is initialized once and reused throughout the application.
11/// This improves performance by avoiding repeated parser initialization.
12static PARSER: OnceLock<CooklangParser> = OnceLock::new();
13
14/// Get the global `CooklangParser` instance.
15///
16/// The parser is initialized with all extensions enabled and an empty converter
17/// (no unit conversions). This allows parsing all recipe features while keeping
18/// units as-is without conversions.
19/// This function is thread-safe and will only initialize the parser once.
20///
21/// # Example
22/// ```no_run
23/// use cooklang_reports::parser::get_parser;
24///
25/// let parser = get_parser();
26/// let (recipe, warnings) = parser.parse("@eggs{2}").into_result().unwrap();
27/// ```
28pub fn get_parser() -> &'static CooklangParser {
29 PARSER.get_or_init(CooklangParser::canonical)
30}
31
32/// Get the converter from the global parser.
33///
34/// This is a convenience function that returns the converter from the global parser instance.
35#[must_use]
36pub fn get_converter() -> &'static Converter {
37 get_parser().converter()
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn test_global_parser_singleton() {
46 // Get parser multiple times and ensure it's the same instance
47 let parser1 = get_parser();
48 let parser2 = get_parser();
49
50 // Both should be the same instance (same memory address)
51 assert!(std::ptr::eq(parser1, parser2));
52 }
53
54 #[test]
55 fn test_parser_works() {
56 let parser = get_parser();
57 let recipe = "@eggs{2} and @milk{250%ml}";
58 let (parsed, _warnings) = parser.parse(recipe).into_result().unwrap();
59
60 assert_eq!(parsed.ingredients.len(), 2);
61 assert_eq!(parsed.ingredients[0].name, "eggs");
62 assert_eq!(parsed.ingredients[1].name, "milk");
63 }
64
65 #[test]
66 fn test_converter_access() {
67 let converter = get_converter();
68 // Just ensure we can get the converter without panic
69 // The converter should be accessible and functional
70 // Try to iterate over units to make sure it works
71 let _units: Vec<_> = converter.all_units().collect();
72 // If we get here without panic, the test passes
73 }
74}