rtx-cli 2023.12.21

Polyglot runtime manager (asdf rust clone)
use confique::env::parse::{list_by_colon, list_by_comma};
use confique::{Builder, Config, Partial};

use log::LevelFilter;
use serde_derive::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::{Debug, Display, Formatter};
use std::path::PathBuf;

use crate::env;

#[derive(Config, Debug, Clone)]
pub struct Settings {
    #[config(env = "RTX_EXPERIMENTAL", default = false)]
    pub experimental: bool,
    #[config(env = "RTX_ALWAYS_KEEP_DOWNLOAD", default = false)]
    pub always_keep_download: bool,
    #[config(env = "RTX_ALWAYS_KEEP_INSTALL", default = false)]
    pub always_keep_install: bool,
    #[config(env = "RTX_LEGACY_VERSION_FILE", default = true)]
    pub legacy_version_file: bool,
    #[config(env = "RTX_LEGACY_VERSION_FILE_DISABLE_TOOLS", default = [], parse_env = list_by_comma)]
    pub legacy_version_file_disable_tools: BTreeSet<String>,
    #[config(env = "RTX_PLUGIN_AUTOUPDATE_LAST_CHECK_DURATION", default = "7d")]
    pub plugin_autoupdate_last_check_duration: String,
    #[config(env = "RTX_TRUSTED_CONFIG_PATHS", default = [], parse_env = list_by_colon)]
    pub trusted_config_paths: BTreeSet<PathBuf>,
    #[config(env = "RTX_VERBOSE", default = false)]
    pub verbose: bool,
    #[config(env = "RTX_ASDF_COMPAT", default = false)]
    pub asdf_compat: bool,
    #[config(env = "RTX_JOBS", default = 4)]
    pub jobs: usize,
    #[config(env = "RTX_SHORTHANDS_FILE")]
    pub shorthands_file: Option<PathBuf>,
    #[config(env = "RTX_DISABLE_DEFAULT_SHORTHANDS", default = false)]
    pub disable_default_shorthands: bool,
    #[config(env = "RTX_DISABLE_TOOLS", default = [], parse_env = list_by_comma)]
    pub disable_tools: BTreeSet<String>,
    #[config(env = "RTX_RAW", default = false)]
    pub raw: bool,
    #[config(env = "RTX_YES", default = false)]
    pub yes: bool,
}

pub type SettingsPartial = <Settings as Config>::Partial;

impl Default for Settings {
    fn default() -> Self {
        Settings::default_builder().load().unwrap()
    }
}
impl Settings {
    pub fn default_builder() -> Builder<Self> {
        let mut p = SettingsPartial::empty();
        if *env::CI {
            p.yes = Some(true);
        }
        if *env::RTX_LOG_LEVEL < LevelFilter::Info {
            p.verbose = Some(true);
        }
        Self::builder().preloaded(p).env()
    }

    pub fn to_index_map(&self) -> BTreeMap<String, String> {
        let mut map = BTreeMap::new();
        map.insert("experimental".to_string(), self.experimental.to_string());
        map.insert(
            "always_keep_download".to_string(),
            self.always_keep_download.to_string(),
        );
        map.insert(
            "always_keep_install".to_string(),
            self.always_keep_install.to_string(),
        );
        map.insert(
            "legacy_version_file".to_string(),
            self.legacy_version_file.to_string(),
        );
        map.insert(
            "legacy_version_file_disable_tools".to_string(),
            format!(
                "{:?}",
                self.legacy_version_file_disable_tools
                    .iter()
                    .collect::<Vec<_>>()
            ),
        );
        map.insert(
            "plugin_autoupdate_last_check_duration".to_string(),
            self.plugin_autoupdate_last_check_duration.to_string(),
        );
        map.insert(
            "trusted_config_paths".to_string(),
            format!("{:?}", self.trusted_config_paths.iter().collect::<Vec<_>>()),
        );
        map.insert("verbose".into(), self.verbose.to_string());
        map.insert("asdf_compat".into(), self.asdf_compat.to_string());
        map.insert("jobs".into(), self.jobs.to_string());
        if let Some(shorthands_file) = &self.shorthands_file {
            map.insert(
                "shorthands_file".into(),
                shorthands_file.to_string_lossy().to_string(),
            );
        }
        map.insert(
            "disable_default_shorthands".into(),
            self.disable_default_shorthands.to_string(),
        );
        map.insert(
            "disable_tools".into(),
            format!("{:?}", self.disable_tools.iter().collect::<Vec<_>>()),
        );
        map.insert("raw".into(), self.raw.to_string());
        map.insert("yes".into(), self.yes.to_string());
        map
    }
}

impl Display for Settings {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        self.to_index_map().fmt(f)
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MissingRuntimeBehavior {
    AutoInstall,
    Prompt,
    Warn,
    Ignore,
}

impl Display for MissingRuntimeBehavior {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            MissingRuntimeBehavior::AutoInstall => write!(f, "autoinstall"),
            MissingRuntimeBehavior::Prompt => write!(f, "prompt"),
            MissingRuntimeBehavior::Warn => write!(f, "warn"),
            MissingRuntimeBehavior::Ignore => write!(f, "ignore"),
        }
    }
}