1use std::{ops::Deref, sync::Arc};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum CommandKind {
10 Builtin,
11 Plugin,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum CommandSourceKind {
16 Command,
17 Script,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ReturnBehavior {
22 None,
23 Fixed(ValueShape),
24 QueryDependsOnFlag,
25 Unknown,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum FlagArity {
30 None,
31 Exact(u8),
32 Range { min: u8, max: u8 },
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct FlagArityByMode {
37 pub create: FlagArity,
38 pub edit: FlagArity,
39 pub query: FlagArity,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum ValueShape {
44 Bool,
45 Int,
46 Float,
47 String,
48 Script,
49 StringArray,
50 FloatTuple(u8),
51 IntTuple(u8),
52 NodeName,
53 Unknown,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum PositionalTailSchema {
58 None,
59 Shaped {
60 min: u8,
61 max: Option<u8>,
62 value_shapes: &'static [ValueShape],
63 },
64 Opaque {
65 min: u8,
66 max: Option<u8>,
67 },
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
71pub enum PositionalSourcePolicy {
72 #[default]
73 ExplicitOnly,
74 ExplicitOrCurrentSelection,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct PositionalSlotSchema {
79 pub value_shapes: &'static [ValueShape],
80 pub source_policy: PositionalSourcePolicy,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84pub struct PositionalSchema {
85 pub prefix: &'static [PositionalSlotSchema],
86 pub tail: PositionalTailSchema,
87}
88
89impl PositionalSchema {
90 #[must_use]
91 pub const fn unconstrained() -> Self {
92 Self {
93 prefix: &[],
94 tail: PositionalTailSchema::Opaque { min: 0, max: None },
95 }
96 }
97}
98
99impl Default for PositionalSchema {
100 fn default() -> Self {
101 Self::unconstrained()
102 }
103}
104
105impl PositionalSchema {
106 fn validate(self, command_name: &Arc<str>) -> Result<(), CommandSchemaValidationError> {
107 let mut seen_selection_aware = false;
108 for (slot_index, slot) in self.prefix.iter().enumerate() {
109 let is_selection_aware = matches!(
110 slot.source_policy,
111 PositionalSourcePolicy::ExplicitOrCurrentSelection
112 );
113 if is_selection_aware {
114 seen_selection_aware = true;
115 continue;
116 }
117 if seen_selection_aware {
118 return Err(
119 CommandSchemaValidationError::SelectionAwarePositionalNotTrailingSuffix {
120 command_name: command_name.clone(),
121 slot_index,
122 },
123 );
124 }
125 }
126
127 Ok(())
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub struct CommandModeMask {
133 pub create: bool,
134 pub edit: bool,
135 pub query: bool,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct FlagSchema {
140 pub long_name: Arc<str>,
141 pub short_name: Option<Arc<str>>,
142 pub mode_mask: CommandModeMask,
143 pub arity_by_mode: FlagArityByMode,
144 pub value_shapes: Arc<[ValueShape]>,
145 pub allows_multiple: bool,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct CommandSchema {
150 pub name: Arc<str>,
151 pub kind: CommandKind,
152 pub source_kind: CommandSourceKind,
153 pub mode_mask: CommandModeMask,
154 pub return_behavior: ReturnBehavior,
155 pub flags: Arc<[FlagSchema]>,
156 pub positionals: PositionalSchema,
157}
158
159impl CommandSchema {
160 pub fn validate(self) -> Result<ValidatedCommandSchema, CommandSchemaValidationError> {
161 self.positionals.validate(&self.name)?;
162 Ok(ValidatedCommandSchema(self))
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub enum CommandSchemaValidationError {
168 SelectionAwarePositionalNotTrailingSuffix {
169 command_name: Arc<str>,
170 slot_index: usize,
171 },
172}
173
174impl std::fmt::Display for CommandSchemaValidationError {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 match self {
177 Self::SelectionAwarePositionalNotTrailingSuffix {
178 command_name,
179 slot_index,
180 } => write!(
181 f,
182 "command schema \"{command_name}\" has non-trailing selection-aware positional slot before explicit-only slot at prefix index {slot_index}"
183 ),
184 }
185 }
186}
187
188impl std::error::Error for CommandSchemaValidationError {}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct ValidatedCommandSchema(CommandSchema);
192
193impl ValidatedCommandSchema {
194 pub fn new(schema: CommandSchema) -> Result<Self, CommandSchemaValidationError> {
195 schema.validate()
196 }
197
198 #[must_use]
199 pub fn schema(&self) -> &CommandSchema {
200 &self.0
201 }
202
203 #[must_use]
204 pub fn into_inner(self) -> CommandSchema {
205 self.0
206 }
207}
208
209impl TryFrom<CommandSchema> for ValidatedCommandSchema {
210 type Error = CommandSchemaValidationError;
211
212 fn try_from(value: CommandSchema) -> Result<Self, Self::Error> {
213 Self::new(value)
214 }
215}
216
217impl Deref for ValidatedCommandSchema {
218 type Target = CommandSchema;
219
220 fn deref(&self) -> &Self::Target {
221 self.schema()
222 }
223}
224
225pub trait CommandRegistry {
226 fn lookup(&self, name: &str) -> Option<&ValidatedCommandSchema>;
227}
228
229#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
230pub struct EmptyCommandRegistry;
231
232impl CommandRegistry for EmptyCommandRegistry {
233 fn lookup(&self, _name: &str) -> Option<&ValidatedCommandSchema> {
234 None
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq, Default)]
239pub struct StaticCommandRegistry {
240 commands: Vec<ValidatedCommandSchema>,
241}
242
243impl StaticCommandRegistry {
244 pub fn try_new(commands: Vec<CommandSchema>) -> Result<Self, CommandSchemaValidationError> {
245 let mut validated = Vec::with_capacity(commands.len());
246 for command in commands {
247 validated.push(command.validate()?);
248 }
249 Ok(Self {
250 commands: validated,
251 })
252 }
253}
254
255impl CommandRegistry for StaticCommandRegistry {
256 fn lookup(&self, name: &str) -> Option<&ValidatedCommandSchema> {
257 self.commands.iter().find(|info| info.name.as_ref() == name)
258 }
259}