1use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Deserialize)]
9#[serde(deny_unknown_fields)]
10pub struct Library {
11 pub version: u32,
12
13 #[serde(default)]
14 pub commands: Vec<Entry>,
15
16 #[serde(default)]
19 pub disabled: Vec<String>,
20}
21
22#[derive(Debug, Clone, PartialEq, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct Entry {
26 pub id: String,
27 pub cmd: CommandBody,
28 pub desc: String,
29
30 #[serde(default)]
31 pub tags: Vec<String>,
32
33 #[serde(default)]
34 pub params: BTreeMap<String, ParamSpec>,
35
36 #[serde(default)]
38 pub danger: bool,
39
40 #[serde(skip)]
41 pub layer: Layer,
42}
43
44#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
46#[serde(untagged)]
47pub enum CommandBody {
48 Shared(String),
49 PerShell(BTreeMap<ShellFamily, String>),
50}
51
52#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
54#[serde(deny_unknown_fields)]
55pub struct ParamSpec {
56 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub desc: Option<String>,
58
59 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub from: Option<String>,
64}
65
66#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
68pub enum Layer {
69 #[default]
70 Builtin,
71 Project,
72 User,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
77#[serde(rename_all = "lowercase")]
78pub enum ShellFamily {
79 Posix,
80 PowerShell,
81}
82
83impl Entry {
84 pub fn cmd_for(&self, family: ShellFamily) -> Option<&str> {
87 match &self.cmd {
88 CommandBody::Shared(cmd) => Some(cmd),
89 CommandBody::PerShell(variants) => variants.get(&family).map(String::as_str),
90 }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 fn entry(cmd: CommandBody) -> Entry {
99 Entry {
100 id: "test".into(),
101 cmd,
102 desc: "test".into(),
103 tags: Vec::new(),
104 params: BTreeMap::new(),
105 danger: false,
106 layer: Layer::Builtin,
107 }
108 }
109
110 #[test]
111 fn shared_command_serves_every_family() {
112 let e = entry(CommandBody::Shared("git status".into()));
113 assert_eq!(e.cmd_for(ShellFamily::Posix), Some("git status"));
114 assert_eq!(e.cmd_for(ShellFamily::PowerShell), Some("git status"));
115 }
116
117 #[test]
118 fn missing_variant_hides_entry_from_that_shell() {
119 let e = entry(CommandBody::PerShell(BTreeMap::from([(
120 ShellFamily::Posix,
121 "ss -tulpn".into(),
122 )])));
123 assert_eq!(e.cmd_for(ShellFamily::Posix), Some("ss -tulpn"));
124 assert_eq!(e.cmd_for(ShellFamily::PowerShell), None);
125 }
126
127 #[test]
128 fn layers_order_from_weakest_to_strongest() {
129 assert!(Layer::Builtin < Layer::Project);
130 assert!(Layer::Project < Layer::User);
131 }
132}