shine-core 2.0.2

Reusable lifecycle runtime and domain core for Shine applications
Documentation
use super::{SysDriverKind, SysItemStatus};
use crate::permission::PermissionDeclarationV1;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::PathBuf;

#[derive(Clone, Debug, Default, Deserialize)]
pub struct SysManifest {
    pub version: Option<u32>,
    #[serde(default)]
    pub description: String,
    pub default_profile: Option<String>,
    #[serde(default)]
    pub items: Vec<SysItem>,
    #[serde(default)]
    pub profiles: BTreeMap<String, SysProfile>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct SysItem {
    pub id: String,
    pub label: String,
    #[serde(default)]
    pub description: String,
    #[serde(default)]
    pub default: bool,
    #[serde(default)]
    pub mode: SysItemMode,
    #[serde(default)]
    pub requires_admin: bool,
    #[serde(default)]
    pub required_env: Vec<String>,
    #[serde(default)]
    pub driver: SysDriverKind,
    #[serde(default)]
    pub config: toml::Table,
    #[serde(default)]
    pub detect: Option<SysDetection>,
    #[serde(default)]
    pub install: Option<SysInstall>,
    #[serde(default)]
    pub shell: Vec<SysShellIntegration>,
    #[serde(default)]
    pub permissions: Option<PermissionDeclarationV1>,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum SysDetection {
    Command {
        command: String,
        #[serde(default)]
        version_args: Vec<String>,
    },
    Path {
        path: String,
    },
    Any {
        probes: Vec<SysDetectionProbe>,
    },
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum SysDetectionProbe {
    Command { command: String },
    Path { path: String },
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum SysInstall {
    Package {
        provider: SysPackageProvider,
        package: String,
        #[serde(default)]
        success_status: Option<SysItemStatus>,
        #[serde(default)]
        success_hint: String,
    },
    Script {
        path: String,
        #[serde(default)]
        success_status: Option<SysItemStatus>,
        #[serde(default)]
        success_hint: String,
    },
}

impl SysInstall {
    pub fn success_status(&self) -> SysItemStatus {
        match self {
            Self::Package { success_status, .. } | Self::Script { success_status, .. } => {
                success_status.unwrap_or(SysItemStatus::Installed)
            }
        }
    }
    pub fn success_hint(&self) -> &str {
        match self {
            Self::Package { success_hint, .. } | Self::Script { success_hint, .. } => success_hint,
        }
    }
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
pub enum SysPackageProvider {
    Homebrew,
    HomebrewCask,
    Apt,
    Winget,
}

#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct SysShellIntegration {
    pub shells: Vec<SysShellKind>,
    pub phase: SysProfilePhase,
    #[serde(default)]
    pub priority: i32,
    #[serde(default)]
    pub when_command: Option<String>,
    #[serde(default)]
    pub path: Option<String>,
    #[serde(default)]
    pub env: BTreeMap<String, String>,
    #[serde(default, rename = "eval")]
    pub eval_argv: Vec<String>,
    #[serde(default)]
    pub source: Option<String>,
    #[serde(default)]
    pub aliases: BTreeMap<String, String>,
    #[serde(default)]
    pub fragment: Option<String>,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum SysShellKind {
    Bash,
    Zsh,
    Powershell,
}

impl SysShellKind {
    pub fn from_runtime(value: &str) -> Option<Self> {
        match value {
            "bash" => Some(Self::Bash),
            "zsh" => Some(Self::Zsh),
            "powershell" => Some(Self::Powershell),
            _ => None,
        }
    }
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Bash => "bash",
            Self::Zsh => "zsh",
            Self::Powershell => "powershell",
        }
    }
}

#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum SysItemMode {
    #[default]
    Init,
    Managed,
}

#[derive(Clone, Debug, Default, Deserialize)]
pub struct SysProfile {
    #[serde(default)]
    pub items: Vec<String>,
}

#[derive(Clone, Debug)]
pub struct LoadedSysPreset {
    pub manifest: SysManifest,
    pub root: PathBuf,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SelectionSource {
    Items,
    Profile(String),
    DefaultProfile(String),
    Interactive,
    NoItems,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedSelection {
    pub item_ids: Vec<String>,
    pub source: SelectionSource,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SysItemOutcome {
    pub item_id: String,
    pub label: String,
    pub status: SysItemStatus,
    pub detail: String,
    pub logs: Vec<String>,
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct SysUpgradeReport {
    pub updated: usize,
    pub skipped: usize,
    pub failed: usize,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SysUpdateRow {
    pub item_id: String,
    pub label: String,
    pub details: Vec<String>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SysInstalledRow {
    pub item_id: String,
    pub label: String,
}

#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "kebab-case")]
pub enum SysProfilePhase {
    Pre,
    Post,
}

pub const SYS_PROFILE_PHASES: [SysProfilePhase; 2] = [SysProfilePhase::Pre, SysProfilePhase::Post];
impl SysProfilePhase {
    pub const fn as_str(self) -> &'static str {
        match self {
            Self::Pre => "pre",
            Self::Post => "post",
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ShellProfileBlockPosition {
    Start,
    End,
}

impl SelectionSource {
    pub fn describe(&self) -> String {
        match self {
            Self::Items => "explicit items".to_string(),
            Self::Profile(name) => format!("profile `{name}`"),
            Self::DefaultProfile(name) => format!("default profile `{name}`"),
            Self::Interactive => "interactive selection".to_string(),
            Self::NoItems => "no selectable items".to_string(),
        }
    }
}