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/// Java Properties format parser
9pub mod properties_parser;
10
11/// INI format parser
12pub mod ini_parser;
13
14#[cfg(feature = "json")]
15pub mod json_parser;
16
17/// XML format parser (enterprise feature)
18#[cfg(feature = "xml")]
19pub mod xml_parser;
20
21/// HCL format parser (HashiCorp Configuration Language)
22#[cfg(feature = "hcl")]
23pub mod hcl_parser;
24
25// TOML and NOML parsers now enabled with published crate
26#[cfg(feature = "toml")]
27pub mod toml_parser;
28
29#[cfg(feature = "noml")]
30pub mod noml_parser;
31
32use crate::error::{Error, Result};
33use crate::value::Value;
34use std::path::Path;
35
36/// Parse configuration from a string with optional format hint
37/// Uses zero-copy AST parser for enterprise performance
38pub fn parse_string(source: &str, format: Option<&str>) -> Result<Value> {
39    let detected_format = format.unwrap_or_else(|| detect_format(source));
40
41    match detected_format {
42        "conf" => conf::parse(source),
43        "properties" => properties_parser::parse(source),
44        "ini" => ini_parser::parse(source),
45        #[cfg(feature = "json")]
46        "json" => json_parser::parse(source),
47        #[cfg(feature = "xml")]
48        "xml" => xml_parser::parse(source),
49        #[cfg(feature = "hcl")]
50        "hcl" => hcl_parser::parse(source),
51        #[cfg(feature = "noml")]
52        "noml" => noml_parser::parse(source),
53        #[cfg(feature = "toml")]
54        "toml" => toml_parser::parse(source),
55        _ => {
56            #[cfg(not(feature = "json"))]
57            if detected_format == "json" {
58                return Err(Error::feature_not_enabled("json"));
59            }
60
61            #[cfg(not(feature = "xml"))]
62            if detected_format == "xml" {
63                return Err(Error::feature_not_enabled("xml"));
64            }
65
66            #[cfg(not(feature = "hcl"))]
67            if detected_format == "hcl" {
68                return Err(Error::feature_not_enabled("hcl"));
69            }
70
71            #[cfg(not(feature = "noml"))]
72            if detected_format == "noml" {
73                return Err(Error::feature_not_enabled("noml"));
74            }
75
76            #[cfg(not(feature = "toml"))]
77            if detected_format == "toml" {
78                return Err(Error::feature_not_enabled("toml"));
79            }
80
81            // For now, treat everything else as conf format
82            conf::parse(source)
83        }
84    }
85}
86
87/// Parse configuration from a file with automatic format detection
88pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<Value> {
89    let path = path.as_ref();
90    let content =
91        std::fs::read_to_string(path).map_err(|e| Error::io(path.display().to_string(), e))?;
92
93    let format = detect_format_from_path(path).or_else(|| Some(detect_format(&content)));
94
95    parse_string(&content, format)
96}
97
98/// Async version of parse_file
99#[cfg(feature = "async")]
100pub async fn parse_file_async<P: AsRef<Path>>(path: P) -> Result<Value> {
101    let path = path.as_ref();
102    let content = tokio::fs::read_to_string(path)
103        .await
104        .map_err(|e| Error::io(path.display().to_string(), e))?;
105
106    let format = detect_format_from_path(path).or_else(|| Some(detect_format(&content)));
107
108    parse_string(&content, format)
109}
110
111/// Detect configuration format from file path
112pub fn detect_format_from_path(path: &Path) -> Option<&'static str> {
113    path.extension()
114        .and_then(|ext| ext.to_str())
115        .map(|ext| match ext.to_lowercase().as_str() {
116            "conf" | "config" | "cfg" => "conf",
117            "properties" => "properties",
118            "ini" => "ini",
119            "toml" => "toml",
120            "json" => "json",
121            "noml" => "noml",
122            "xml" => "xml",
123            "hcl" | "tf" => "hcl", // .tf files are Terraform HCL
124            _ => "conf",           // Default to conf for unknown extensions
125        })
126}
127
128/// Detect configuration format from content
129pub fn detect_format(content: &str) -> &'static str {
130    let trimmed = content.trim();
131
132    // XML detection - starts with < and contains XML tags
133    if trimmed.starts_with('<') && contains_xml_features(content) {
134        return "xml";
135    }
136
137    // JSON detection - starts with { or [
138    if trimmed.starts_with('{') || trimmed.starts_with('[') {
139        return "json";
140    }
141
142    // HCL detection - look for HCL-specific features
143    if contains_hcl_features(content) {
144        return "hcl";
145    }
146
147    // NOML detection - look for NOML-specific features
148    if contains_noml_features(content) {
149        return "noml";
150    }
151
152    // INI detection - look for section headers (before properties since INI can use colons)
153    if contains_ini_features(content) {
154        return "ini";
155    }
156
157    // Properties detection - look for properties-specific features
158    if contains_properties_features(content) {
159        return "properties";
160    }
161
162    // TOML detection - look for TOML-specific syntax
163    if contains_toml_features(content) {
164        return "toml";
165    }
166
167    // Default to conf format
168    "conf"
169}
170
171/// Check if content contains NOML-specific features
172fn contains_noml_features(content: &str) -> bool {
173    // Look for NOML-specific syntax
174    content.contains("env(")
175        || content.contains("include ")
176        || content.contains("${")
177        || content.contains("@size(")
178        || content.contains("@duration(")
179        || content.contains("@url(")
180        || content.contains("@ip(")
181}
182
183/// Check if content contains Properties-specific features
184fn contains_properties_features(content: &str) -> bool {
185    content.lines().any(|line| {
186        let trimmed = line.trim();
187        // Properties comments with ! (specific to Java Properties)
188        if trimmed.starts_with('!') {
189            return true;
190        }
191        // Properties escape sequences (more specific than CONF)
192        if trimmed.contains("\\n") || trimmed.contains("\\t") || trimmed.contains("\\u") {
193            return true;
194        }
195        // Properties use : separator more commonly than CONF
196        if trimmed.contains(':') && !trimmed.contains('=') && !trimmed.starts_with('#') {
197            return true;
198        }
199        false
200    })
201}
202
203/// Check if content contains INI-specific features
204fn contains_ini_features(content: &str) -> bool {
205    let mut has_section = false;
206    let mut has_ini_comment = false;
207    let mut has_key_value_in_section = false;
208    let mut in_section = false;
209
210    for line in content.lines() {
211        let trimmed = line.trim();
212
213        // INI section headers [section] but not TOML arrays
214        if trimmed.starts_with('[') && trimmed.ends_with(']') && !trimmed.contains('=') {
215            // Make sure it's not a TOML array of tables
216            let section_content = &trimmed[1..trimmed.len() - 1];
217            if !section_content.contains('[') && !section_content.contains(']') {
218                has_section = true;
219                in_section = true;
220                continue;
221            }
222        }
223
224        // INI comments with ; (TOML uses #)
225        if trimmed.starts_with(';') {
226            has_ini_comment = true;
227        }
228
229        // Key-value pairs in sections
230        if in_section
231            && (trimmed.contains('=') || trimmed.contains(':'))
232            && !trimmed.starts_with('#')
233            && !trimmed.starts_with(';')
234        {
235            has_key_value_in_section = true;
236        }
237    }
238
239    // INI is likely if we have sections with key-value pairs OR semicolon comments
240    has_section && has_key_value_in_section || has_ini_comment
241}
242
243/// Check if content contains TOML-specific features
244fn contains_toml_features(content: &str) -> bool {
245    // Look for TOML-specific syntax patterns
246    content.lines().any(|line| {
247        let trimmed = line.trim();
248        // TOML section headers
249        if trimmed.starts_with('[') && trimmed.ends_with(']') && !trimmed.contains('=') {
250            return true;
251        }
252        // TOML datetime format
253        if trimmed.contains("T") && trimmed.contains("Z") {
254            return true;
255        }
256        false
257    })
258}
259
260/// Check if content contains XML-specific features
261fn contains_xml_features(content: &str) -> bool {
262    let trimmed = content.trim();
263
264    // Look for XML declaration
265    if trimmed.starts_with("<?xml") {
266        return true;
267    }
268
269    // Look for closing XML tags
270    if trimmed.contains("</") {
271        return true;
272    }
273
274    // Look for XML namespaces
275    if trimmed.contains("xmlns") {
276        return true;
277    }
278
279    // Look for self-closing tags
280    if trimmed.contains("/>") {
281        return true;
282    }
283
284    // Check for balanced XML structure
285    let open_tags = trimmed.matches('<').count();
286    let close_tags = trimmed.matches('>').count();
287
288    // Basic XML structure validation
289    open_tags > 0 && close_tags > 0 && open_tags <= close_tags
290}
291
292/// Check if content contains HCL-specific features
293fn contains_hcl_features(content: &str) -> bool {
294    // Look for HCL-specific syntax patterns
295    for line in content.lines() {
296        let trimmed = line.trim();
297
298        // HCL block syntax: resource "type" "name" {
299        if trimmed.contains(" \"") && trimmed.contains("\" {") {
300            return true;
301        }
302
303        // HCL variable/output blocks
304        if trimmed.starts_with("variable ") || trimmed.starts_with("output ") {
305            return true;
306        }
307
308        // HCL resource/data blocks
309        if trimmed.starts_with("resource ") || trimmed.starts_with("data ") {
310            return true;
311        }
312
313        // HCL provider blocks
314        if trimmed.starts_with("provider ") {
315            return true;
316        }
317
318        // HCL terraform blocks
319        if trimmed.starts_with("terraform ") {
320            return true;
321        }
322
323        // HCL module blocks
324        if trimmed.starts_with("module ") {
325            return true;
326        }
327
328        // HCL functions and interpolation
329        if trimmed.contains("${") && trimmed.contains("}") {
330            return true;
331        }
332    }
333
334    false
335}