config_lib/parsers/
mod.rs1pub mod conf;
7
8#[cfg(feature = "json")]
9pub mod json_parser;
10
11use crate::error::{Error, Result};
16use crate::value::Value;
17use std::path::Path;
18
19pub 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 conf::parse(source)
36 }
37 }
38}
39
40pub 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#[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
66pub 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", })
77}
78
79pub fn detect_format(content: &str) -> &'static str {
81 let trimmed = content.trim();
82
83 if trimmed.starts_with('{') || trimmed.starts_with('[') {
85 return "json";
86 }
87
88 if contains_noml_features(content) {
90 return "noml";
91 }
92
93 if contains_toml_features(content) {
95 return "toml";
96 }
97
98 "conf"
100}
101
102fn contains_noml_features(content: &str) -> bool {
104 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
114fn contains_toml_features(content: &str) -> bool {
116 content.lines().any(|line| {
118 let trimmed = line.trim();
119 if trimmed.starts_with('[') && trimmed.ends_with(']') && !trimmed.contains('=') {
121 return true;
122 }
123 if trimmed.contains("T") && trimmed.contains("Z") {
125 return true;
126 }
127 false
128 })
129}