use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::path::Path;
use crate::{ConfDirective, ConfOptions, parse};
#[derive(Debug)]
pub enum MapperError {
ParseError(String),
SerializeError(String),
IoError(io::Error),
ConversionError(String),
MissingField(String),
}
impl Error for MapperError {}
impl fmt::Display for MapperError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MapperError::ParseError(msg) => write!(f, "Parse error: {}", msg),
MapperError::SerializeError(msg) => write!(f, "Serialization error: {}", msg),
MapperError::IoError(err) => write!(f, "I/O error: {}", err),
MapperError::ConversionError(msg) => write!(f, "Conversion error: {}", msg),
MapperError::MissingField(name) => write!(f, "Missing required field: {}", name),
}
}
}
impl From<io::Error> for MapperError {
fn from(error: io::Error) -> Self {
MapperError::IoError(error)
}
}
impl From<crate::ConfError> for MapperError {
fn from(error: crate::ConfError) -> Self {
MapperError::ParseError(error.to_string())
}
}
pub trait FromConf: Sized {
fn from_directive(directive: &ConfDirective) -> Result<Self, MapperError>;
fn from_str(s: &str) -> Result<Self, MapperError> {
let options = MapperOptions::default().parser_options;
let conf_unit = parse(s, options)?;
if conf_unit.directives.is_empty() {
return Err(MapperError::ParseError("No directives found".into()));
}
Self::from_directive(&conf_unit.directives[0])
}
fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, MapperError> {
let content = fs::read_to_string(path)?;
Self::from_str(&content)
}
}
pub trait ToConf {
fn to_directive(&self) -> Result<ConfDirective, MapperError>;
fn to_string(&self) -> Result<String, MapperError> {
let directive = self.to_directive()?;
let mut result = String::new();
serialize_directive(&directive, &mut result, 0)?;
Ok(result)
}
fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), MapperError> {
let content = self.to_string()?;
fs::write(path, content)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct MapperOptions {
pub parser_options: ConfOptions,
pub use_kebab_case: bool,
pub indent: String,
}
impl Default for MapperOptions {
fn default() -> Self {
Self {
parser_options: ConfOptions::default(),
use_kebab_case: false,
indent: " ".to_string(),
}
}
}
#[allow(dead_code)]
fn to_kebab_case(s: &str) -> String {
let mut result = String::new();
let mut prev_is_lowercase = false;
for c in s.chars() {
if c.is_uppercase() {
if prev_is_lowercase {
result.push('-');
}
result.push(c.to_lowercase().next().unwrap());
prev_is_lowercase = false;
} else {
result.push(c);
prev_is_lowercase = true;
}
}
result
}
#[allow(dead_code)]
fn from_kebab_case(s: &str) -> String {
let mut result = String::new();
let mut capitalize_next = false;
for c in s.chars() {
if c == '-' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_uppercase().next().unwrap());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
fn serialize_directive(
directive: &ConfDirective,
output: &mut String,
depth: usize,
) -> Result<(), MapperError> {
let indent = " ".repeat(depth);
output.push_str(&indent);
output.push_str(&directive.name.value);
for arg in &directive.arguments {
output.push(' ');
if arg.is_quoted {
output.push('"');
output.push_str(&arg.value);
output.push('"');
} else {
output.push_str(&arg.value);
}
}
if directive.children.is_empty() {
output.push_str(";\n");
} else {
output.push_str(" {\n");
for child in &directive.children {
serialize_directive(child, output, depth + 1)?;
}
output.push_str(&indent);
output.push_str("}\n");
}
Ok(())
}
pub trait ValueConverter: Sized {
fn from_conf_value(value: &str) -> Result<Self, MapperError>;
fn to_conf_value(&self) -> Result<String, MapperError>;
}
impl ValueConverter for String {
fn from_conf_value(value: &str) -> Result<Self, MapperError> {
Ok(value.to_string())
}
fn to_conf_value(&self) -> Result<String, MapperError> {
Ok(self.clone())
}
}
impl ValueConverter for bool {
fn from_conf_value(value: &str) -> Result<Self, MapperError> {
match value.to_lowercase().as_str() {
"true" | "yes" | "on" | "1" => Ok(true),
"false" | "no" | "off" | "0" => Ok(false),
_ => Err(MapperError::ConversionError(format!(
"Cannot convert '{}' to bool",
value
))),
}
}
fn to_conf_value(&self) -> Result<String, MapperError> {
Ok(self.to_string())
}
}
impl ValueConverter for i32 {
fn from_conf_value(value: &str) -> Result<Self, MapperError> {
value.parse::<i32>().map_err(|e| {
MapperError::ConversionError(format!("Cannot convert '{}' to i32: {}", value, e))
})
}
fn to_conf_value(&self) -> Result<String, MapperError> {
Ok(self.to_string())
}
}
impl ValueConverter for f64 {
fn from_conf_value(value: &str) -> Result<Self, MapperError> {
value.parse::<f64>().map_err(|e| {
MapperError::ConversionError(format!("Cannot convert '{}' to f64: {}", value, e))
})
}
fn to_conf_value(&self) -> Result<String, MapperError> {
Ok(self.to_string())
}
}
impl<T: ValueConverter> ValueConverter for Option<T> {
fn from_conf_value(value: &str) -> Result<Self, MapperError> {
if value.trim().is_empty() {
Ok(None)
} else {
Ok(Some(T::from_conf_value(value)?))
}
}
fn to_conf_value(&self) -> Result<String, MapperError> {
match self {
Some(val) => val.to_conf_value(),
None => Ok("".to_string()),
}
}
}
impl<T: ValueConverter> ValueConverter for Vec<T> {
fn from_conf_value(value: &str) -> Result<Self, MapperError> {
let values = value
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| T::from_conf_value(s))
.collect::<Result<Vec<T>, _>>()?;
Ok(values)
}
fn to_conf_value(&self) -> Result<String, MapperError> {
let values: Result<Vec<String>, _> = self.iter().map(|val| val.to_conf_value()).collect();
Ok(values?.join(", "))
}
}