darklua 0.18.0

Transform Lua scripts
Documentation
use std::{
    cell::RefCell,
    collections::HashMap,
    path::{Path, PathBuf},
};

use serde::Deserialize;

use crate::{utils::normalize_path, DarkluaError, Resources};

const LUAU_RC_FILE_NAME: &str = ".luaurc";

#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq)]
pub(crate) struct LuauConfiguration {
    #[serde(default)]
    pub(crate) aliases: HashMap<String, PathBuf>,
}

fn find_luau_configuration_private(
    luau_file: &Path,
    resources: &Resources,
) -> Result<Option<LuauConfiguration>, DarkluaError> {
    log::debug!(
        "find closest {} for '{}'",
        LUAU_RC_FILE_NAME,
        luau_file.display()
    );

    for ancestor in luau_file.ancestors() {
        let config_path = ancestor.join(LUAU_RC_FILE_NAME);

        if resources.exists(&config_path)? {
            let config = resources.get(&config_path)?;
            log::trace!(
                "attempt to parse luau configuration at '{}'",
                config_path.display()
            );

            return serde_json::from_str(&config)
                .map(|mut config: LuauConfiguration| {
                    log::debug!("found luau configuration at '{}'", config_path.display());

                    config.aliases = config
                        .aliases
                        .into_iter()
                        .map(|(mut key, value)| {
                            key.insert(0, '@');
                            (key, normalize_path(ancestor.join(value)))
                        })
                        .inspect(|(key, value)| {
                            log::trace!(" ⨽ parsed alias `{}` (`{}`)", key, value.display())
                        })
                        .collect();

                    Some(config)
                })
                .map_err(Into::into);
        }
    }

    Ok(None)
}

thread_local! {
    static LUAU_RC_CACHE: RefCell<HashMap<Option<PathBuf>, Option<LuauConfiguration>>> =  RefCell::new(HashMap::new());
}

pub(crate) fn find_luau_configuration(
    luau_file: &Path,
    resources: &Resources,
) -> Result<Option<LuauConfiguration>, DarkluaError> {
    let key = luau_file.parent().map(Path::to_path_buf);

    LUAU_RC_CACHE.with(|luau_rc_cache| {
        {
            let cache = luau_rc_cache.borrow();

            let res = cache.get(&key);
            if let Some(res) = res {
                log::trace!(
                    "found luau configuration in cache for '{}'",
                    luau_file.display()
                );
                return Ok(res.clone());
            }
        }

        let mut cache = luau_rc_cache.borrow_mut();

        let value = find_luau_configuration_private(luau_file, resources)?;

        cache.insert(key, value.clone());

        Ok(value)
    })
}

pub fn clear_luau_configuration_cache() {
    LUAU_RC_CACHE.with(|luau_rc_cache| {
        let mut cache = luau_rc_cache.borrow_mut();
        cache.clear();
        log::debug!("luau configuration cache cleared");
    })
}