Skip to main content

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}