config_lib/parsers/
mod.rs

1//! # Configuration Format Parsers
2//!
3//! Multi-format configuration parsing with automatic format detection
4//! and high-performance implementations.
5
6pub mod conf;
7
8#[cfg(feature = "json")]
9pub mod json_parser;
10
11// Disabled for now - require external NOML crate
12// pub mod toml_parser;
13// pub mod noml_parser;
14
15use crate::error::{Error, Result};
16use crate::value::Value;
17use std::path::Path;
18
19/// Parse configuration from a string with optional format hint
20/// Uses zero-copy AST parser for enterprise performance
21pub fn parse_string(source: &str, format: Option<&str>) -> Result<Value> {
22    let detected_format = format.unwrap_or_else(|| detect_format(source));
23
24    match detected_format {
25        "conf" => conf::parse(source),
26        #[cfg(feature = "json")]
27        "json" => json_parser::parse(source),
28        _ => {
29            #[cfg(not(feature = "json"))]
30            if detected_format == "json" {
31                return Err(Error::feature_not_enabled("json"));
32            }
33            
34            // For now, treat everything else as conf format
35            conf::parse(source)
36        }
37    }
38}
39
40/// Parse configuration from a file with automatic format detection
41pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Value> {
42    let path = path.as_ref();
43    let content = std::fs::read_to_string(path)
44        .map_err(|e| Error::io(path.display().to_string(), e))?;
45
46    let format = detect_format_from_path(path)
47        .or_else(|| Some(detect_format(&content)));
48
49    parse_string(&content, format)
50}
51
52/// Async version of parse_file
53#[cfg(feature = "async")]
54pub async fn parse_file_async<P: AsRef<Path>>(path: P) -> Result<Value> {
55    let path = path.as_ref();
56    let content = tokio::fs::read_to_string(path)
57        .await
58        .map_err(|e| Error::io(path.display().to_string(), e))?;
59
60    let format = detect_format_from_path(path)
61        .or_else(|| Some(detect_format(&content)));
62
63    parse_string(&content, format)
64}
65
66/// Detect configuration format from file path
67pub fn detect_format_from_path(path: &Path) -> Option<&'static str> {
68    path.extension()
69        .and_then(|ext| ext.to_str())
70        .map(|ext| match ext.to_lowercase().as_str() {
71            "conf" | "config" | "cfg" => "conf",
72            "toml" => "toml",
73            "json" => "json",
74            "noml" => "noml",
75            _ => "conf", // Default to conf for unknown extensions
76        })
77}
78
79/// Detect configuration format from content
80pub fn detect_format(content: &str) -> &'static str {
81    let trimmed = content.trim();
82    
83    // JSON detection - starts with { or [
84    if trimmed.starts_with('{') || trimmed.starts_with('[') {
85        return "json";
86    }
87    
88    // NOML detection - look for NOML-specific features
89    if contains_noml_features(content) {
90        return "noml";
91    }
92    
93    // TOML detection - look for TOML-specific syntax
94    if contains_toml_features(content) {
95        return "toml";
96    }
97    
98    // Default to conf format
99    "conf"
100}
101
102/// Check if content contains NOML-specific features
103fn contains_noml_features(content: &str) -> bool {
104    // Look for NOML-specific syntax
105    content.contains("env(") ||
106    content.contains("include ") ||
107    content.contains("${") ||
108    content.contains("@size(") ||
109    content.contains("@duration(") ||
110    content.contains("@url(") ||
111    content.contains("@ip(")
112}
113
114/// Check if content contains TOML-specific features
115fn contains_toml_features(content: &str) -> bool {
116    // Look for TOML-specific syntax patterns
117    content.lines().any(|line| {
118        let trimmed = line.trim();
119        // TOML section headers
120        if trimmed.starts_with('[') && trimmed.ends_with(']') && !trimmed.contains('=') {
121            return true;
122        }
123        // TOML datetime format
124        if trimmed.contains("T") && trimmed.contains("Z") {
125            return true;
126        }
127        false
128    })
129}