zenops 0.20.0

Declarative system configuration management for shell config and dotfiles.
//! Per-package-manager install hints.
//!
//! [`InstallHint`] is an open map keyed by manager name: `brew`, `dnf`,
//! `apt`, `pacman`, `cargo`, and anything else a user (or a future zenops
//! release) wants to add. Each entry holds the package list zenops would
//! ask that manager to install.
//!
//! The map shape is deliberately schema-loose: adding zypper or apk on
//! the runtime side means a new row in the manager-grammar registry plus
//! `install_hint.zypper.packages = [...]` in user pkg TOML — no
//! deserialize change, no migration. Unknown keys parse cleanly and sit
//! dormant until detection finds a matching `PackageManager`.

use indexmap::IndexMap;
use smol_str::SmolStr;

/// How to install this package via each package manager that knows it.
///
/// Keyed by the lowercase manager name (matching
/// [`crate::platform::PackageManager::name`]); the value carries that
/// manager's package list. Manager names zenops doesn't ship a grammar
/// for are accepted silently — they're useful as forward-compatible
/// hints for users on managers zenops will support later.
#[derive(
    serde::Deserialize, serde::Serialize, schemars::JsonSchema, Debug, Clone, PartialEq, Default,
)]
#[serde(transparent)]
pub struct InstallHint(IndexMap<SmolStr, InstallHintEntry>);

impl InstallHint {
    /// Packages declared for the manager named `name`. Returns an empty
    /// slice when the manager has no entry — explicit `packages = []`
    /// and "manager omitted entirely" are indistinguishable here, which
    /// is the right call for "no install path".
    pub fn packages_for(&self, name: &str) -> &[String] {
        self.0
            .get(name)
            .map(|e| e.packages.as_slice())
            .unwrap_or(&[])
    }
}

/// One manager's install entry. `packages` is the list of package names
/// to pass to the manager's install command.
#[derive(
    serde::Deserialize, serde::Serialize, schemars::JsonSchema, Debug, Clone, PartialEq, Default,
)]
#[serde(deny_unknown_fields)]
pub struct InstallHintEntry {
    /// Package names. Empty list and "no entry" both render as "no
    /// install path"; use the empty form to *signal* "intentionally
    /// not installable via this manager" so a reader of the TOML can
    /// tell apart "we considered this" from "we forgot about it".
    pub packages: Vec<String>,
}