rblur 0.1.0

一個支援網頁配置介面的輕量級網頁伺服器
Documentation
use serde_json::{json, Value};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};

use crate::core::config::command::Command;

pub static REGISTERED_COMMANDS: OnceLock<Mutex<HashMap<String, Arc<Command>>>> = OnceLock::new();

fn get_registry() -> MutexGuard<'static, HashMap<String, Arc<Command>>> {
    REGISTERED_COMMANDS
        .get_or_init(|| Mutex::new(HashMap::new()))
        .lock()
        .unwrap()
}

fn param_to_json(p: &crate::core::config::command::Parameter) -> Value {
    json!({
        "index": p.index,
        "display_name": p.display_name,
        "type": p.type_name,
        "is_required": p.is_required,
        "default": p.default,
        "value": p.default,
        "desc": p.desc,
    })
}

fn command_to_json(
    cmd: &Command,
    recursive: bool,
    registry: &HashMap<String, Arc<Command>>,
) -> Value {
    let mut obj = json!({
        "is_block": cmd.is_block,
        "unique": cmd.unique,
        "display_name": cmd.display_name,
        "desc": cmd.desc,
        "params": cmd.params.iter().map(param_to_json).collect::<Vec<Value>>()
    });

    if cmd.is_block && recursive {
        let children = build_children_template(registry, &cmd.name, recursive);
        obj.as_object_mut()
            .unwrap()
            .insert("children".to_string(), children);
    }
    obj
}

pub fn register_command(cmd: Command) {
    let mut reg = get_registry();
    if reg.contains_key(&cmd.name) {
        panic!("Command {} is already registered", cmd.name);
    }
    reg.insert(cmd.name.clone(), Arc::new(cmd));
}

pub fn get_command(name: &str) -> Option<Arc<Command>> {
    let reg = get_registry();
    reg.get(name).cloned()
}

pub fn get_block_json(block_name: &str, recursive: bool) -> Option<Value> {
    let reg = get_registry();
    reg.get(block_name)
        .map(|cmd| command_to_json(cmd, recursive, &reg))
}

fn build_children_template(
    registry: &HashMap<String, Arc<Command>>,
    parent: &str,
    recursive: bool,
) -> Value {
    let mut children_map = serde_json::Map::new();

    for cmd in registry.values() {
        if cmd.allowed_parents.contains(&parent.to_string()) {
            let child_json = command_to_json(cmd, recursive, registry);
            if cmd.unique {
                children_map.insert(cmd.name.clone(), child_json);
            } else {
                children_map
                    .entry(cmd.name.clone())
                    .and_modify(|e| {
                        if let Value::Array(arr) = e {
                            arr.push(child_json.clone());
                        }
                    })
                    .or_insert_with(|| Value::Array(vec![child_json]));
            }
        }
    }
    Value::Object(children_map)
}

pub struct ConfigManager;

impl ConfigManager {
    pub fn get_complete_template(top_blocks: Vec<String>) -> Result<Value, String> {
        let registry_data: HashMap<String, Arc<Command>> = {
            let reg = get_registry();
            reg.clone()
        };
        let mut map = serde_json::Map::new();

        for block in top_blocks {
            let cmd = registry_data
                .get(&block)
                .ok_or_else(|| format!("Block {} not registered", block))?;
            let template = get_block_json(&block, true)
                .ok_or_else(|| format!("No template for block {}", block))?;

            let final_template = if !cmd.unique {
                match template {
                    Value::Object(_) => Value::Array(vec![template]),
                    Value::Array(arr) if arr.is_empty() => Value::Array(vec![json!({})]),
                    Value::Array(arr) => Value::Array(arr),
                    other => other,
                }
            } else {
                template
            };

            map.insert(block, final_template);
        }
        Ok(Value::Object(map))
    }

    pub fn get_block_template(block_name: &str, recursive: bool) -> Option<Value> {
        get_block_json(block_name, recursive)
    }
}

pub fn bool_str_to_bool(value: &str) -> Result<bool, String> {
    match value.to_lowercase().as_str() {
        "on" | "true" => Ok(true),
        "off" | "false" => Ok(false),
        _ => Err(format!("Invalid boolean value: {}", value)),
    }
}

pub fn get_config_param(config: &Value, index: usize) -> Option<String> {
    config
        .get("params")
        .and_then(|arr| arr.as_array())
        .and_then(|arr| arr.get(index))
        .and_then(|param| param.get("value"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
}