pub mod parser;
pub mod error;
pub mod value;
pub use parser::{Parser, ParserBuilder};
pub use error::{TuskError, TuskResult};
pub use value::{Value, ValueType};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
pub type Config = HashMap<String, Value>;
pub fn parse(input: &str) -> TuskResult<Config> {
let mut parser = Parser::new();
parser.parse(input)
}
pub fn parse_into<T>(input: &str) -> TuskResult<T>
where
T: for<'de> Deserialize<'de>,
{
let config = parse(input)?;
let json = serde_json::to_value(config)?;
Ok(T::deserialize(json)?)
}
pub fn serialize(config: &Config) -> TuskResult<String> {
let mut output = String::new();
serialize_config(config, &mut output, 0)?;
Ok(output)
}
fn serialize_config(config: &Config, output: &mut String, indent: usize) -> TuskResult<()> {
let indent_str = " ".repeat(indent);
for (key, value) in config {
output.push_str(&indent_str);
output.push_str(key);
output.push_str(": ");
match value {
Value::String(s) => {
output.push('"');
output.push_str(s);
output.push('"');
}
Value::Number(n) => {
output.push_str(&n.to_string());
}
Value::Boolean(b) => {
output.push_str(&b.to_string());
}
Value::Array(arr) => {
output.push('\n');
for item in arr {
output.push_str(&indent_str);
output.push_str(" - ");
serialize_value(item, output)?;
output.push('\n');
}
output.pop(); continue;
}
Value::Object(obj) => {
output.push('\n');
serialize_config(obj, output, indent + 1)?;
continue;
}
Value::Null => {
output.push_str("null");
}
}
output.push('\n');
}
Ok(())
}
fn serialize_value(value: &Value, output: &mut String) -> TuskResult<()> {
match value {
Value::String(s) => {
output.push('"');
output.push_str(s);
output.push('"');
}
Value::Number(n) => {
output.push_str(&n.to_string());
}
Value::Boolean(b) => {
output.push_str(&b.to_string());
}
Value::Array(arr) => {
output.push('[');
for (i, item) in arr.iter().enumerate() {
if i > 0 {
output.push_str(", ");
}
serialize_value(item, output)?;
}
output.push(']');
}
Value::Object(obj) => {
output.push('{');
for (i, (key, val)) in obj.iter().enumerate() {
if i > 0 {
output.push_str(", ");
}
output.push('"');
output.push_str(key);
output.push_str("\": ");
serialize_value(val, output)?;
}
output.push('}');
}
Value::Null => {
output.push_str("null");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_parsing() {
let input = r#"
app_name: "Test App"
version: "1.0.0"
debug: true
port: 8080
"#;
let result = parse(input).unwrap();
assert_eq!(result.get("app_name").unwrap(), &Value::String("Test App".to_string()));
assert_eq!(result.get("version").unwrap(), &Value::String("1.0.0".to_string()));
assert_eq!(result.get("debug").unwrap(), &Value::Boolean(true));
assert_eq!(result.get("port").unwrap(), &Value::Number(8080.0));
}
#[test]
fn test_nested_parsing() {
let input = r#"
database:
host: "localhost"
port: 5432
name: "testdb"
"#;
let result = parse(input).unwrap();
let database = result.get("database").unwrap().as_object().unwrap();
assert_eq!(database.get("host").unwrap(), &Value::String("localhost".to_string()));
assert_eq!(database.get("port").unwrap(), &Value::Number(5432.0));
assert_eq!(database.get("name").unwrap(), &Value::String("testdb".to_string()));
}
#[test]
fn test_array_parsing() {
let input = r#"
features:
- logging
- metrics
- caching
"#;
let result = parse(input).unwrap();
let features = result.get("features").unwrap().as_array().unwrap();
assert_eq!(features.len(), 3);
assert_eq!(features[0], Value::String("logging".to_string()));
assert_eq!(features[1], Value::String("metrics".to_string()));
assert_eq!(features[2], Value::String("caching".to_string()));
}
}