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