krypt_core/config/schema.rs
1//! Typed representation of a parsed `.krypt.toml`.
2//!
3//! All structs use `#[serde(deny_unknown_fields)]` so typos in user config
4//! produce loud errors at parse time rather than silently no-oping.
5
6use std::collections::BTreeMap;
7
8use serde::{Deserialize, Serialize};
9
10/// Top-level parsed config.
11///
12/// Build via [`super::parse_file`] or [`super::parse_str`].
13#[derive(Debug, Deserialize, Serialize, Default, Clone)]
14#[serde(deny_unknown_fields)]
15pub struct Config {
16 /// Metadata block.
17 #[serde(default)]
18 pub meta: Meta,
19
20 /// Other `.krypt.toml` files to merge in. Resolution is handled by the
21 /// `include` pass, not the parser.
22 #[serde(default)]
23 pub include: Vec<String>,
24
25 /// User-defined path variable overrides. Values may reference other vars
26 /// via `${NAME}`; resolution is deferred to the path resolver.
27 #[serde(default)]
28 pub paths: BTreeMap<String, String>,
29
30 /// File-deploy entries. Renamed because TOML uses `[[link]]` but Rust
31 /// idiom is `links` for the collection.
32 #[serde(default, rename = "link")]
33 pub links: Vec<Link>,
34
35 /// Templated file entries. Deployed like links, then optionally patched
36 /// by `krypt setup` based on the named prompt sections.
37 #[serde(default, rename = "template")]
38 pub templates: Vec<Template>,
39
40 /// Wizard prompt definitions, keyed by section name.
41 #[serde(default)]
42 pub prompts: BTreeMap<String, PromptSection>,
43
44 /// Package dependency groups, one per cross-distro mapping.
45 #[serde(default, rename = "deps")]
46 pub deps: Vec<DepsGroup>,
47
48 /// Lifecycle hooks (post-update plugin updates, etc.).
49 #[serde(default, rename = "hook")]
50 pub hooks: Vec<Hook>,
51
52 /// Custom subcommands surfaced as `krypt <group> <name>`.
53 #[serde(default, rename = "command")]
54 pub commands: Vec<Command>,
55}
56
57/// `[meta]` section.
58#[derive(Debug, Deserialize, Serialize, Default, Clone)]
59#[serde(deny_unknown_fields)]
60pub struct Meta {
61 /// Human-readable repo name.
62 #[serde(default)]
63 pub name: String,
64
65 /// Free-form short description.
66 #[serde(default)]
67 pub description: String,
68
69 /// Minimum `krypt` binary version this repo expects. If the installed
70 /// `krypt` is older, the loader emits a warning (or errors on
71 /// `--strict`). Format: SemVer.
72 #[serde(default)]
73 pub krypt_min: Option<String>,
74}
75
76/// `[[link]]` entry — a file (or glob) to deploy from the repo to a path
77/// under `$HOME`.
78#[derive(Debug, Deserialize, Serialize, Clone)]
79#[serde(deny_unknown_fields)]
80pub struct Link {
81 /// Source path within the dotfiles repo. Mutually exclusive with
82 /// `src_glob`.
83 #[serde(default)]
84 pub src: Option<String>,
85
86 /// Glob pattern matched against the repo. Expanded at deploy time.
87 /// Mutually exclusive with `src`.
88 #[serde(default)]
89 pub src_glob: Option<String>,
90
91 /// Destination path. May contain `${VAR}` placeholders.
92 pub dst: String,
93
94 /// Optional OS gate. One of `linux`, `macos`, `windows`. Multiple OSes:
95 /// duplicate the link entry. Omitted = all platforms.
96 #[serde(default)]
97 pub platform: Option<String>,
98}
99
100/// `[[template]]` entry — a file copied like a `[[link]]`, but with prompt-
101/// section names that wire it to `krypt setup`.
102#[derive(Debug, Deserialize, Serialize, Clone)]
103#[serde(deny_unknown_fields)]
104pub struct Template {
105 /// Source path within the repo.
106 pub src: String,
107
108 /// Destination path. May contain `${VAR}` placeholders.
109 pub dst: String,
110
111 /// Names of `[prompts.<name>]` sections that drive this template's
112 /// post-copy patching.
113 #[serde(default)]
114 pub prompts: Vec<String>,
115
116 /// Optional OS gate. Same semantics as [`Link::platform`].
117 #[serde(default)]
118 pub platform: Option<String>,
119}
120
121/// `[prompts.<name>]` section — an interactive wizard subsection.
122#[derive(Debug, Deserialize, Serialize, Clone)]
123#[serde(deny_unknown_fields)]
124pub struct PromptSection {
125 /// Heading shown above this group of prompts.
126 #[serde(default)]
127 pub heading: String,
128
129 /// Ordered list of fields to prompt for.
130 pub fields: Vec<PromptField>,
131
132 /// Named built-in writer that applies the collected values. e.g.
133 /// `gitconfig`, `hypr_vars`, `env`, `generic_template`.
134 pub writer: String,
135}
136
137/// A single field inside a [`PromptSection`].
138#[derive(Debug, Deserialize, Serialize, Clone)]
139#[serde(deny_unknown_fields)]
140pub struct PromptField {
141 /// Variable name the value is stored under.
142 pub key: String,
143
144 /// Question presented to the user.
145 pub prompt: String,
146
147 /// Field type: `string` (default), `bool`, `int`. Future: `password`,
148 /// `path`, `enum`.
149 #[serde(default = "default_prompt_type")]
150 pub r#type: String,
151
152 /// Literal default value.
153 #[serde(default)]
154 pub default: Option<toml::Value>,
155
156 /// Lookup expression for the default. Examples:
157 /// - `git:user.name` — run `git config --get user.name`
158 /// - `env:USER` — read env var
159 /// - `field:email` — value of an earlier field
160 /// - `read_var:terminal` — read existing Hyprland `$terminal` from dest
161 #[serde(default)]
162 pub default_from: Option<String>,
163
164 /// Read the current value of a Hyprland-style variable from the
165 /// destination file when computing the default. Shorthand alternative
166 /// to `default_from = "read_var:..."`.
167 #[serde(default)]
168 pub read_var: Option<String>,
169
170 /// If true, blank answers are allowed.
171 #[serde(default)]
172 pub optional: bool,
173
174 /// Only ask this field if the named earlier field has a non-empty value.
175 #[serde(default)]
176 pub requires: Option<String>,
177}
178
179fn default_prompt_type() -> String {
180 "string".into()
181}
182
183/// `[[deps]]` entry — one cross-distro dependency group.
184#[derive(Debug, Deserialize, Serialize, Default, Clone)]
185#[serde(deny_unknown_fields)]
186pub struct DepsGroup {
187 /// Group name (e.g. `core`, `fonts`).
188 pub group: String,
189
190 /// Required platforms for this group. `["all"]` or omitted = required
191 /// everywhere; otherwise restrict to listed OSes.
192 #[serde(default)]
193 pub required_platforms: Vec<String>,
194
195 /// Packages on each manager. Empty list = unavailable on that manager.
196 #[serde(default)]
197 pub pacman: Vec<String>,
198 /// Packages on apt (Debian/Ubuntu).
199 #[serde(default)]
200 pub apt: Vec<String>,
201 /// Packages on dnf (Fedora/RHEL).
202 #[serde(default)]
203 pub dnf: Vec<String>,
204 /// Packages on brew (macOS).
205 #[serde(default)]
206 pub brew: Vec<String>,
207 /// Packages on scoop (Windows).
208 #[serde(default)]
209 pub scoop: Vec<String>,
210 /// Packages on winget (Windows).
211 #[serde(default)]
212 pub winget: Vec<String>,
213}
214
215/// `[[hook]]` entry — a lifecycle hook tied to a phase.
216#[derive(Debug, Deserialize, Serialize, Clone)]
217#[serde(deny_unknown_fields)]
218pub struct Hook {
219 /// Hook name, useful in logs.
220 pub name: String,
221
222 /// Phase: `post-update`, `post-link`, etc.
223 pub when: String,
224
225 /// Optional predicate gating execution. Same syntax as `[[command]]`
226 /// step predicates.
227 #[serde(default)]
228 pub r#if: Option<String>,
229
230 /// Command + args to execute.
231 pub run: Vec<String>,
232
233 /// Don't fail the parent command if this hook errors.
234 #[serde(default)]
235 pub ignore_failure: bool,
236}
237
238/// `[[command]]` entry — a custom user subcommand built from step primitives.
239///
240/// Exposed at the CLI as `krypt <group> <name>`.
241#[derive(Debug, Deserialize, Serialize, Clone)]
242#[serde(deny_unknown_fields)]
243pub struct Command {
244 /// Group bucket (e.g. `menu`, `battery`).
245 pub group: String,
246
247 /// Command name within the group.
248 pub name: String,
249
250 /// Help text shown by `krypt <group> --help`.
251 #[serde(default)]
252 pub description: String,
253
254 /// Optional OS gate.
255 #[serde(default)]
256 pub platform: Option<String>,
257
258 /// Ordered execution steps.
259 pub steps: Vec<Step>,
260}
261
262/// One step inside a [`Command`] or [`Hook`].
263///
264/// Step kinds are mutually exclusive but all carry optional shared fields
265/// like `capture`, `if`, `on_fail`. Validation enforces exactly one of
266/// `run` / `pipe` / `notify` per step.
267#[derive(Debug, Deserialize, Serialize, Default, Clone)]
268#[serde(deny_unknown_fields)]
269pub struct Step {
270 /// Spawn a subprocess with these args. String args may interpolate
271 /// captured vars via `{name}`.
272 #[serde(default)]
273 pub run: Option<Vec<String>>,
274
275 /// Spawn a subprocess that receives `input` on stdin.
276 #[serde(default)]
277 pub pipe: Option<Vec<String>>,
278
279 /// Send a desktop notification with this title (`notify[0]`) and body
280 /// (`notify[1]`).
281 #[serde(default)]
282 pub notify: Option<Vec<String>>,
283
284 /// Variable name to capture stdout into.
285 #[serde(default)]
286 pub capture: Option<String>,
287
288 /// Input string fed to stdin (used with `pipe`). May reference captures.
289 #[serde(default)]
290 pub input: Option<String>,
291
292 /// Predicate gating this step. e.g. `command_exists:fish`,
293 /// `platform:linux`, `env:FOO=bar`, `!file_exists:/etc/passwd`.
294 #[serde(default)]
295 pub r#if: Option<String>,
296
297 /// Failure mode: `abort` (default), `notify`, `ignore`, `prompt`.
298 #[serde(default)]
299 pub on_fail: Option<String>,
300
301 /// Boolean shortcut for `on_fail = "ignore"`.
302 #[serde(default)]
303 pub ignore_failure: bool,
304}