sora-ir 0.3.0

Simple and powerful configuration table compiler for games and data-heavy tools.
Documentation
use std::{collections::BTreeMap, fmt};

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConfigIr {
    pub package: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub localization: Option<LocalizationIr>,
    pub enums: Vec<EnumIr>,
    pub structs: Vec<StructIr>,
    pub unions: Vec<UnionIr>,
    pub tables: Vec<TableIr>,
}

impl ConfigIr {
    pub fn data_schema(&self) -> Self {
        self.clone()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LocalizationIr {
    pub locales: Vec<String>,
    pub default_locale: String,
    pub fallback_locale: Option<String>,
    pub sources: Vec<LocalizationSourceIr>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LocalizationSourceIr {
    pub name: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub format: Option<String>,
    pub file: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sheet: Option<String>,
    pub key: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EnumIr {
    pub name: String,
    pub scope: ScopeIr,
    pub values: Vec<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub aliases: Vec<EnumAliasIr>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct EnumAliasIr {
    pub name: String,
    pub alias: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StructIr {
    pub name: String,
    pub scope: ScopeIr,
    pub fields: Vec<FieldIr>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnionIr {
    pub name: String,
    pub scope: ScopeIr,
    pub tag: String,
    pub variants: Vec<UnionVariantIr>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnionVariantIr {
    pub name: String,
    pub scope: ScopeIr,
    pub fields: Vec<FieldIr>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TableIr {
    pub name: String,
    pub scope: ScopeIr,
    pub mode: TableModeIr,
    pub key: Option<String>,
    pub source: Option<TableSourceIr>,
    pub fields: Vec<FieldIr>,
    pub indexes: Vec<IndexIr>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TableSourceIr {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub format: Option<String>,
    pub file: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sheet: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TableModeIr {
    List,
    Map,
    Singleton,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexIr {
    pub name: String,
    pub fields: Vec<String>,
    pub unique: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct FieldIr {
    pub name: String,
    pub ty: TypeIr,
    pub scope: ScopeIr,
    pub key: bool,
    pub comment: Option<String>,
    pub default: Option<String>,
    pub range: Option<[i64; 2]>,
    pub length: Option<[usize; 2]>,
    pub parser: Option<ParserIr>,
    pub derived_from: Option<DerivedFieldIr>,
}

impl FieldIr {
    pub fn is_required(&self) -> bool {
        !self.ty.is_optional() && self.default.is_none()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ParserIr {
    pub kind: String,
    pub options: BTreeMap<String, String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DerivedFieldIr {
    pub source_table: String,
    pub parent_key: String,
    pub child_key: String,
    pub value_field: Option<String>,
    pub order_by: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ScopeIr {
    pub values: Vec<String>,
}

impl Default for ScopeIr {
    fn default() -> Self {
        Self {
            values: vec!["all".to_owned()],
        }
    }
}

impl ScopeIr {
    pub fn includes(&self, target: &str) -> bool {
        target == "all"
            || self.values.iter().any(|value| value == "all")
            || self.values.iter().any(|value| value == target)
    }

    pub fn display(&self) -> String {
        self.values.join(",")
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TypeIr {
    Bool,
    I8,
    U8,
    I16,
    U16,
    I32,
    U32,
    I64,
    F32,
    F64,
    String,
    Duration,
    Text,
    Enum(String),
    Struct(String),
    Union(String),
    List(Box<TypeIr>),
    Set(Box<TypeIr>),
    Map {
        key: Box<TypeIr>,
        value: Box<TypeIr>,
    },
    Array {
        element: Box<TypeIr>,
        len: usize,
    },
    Ref {
        table: String,
        field: String,
    },
    Optional(Box<TypeIr>),
}

impl TypeIr {
    pub fn is_optional(&self) -> bool {
        matches!(self, Self::Optional(_))
    }
}

impl fmt::Display for TypeIr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TypeIr::Bool => f.write_str("bool"),
            TypeIr::I8 => f.write_str("i8"),
            TypeIr::U8 => f.write_str("u8"),
            TypeIr::I16 => f.write_str("i16"),
            TypeIr::U16 => f.write_str("u16"),
            TypeIr::I32 => f.write_str("i32"),
            TypeIr::U32 => f.write_str("u32"),
            TypeIr::I64 => f.write_str("i64"),
            TypeIr::F32 => f.write_str("f32"),
            TypeIr::F64 => f.write_str("f64"),
            TypeIr::String => f.write_str("string"),
            TypeIr::Duration => f.write_str("duration"),
            TypeIr::Text => f.write_str("text"),
            TypeIr::Enum(name) => write!(f, "enum<{name}>"),
            TypeIr::Struct(name) => write!(f, "struct<{name}>"),
            TypeIr::Union(name) => write!(f, "union<{name}>"),
            TypeIr::List(element) => write!(f, "list<{element}>"),
            TypeIr::Set(element) => write!(f, "set<{element}>"),
            TypeIr::Map { key, value } => write!(f, "map<{key},{value}>"),
            TypeIr::Array { element, len } => write!(f, "array<{element},{len}>"),
            TypeIr::Ref { table, field } => write!(f, "ref<{table}.{field}>"),
            TypeIr::Optional(element) => write!(f, "optional<{element}>"),
        }
    }
}