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