zenops 0.20.0

Declarative system configuration management for shell config and dotfiles.
//! Shell init actions: lines a pkg contributes to the rendered shell
//! profile. [`ShellInitAction`] wraps an [`ActionKind`] (the concrete line:
//! `Source`, `Export`, `PathPrepend`, …) with an `optional` flag for
//! best-effort lines. [`PerShellActions`] / [`PkgShellConfig`] route them
//! into the right rc file (env, login, interactive) per shell.

use zenops_expand::ExpandStr;

use super::Shell;

/// One line zenops adds to the generated shell rc file when this
/// package is installed.
///
/// Wraps an [`ActionKind`] — what the line does — with an `optional`
/// flag for best-effort lines.
#[derive(serde::Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq)]
pub struct ShellInitAction {
    /// If `true`, an unresolved `${var}` placeholder in this action
    /// silently skips the line instead of failing the apply. Use it
    /// for best-effort lines like an environment variable that only
    /// makes sense on some hosts.
    #[serde(default)]
    pub optional: bool,
    #[serde(flatten)]
    pub kind: ActionKind,
}

/// Which kind of shell line to emit. Pick one with `type = "<kind>"`;
/// the other fields on the table are the kind's arguments.
#[derive(serde::Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ActionKind {
    /// A `#` comment in the rendered file. Useful for grouping
    /// related lines.
    Comment {
        /// The comment text. zenops prepends `# `.
        text: ExpandStr,
    },
    /// A `. <path>` line that sources another file at shell start.
    /// `~` and `${var}` are expanded before quoting.
    Source {
        /// Path to the file to source.
        path: ExpandStr,
    },
    /// `eval "$(command)"` — runs the command at shell start and
    /// evaluates its output as shell code. The classic pattern for
    /// `starship init zsh`, `direnv hook bash`, and friends.
    EvalOutput {
        /// The command and its arguments, one element each.
        command: Vec<ExpandStr>,
    },
    /// `source <(command)` — runs the command at shell start and
    /// sources its output. Common for shell-completion generators.
    SourceOutput {
        /// The command and its arguments, one element each.
        command: Vec<ExpandStr>,
    },
    /// `export NAME="VALUE"` — sets an environment variable.
    Export {
        /// Variable name.
        name: ExpandStr,
        /// Variable value. zenops quotes it for the shell.
        value: ExpandStr,
    },
    /// A raw line written as-is into the rc file. Escape hatch for
    /// anything the other kinds don't cover; you own the syntax.
    Line {
        /// The exact line to emit.
        line: ExpandStr,
    },
    /// Add a directory to the front of `PATH` (so it wins over what's
    /// already there). zenops emits the right export syntax for the
    /// shell — you just supply the directory.
    PathPrepend {
        /// The directory to add.
        value: ExpandStr,
    },
    /// Add a directory to the back of `PATH` (so it loses to what's
    /// already there). zenops emits the right export syntax for the
    /// shell — you just supply the directory.
    PathAppend {
        /// The directory to add.
        value: ExpandStr,
    },
}

/// Shell init actions grouped by shell. Each list is the sequence of
/// lines zenops drops into that shell's rc when this package is
/// installed.
#[derive(serde::Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq, Default)]
#[serde(default)]
pub(crate) struct PerShellActions {
    /// Lines for bash.
    pub bash: Vec<ShellInitAction>,
    /// Lines for zsh.
    pub zsh: Vec<ShellInitAction>,
}

impl PerShellActions {
    pub fn for_shell(&self, shell: Shell) -> &[ShellInitAction] {
        match shell {
            Shell::Bash => &self.bash,
            Shell::Zsh => &self.zsh,
        }
    }
}

/// Three slots for shell init actions, each grouped by shell.
///
/// `env_init` runs the earliest — suitable for environment variables
/// that other tools rely on. `login_init` runs only in login shells.
/// `interactive_init` runs only in interactive shells (the most common
/// slot for prompt themes, completions, and shell hooks).
#[derive(serde::Deserialize, schemars::JsonSchema, Debug, Clone, PartialEq, Default)]
#[serde(default)]
pub(crate) struct PkgShellConfig {
    /// Lines run on every shell startup, before anything else. Right
    /// place for environment variables that other init code depends
    /// on.
    pub env_init: PerShellActions,
    /// Lines run only in login shells.
    pub login_init: PerShellActions,
    /// Lines run only in interactive shells — typically prompt themes
    /// (`starship init`), completion setup, and shell hooks (`direnv
    /// hook`).
    pub interactive_init: PerShellActions,
}