Skip to main content

cli/commands/
preset.rs

1use std::path::PathBuf;
2
3use clap::{Args, Subcommand, ValueEnum};
4
5#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
6pub enum PresetTemplateKind {
7    App,
8    Shell,
9    Sys,
10}
11
12#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
13pub enum PresetValidationFormat {
14    Text,
15    Json,
16}
17
18#[derive(Args, Debug)]
19pub struct ExportCommand {
20    /// Directory to export presets into. Defaults to the configured presets_dir.
21    #[arg(value_name = "DIR")]
22    pub dir: Option<PathBuf>,
23    /// Overwrite existing files
24    #[arg(long, short = 'f')]
25    pub force: bool,
26}
27
28#[derive(Args, Debug)]
29pub struct CopyCommand {
30    /// Built-in preset to copy (app/name, shell/name, or sys/name)
31    #[arg(value_name = "KIND/NAME", value_parser = parse_copy_target)]
32    pub target: String,
33    /// Overwrite existing files
34    #[arg(long, short = 'f')]
35    pub force: bool,
36}
37
38pub(crate) fn parse_copy_target(value: &str) -> Result<String, String> {
39    if value.contains('\\') {
40        return Err(format!(
41            "invalid preset target '{value}': expected app/name, shell/name, or sys/name"
42        ));
43    }
44
45    let mut parts = value.split('/');
46    let kind = parts.next().unwrap_or_default();
47    let name = parts.next().unwrap_or_default();
48    if parts.next().is_some()
49        || !matches!(kind, "app" | "shell" | "sys")
50        || name.is_empty()
51        || matches!(name, "." | "..")
52    {
53        return Err(format!(
54            "invalid preset target '{value}': expected app/name, shell/name, or sys/name"
55        ));
56    }
57    Ok(value.to_string())
58}
59
60#[derive(Args, Debug)]
61pub struct LinkCommand {
62    /// Directory to use as the external presets source.
63    #[arg(value_name = "PATH")]
64    pub path: PathBuf,
65    /// Create the directory if it does not already exist.
66    #[arg(long)]
67    pub create: bool,
68    /// Run external shell source changes on their next invocation.
69    #[arg(long)]
70    pub live: bool,
71}
72
73#[derive(Args, Debug)]
74pub struct OverlayLinkCommand {
75    /// Directory to use as the presets overlay. Mutually exclusive with --git.
76    #[arg(value_name = "PATH", conflicts_with = "git")]
77    pub path: Option<PathBuf>,
78    /// Git URL for a shine-managed overlay. shine clones it (`--depth 1`) under
79    /// `~/.shine/overlay` and keeps it mirrored to the remote tip on `shine preset pull`.
80    #[arg(long, value_name = "URL")]
81    pub git: Option<String>,
82    /// Branch to track for --git. Defaults to the remote's default branch.
83    #[arg(long, value_name = "BRANCH", requires = "git")]
84    pub branch: Option<String>,
85    /// Create the directory if it does not already exist (path mode only).
86    #[arg(long)]
87    pub create: bool,
88}
89
90#[derive(Subcommand, Debug)]
91pub enum OverlayCommands {
92    /// Set the presets overlay in the active config (local PATH or --git URL).
93    Link(OverlayLinkCommand),
94    /// Remove the presets overlay from the active config.
95    Unlink,
96    /// Show information about the active presets overlay.
97    Info,
98}
99
100#[derive(Subcommand, Debug)]
101pub enum PresetCommands {
102    /// Create a shine.toml template for a new app, shell, or sys preset
103    New {
104        #[arg(value_enum)]
105        kind: PresetTemplateKind,
106        /// Overwrite shine.toml if it already exists
107        #[arg(long, short = 'f')]
108        force: bool,
109    },
110    /// Statically validate preset metadata and referenced files
111    Validate {
112        /// Preset repository, category directory, or shine.toml (defaults to current directory)
113        #[arg(value_name = "PATH", default_value = ".")]
114        path: PathBuf,
115        /// Output format
116        #[arg(long, value_enum, default_value_t = PresetValidationFormat::Text)]
117        format: PresetValidationFormat,
118    },
119    /// Copy built-in presets to a directory for local customization
120    Export(ExportCommand),
121    /// Copy one built-in preset into the current directory
122    Copy(CopyCommand),
123    /// Set the external presets directory in the active config
124    Link(LinkCommand),
125    /// Remove the external presets directory from the active config
126    Unlink,
127    /// Manage the personal presets overlay directory
128    Overlay {
129        #[command(subcommand)]
130        command: OverlayCommands,
131    },
132    /// Pull Git-managed preset and overlay repositories
133    Pull,
134}