rtx-cli 2024.0.0

Polyglot runtime manager (asdf rust clone)
use std::collections::HashMap;
use std::sync::RwLock;

use eyre::WrapErr;
use eyre::{eyre, Result};

use crate::cache::CacheManager;
use crate::config::Config;
use crate::hash::hash_to_str;
use crate::plugins::ExternalPlugin;
use crate::tera::{get_tera, BASE_CONTEXT};
use crate::toolset::{ToolVersion, ToolVersionRequest};
use crate::{dirs, env};

#[derive(Debug, Default)]
pub struct ExternalPluginCache {
    list_bin_paths: RwLock<HashMap<ToolVersionRequest, CacheManager<Vec<String>>>>,
    exec_env: RwLock<HashMap<ToolVersionRequest, CacheManager<HashMap<String, String>>>>,
}

impl ExternalPluginCache {
    pub fn list_bin_paths<F>(
        &self,
        plugin: &ExternalPlugin,
        tv: &ToolVersion,
        fetch: F,
    ) -> Result<Vec<String>>
    where
        F: FnOnce() -> Result<Vec<String>>,
    {
        let mut w = self.list_bin_paths.write().unwrap();
        let cm = w.entry(tv.request.clone()).or_insert_with(|| {
            let list_bin_paths_filename = match &plugin.toml.list_bin_paths.cache_key {
                Some(key) => {
                    let config = Config::get();
                    let key = render_cache_key(&config, tv, key);
                    let filename = format!("{}.msgpack.z", key);
                    tv.cache_path().join("list_bin_paths").join(filename)
                }
                None => tv.cache_path().join("list_bin_paths.msgpack.z"),
            };
            CacheManager::new(list_bin_paths_filename)
                .with_fresh_file(dirs::DATA.to_path_buf())
                .with_fresh_file(plugin.plugin_path.clone())
                .with_fresh_file(tv.install_path())
        });
        cm.get_or_try_init(fetch).cloned()
    }

    pub fn exec_env<F>(
        &self,
        config: &Config,
        plugin: &ExternalPlugin,
        tv: &ToolVersion,
        fetch: F,
    ) -> Result<HashMap<String, String>>
    where
        F: FnOnce() -> Result<HashMap<String, String>>,
    {
        let mut w = self.exec_env.write().unwrap();
        let cm = w.entry(tv.request.clone()).or_insert_with(|| {
            let exec_env_filename = match &plugin.toml.exec_env.cache_key {
                Some(key) => {
                    let key = render_cache_key(config, tv, key);
                    let filename = format!("{}.msgpack.z", key);
                    tv.cache_path().join("exec_env").join(filename)
                }
                None => tv.cache_path().join("exec_env.msgpack.z"),
            };
            CacheManager::new(exec_env_filename)
                .with_fresh_file(dirs::DATA.to_path_buf())
                .with_fresh_file(plugin.plugin_path.clone())
                .with_fresh_file(tv.install_path())
        });
        cm.get_or_try_init(fetch).cloned()
    }
}

fn render_cache_key(config: &Config, tv: &ToolVersion, cache_key: &[String]) -> String {
    let elements = cache_key
        .iter()
        .map(|tmpl| {
            let s = parse_template(config, tv, tmpl).unwrap();
            let s = s.trim().to_string();
            trace!("cache key element: {} -> {}", tmpl.trim(), s);
            let mut s = hash_to_str(&s);
            s.truncate(10);
            s
        })
        .collect::<Vec<String>>();
    elements.join("-")
}

fn parse_template(config: &Config, tv: &ToolVersion, tmpl: &str) -> Result<String> {
    let mut ctx = BASE_CONTEXT.clone();
    ctx.insert("project_root", &config.project_root);
    ctx.insert("opts", &tv.opts);
    get_tera(
        config
            .project_root
            .as_ref()
            .unwrap_or(&env::current_dir().unwrap()),
    )
    .render_str(tmpl, &ctx)
    .wrap_err_with(|| eyre!("failed to parse template: {tmpl}"))
}