Skip to main content

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}