#[cfg(test)]
mod tests;
use std::collections::HashMap;
use std::fs;
use std::sync::Mutex;
thread_local! {
static GLOBAL_INI: Mutex<HashMap<String, String>> = Mutex::new(HashMap::new());
}
#[derive(Debug, Clone)]
pub struct IniEntry {
pub name: String,
pub value: String,
pub section: Option<String>,
}
pub struct IniParser {
entries: HashMap<String, IniEntry>,
current_section: Option<String>,
}
impl IniParser {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
current_section: None,
}
}
pub fn parse_file(&mut self, filename: &str) -> Result<(), String> {
let content =
fs::read_to_string(filename).map_err(|e| format!("Failed to read INI file: {e}"))?;
self.parse_string(&content)
}
pub fn parse_string(&mut self, content: &str) -> Result<(), String> {
for (line_num, line) in content.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with(';') || line.starts_with('#') {
continue;
}
if line.starts_with('[') && line.ends_with(']') {
let section = &line[1..line.len() - 1];
self.current_section = Some(section.to_string());
continue;
}
if let Some(equal_pos) = line.find('=') {
let key = line[..equal_pos].trim().to_string();
let value = line[equal_pos + 1..].trim().to_string();
let value = value.trim_matches(|c| c == '"' || c == '\'');
let entry = IniEntry {
name: key.clone(),
value: value.to_string(),
section: self.current_section.clone(),
};
let map_key = if let Some(ref section) = self.current_section {
format!("{section}.{key}")
} else {
key
};
self.entries.insert(map_key, entry);
} else {
return Err(format!("Invalid INI syntax at line {line_num}: {line}"));
}
}
Ok(())
}
pub fn get(&self, name: &str) -> Option<&IniEntry> {
self.entries.get(name)
}
pub fn get_all(&self) -> Vec<&IniEntry> {
self.entries.values().collect()
}
pub fn get_section(&self, section: &str) -> Vec<&IniEntry> {
self.entries
.values()
.filter(|e| e.section.as_ref().map(|s| s == section).unwrap_or(false))
.collect()
}
}
impl Default for IniParser {
fn default() -> Self {
Self::new()
}
}
pub fn php_ini_parse_file(filename: &str) -> Result<Vec<IniEntry>, String> {
let mut parser = IniParser::new();
parser.parse_file(filename)?;
Ok(parser.get_all().into_iter().cloned().collect())
}
pub fn php_ini_get(name: &str) -> Option<String> {
GLOBAL_INI.with(|ini| {
let ini = ini.lock().unwrap();
ini.get(name).cloned()
})
}
pub fn php_ini_set(name: &str, value: &str) -> Result<(), String> {
GLOBAL_INI.with(|ini| {
let mut ini = ini.lock().unwrap();
ini.insert(name.to_string(), value.to_string());
Ok(())
})
}