Ferrit Explore
中文·繁體·EN·日本語 Sign in Register
cielxl / veld / src / config / ast.rs
/// A directive value - can be a string, number, or boolean
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
    String(String),
    Number(i64),
    Float(f64),
    Bool(bool),
    Size(u64), // e.g. "1024k", "10m"
    Time(u64), // in milliseconds, e.g. "30s", "1m"
}

impl Value {
    pub fn as_str(&self) -> &str {
        match self {
            Value::String(s) => s,
            _ => "", // Numbers don't have a string representation; use to_string() instead
        }
    }

    /// Get a string representation of the value (allocates for non-string values).
    pub fn to_string_lossy(&self) -> String {
        match self {
            Value::String(s) => s.clone(),
            Value::Number(n) => n.to_string(),
            Value::Float(f) => f.to_string(),
            Value::Bool(b) => b.to_string(),
            Value::Size(s) => s.to_string(),
            Value::Time(t) => t.to_string(),
        }
    }

    pub fn as_i64(&self) -> i64 {
        match self {
            Value::Number(n) => *n,
            Value::Float(f) => *f as i64,
            Value::Size(s) => *s as i64,
            Value::Time(t) => *t as i64,
            Value::Bool(b) => *b as i64,
            _ => 0,
        }
    }

    pub fn as_u64(&self) -> u64 {
        self.as_i64() as u64
    }

    pub fn as_usize(&self) -> usize {
        self.as_i64() as usize
    }

    pub fn as_bool(&self) -> bool {
        match self {
            Value::Bool(b) => *b,
            Value::String(s) => matches!(s.as_str(), "on" | "true" | "yes" | "1"),
            Value::Number(n) => *n != 0,
            _ => false,
        }
    }

    pub fn as_time_ms(&self) -> u64 {
        match self {
            Value::Time(t) => *t,
            Value::Number(n) => *n as u64,
            _ => 0,
        }
    }

    pub fn as_size_bytes(&self) -> u64 {
        match self {
            Value::Size(s) => *s,
            Value::Number(n) => *n as u64,
            _ => 0,
        }
    }
}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::String(s) => write!(f, "{}", s),
            Value::Number(n) => write!(f, "{}", n),
            Value::Float(fl) => write!(f, "{}", fl),
            Value::Bool(b) => write!(f, "{}", b),
            Value::Size(s) => write!(f, "{}", s),
            Value::Time(t) => write!(f, "{}", t),
        }
    }
}

/// A directive is a key-value pair with optional block
/// e.g. "worker_processes 4;" or "server { ... }"
#[derive(Debug, Clone)]
pub struct Directive {
    pub name: String,
    pub args: Vec<Value>,
    pub block: Option<Block>,
    pub line: usize,
}

impl Directive {
    pub fn new(name: String, args: Vec<Value>, line: usize) -> Self {
        Self {
            name,
            args,
            block: None,
            line,
        }
    }

    pub fn with_block(mut self, block: Block) -> Self {
        self.block = Some(block);
        self
    }

    pub fn first_arg(&self) -> Option<&Value> {
        self.args.first()
    }

    pub fn first_arg_str(&self) -> &str {
        self.args.first().map(|v| v.as_str()).unwrap_or("")
    }
}

/// A block is a collection of directives enclosed in { }
#[derive(Debug, Clone)]
pub struct Block {
    pub directives: Vec<Directive>,
}

impl Block {
    pub fn new() -> Self {
        Self {
            directives: Vec::new(),
        }
    }

    pub fn is_empty(&self) -> bool {
        self.directives.is_empty()
    }

    /// Get all directives with a given name
    pub fn get_all(&self, name: &str) -> Vec<&Directive> {
        self.directives.iter().filter(|d| d.name == name).collect()
    }

    /// Get the first directive with a given name
    pub fn get(&self, name: &str) -> Option<&Directive> {
        self.directives.iter().find(|d| d.name == name)
    }

    /// Get the first directive's first argument as string
    pub fn get_str(&self, name: &str) -> Option<&str> {
        self.get(name).map(|d| d.first_arg_str())
    }

    /// Get the first directive's first argument as i64
    pub fn get_i64(&self, name: &str) -> Option<i64> {
        self.get(name)
            .and_then(|d| d.first_arg())
            .map(|v| v.as_i64())
    }

    /// Get the first directive's first argument as u64
    pub fn get_u64(&self, name: &str) -> Option<u64> {
        self.get(name)
            .and_then(|d| d.first_arg())
            .map(|v| v.as_u64())
    }

    /// Get the first directive's first argument as bool
    pub fn get_bool(&self, name: &str) -> Option<bool> {
        self.get(name)
            .and_then(|d| d.first_arg())
            .map(|v| v.as_bool())
    }

    /// Get all blocks from directives with a given name
    pub fn get_blocks(&self, name: &str) -> Vec<&Block> {
        self.directives
            .iter()
            .filter(|d| d.name == name)
            .filter_map(|d| d.block.as_ref())
            .collect()
    }

    /// Check if a directive exists
    pub fn has(&self, name: &str) -> bool {
        self.directives.iter().any(|d| d.name == name)
    }
}

impl Default for Block {
    fn default() -> Self {
        Self::new()
    }
}

/// The root configuration
pub type Config = Block;