1use kdl::KdlNode;
2use std::{
3 collections::{HashMap, HashSet},
4 io,
5};
6
7use crate::namespace::NameSpace;
8
9#[derive(Debug, Default, Clone)]
10pub struct UsageSpec {
11 pub info: Info,
12 pub flags: Vec<Flag>,
13 pub args: Vec<Arg>,
14 pub cmds: Vec<Cmd>,
15 pub completes: HashMap<String, Complete>,
16}
17
18#[derive(Debug, Default, Clone)]
19pub struct Info {
20 pub name: String,
21 pub bin: String,
22}
23
24#[derive(Debug, Clone)]
25pub enum Usage {
26 Flag(Flag),
27 Arg(Arg),
28 Cmd(Cmd),
29 Complete(Complete),
30}
31
32#[derive(Debug, Default, Clone)]
33pub struct Alias {
34 pub name: String,
35 pub hide: bool,
36}
37
38#[derive(Debug, Default, Clone)]
39pub enum GlobalFlag {
40 #[default]
41 None,
42 Itself,
43 Imposed(NameSpace),
44}
45
46#[derive(Debug, Default, Clone)]
47pub struct Flag {
48 pub name: String,
49 pub names: Vec<String>,
50 pub help: String,
51 pub hide: bool,
52 pub global: GlobalFlag,
53 pub aliases: Vec<Alias>,
54 pub arg: Option<Arg>,
55}
56
57#[derive(Debug, Default, Clone)]
58pub struct Arg {
59 pub name: String,
60 pub repr: String,
61 pub required: bool,
62 pub choices: Vec<String>,
63 pub hide: bool,
64 pub var: bool,
65 pub min: Option<i128>,
66 pub max: Option<i128>,
67 pub default: Option<String>,
68}
69
70#[derive(Debug, Default, Clone)]
71pub struct Cmd {
72 pub name: String,
73 pub help: String,
74 pub hide: bool,
75 pub args: Vec<Arg>,
76 pub flags: Vec<Flag>,
77 pub aliases: Vec<Alias>,
78 pub cmds: Vec<Box<Cmd>>,
79}
80
81#[derive(Debug, Default, Clone)]
82pub struct Complete {
83 pub name: String,
84 pub kind: CompleteKind,
85 pub descs: bool,
86}
87
88#[derive(Debug, Clone)]
89pub enum CompleteKind {
90 None,
91 File,
92 Dir,
93 Run(String),
94}
95
96pub fn parse_name(node: &KdlNode) -> Result<String, UError> {
97 if node.name().value() != "name" {
98 return Err(UError::InvalidNodeName(io::Error::new(
99 io::ErrorKind::InvalidInput,
100 format!("Node name wasn't name!\n{:?}", node),
101 )));
102 }
103 let name = node
104 .get(0)
105 .map(|v| v.as_string().unwrap_or_default().to_string())
106 .ok_or_else(|| {
107 UError::InvalidNodeFirstArg(io::Error::new(
108 io::ErrorKind::NotFound,
109 format!("No name found in {:?}", node),
110 ))
111 })?;
112 Ok(name)
113}
114
115pub fn parse_bin(node: &KdlNode) -> Result<String, UError> {
116 if node.name().value() != "bin" {
117 return Err(UError::InvalidNodeName(io::Error::new(
118 io::ErrorKind::InvalidInput,
119 format!("Node name wasn't bin!\n{:?}", node),
120 )));
121 }
122 let bin = node
123 .get(0)
124 .map(|v| v.as_string().unwrap_or_default().to_string())
125 .ok_or_else(|| {
126 UError::InvalidNodeFirstArg(io::Error::new(
127 io::ErrorKind::NotFound,
128 format!("No bin found in {:?}", node),
129 ))
130 })?;
131 Ok(bin)
132}
133
134pub fn parse_include(node: &KdlNode) -> Result<String, UError> {
135 if node.name().value() != "include" {
136 return Err(UError::InvalidNodeName(io::Error::new(
137 io::ErrorKind::InvalidInput,
138 format!("Node name wasn't include!\n{:?}", node),
139 )));
140 }
141 let include = node
142 .get(0)
143 .map(|v| v.as_string().unwrap_or_default().to_string())
144 .ok_or_else(|| {
145 UError::InvalidNodeFirstArg(io::Error::new(
146 io::ErrorKind::NotFound,
147 format!("No include found in {:?}", node),
148 ))
149 })?;
150 Ok(include)
151}
152
153pub fn parse_alias(node: &KdlNode) -> Result<Vec<Alias>, UError> {
154 if node.name().value() != "alias" {
155 return Err(UError::InvalidNodeName(io::Error::new(
156 io::ErrorKind::InvalidInput,
157 format!("Node name wasn't alias!\n{:?}", node),
158 )));
159 }
160
161 let mut aliases: Vec<Alias> = vec![];
162 for entry in node.entries() {
163 let mut hide = false;
164 if entry.name().is_none() {
165 let alias_name = entry
166 .value()
167 .as_string()
168 .ok_or_else(|| {
169 UError::InvalidNodeFirstArg(io::Error::new(
170 io::ErrorKind::NotFound,
171 format!("No alias found in {:?}", entry),
172 ))
173 })?
174 .to_string();
175 if let Some(hide_val) = node.get("hide") {
176 hide = hide_val.as_bool().unwrap_or_default();
177 }
178 let alias = Alias {
179 name: alias_name,
180 hide,
181 };
182 aliases.push(alias);
183 }
184 }
185 Ok(aliases)
186}
187
188pub fn parse_choices(node: &KdlNode) -> Result<Vec<String>, UError> {
189 if node.name().value() != "choices" {
190 return Err(UError::InvalidNodeName(io::Error::new(
191 io::ErrorKind::InvalidInput,
192 format!("Node name wasn't choices!\n{:?}", node),
193 )));
194 }
195
196 let mut choices: Vec<String> = vec![];
197 for entry in node.entries() {
198 let choice = entry
199 .value()
200 .as_string()
201 .ok_or_else(|| {
202 UError::InvalidNodeFirstArg(io::Error::new(
203 io::ErrorKind::NotFound,
204 format!("No choice found in {:?}", entry),
205 ))
206 })?
207 .to_string();
208 choices.push(choice);
209 }
210 Ok(choices)
211}
212
213pub fn parse_flag(node: &KdlNode) -> Result<Flag, UError> {
214 if node.name().value() != "flag" {
215 return Err(UError::InvalidNodeName(io::Error::new(
216 io::ErrorKind::InvalidInput,
217 format!("Node name wasn't flag!\n{:?}", node),
218 )));
219 }
220
221 let mut flag = Flag::default();
222 for (index, entry) in node.entries().iter().enumerate() {
223 if index == 0 {
224 let entry_flag_names = entry
225 .value()
226 .as_string()
227 .ok_or_else(|| {
228 UError::InvalidNodeFirstArg(io::Error::new(
229 io::ErrorKind::NotFound,
230 format!("No flag found in {:?}", entry),
231 ))
232 })?
233 .to_string();
234
235 let (long_flag_index, flag_names) = {
237 let mut name_len = 0;
238 let mut flag_index = 0;
239 let mut long_flag_index = flag_index;
240 let flag_names: Vec<String> = entry_flag_names
241 .split_whitespace()
242 .map(|s| {
243 let s = String::from(s);
244 let len = s.len();
245 if len > name_len {
246 name_len = len;
247 long_flag_index = flag_index;
248 };
249 flag_index += 1;
250 s
251 })
252 .collect();
253 (long_flag_index, flag_names)
254 };
255
256 let slugify = |mut c: char| {
257 if !c.is_alphanumeric() && c != '_' {
258 c = '_';
259 }
260 c
261 };
262
263 let flag_name = flag_names[long_flag_index]
264 .trim_matches('-')
265 .chars()
266 .map(slugify)
267 .collect();
268
269 flag.name = flag_name;
270 flag.names = flag_names;
271 }
272
273 if let Some(iden_name) = entry.name() {
274 match iden_name.value() {
275 "help" => flag.help = entry.value().as_string().unwrap_or_default().to_string(),
276 "hide" => flag.hide = entry.value().as_bool().unwrap_or_default(),
277 "global" => flag.global = entry.value().as_bool().unwrap_or_default().into(),
278 _ => {}
279 }
280 }
281 }
282
283 if let Some(child_doc) = node.children() {
284 for child_node in child_doc.nodes() {
285 match child_node.name().value() {
286 "arg" => flag.arg = Some(parse_arg(child_node)?),
287 "alias" => flag.aliases = parse_alias(child_node)?,
288 "choices" => {
289 if let Some(arg_name) = flag.names.pop() {
290 let mut arg = Arg::default();
291 arg.name = arg_name;
292 arg.choices = parse_choices(child_node)?;
293 if arg.name.starts_with("<") {
294 arg.required = true;
295 }
296 flag.arg = Some(arg);
297 }
298 }
299 _ => {}
300 }
301 }
302 }
303 Ok(flag)
304}
305
306pub fn parse_arg(node: &KdlNode) -> Result<Arg, UError> {
307 if node.name().value() != "arg" {
308 return Err(UError::InvalidNodeName(io::Error::new(
309 io::ErrorKind::InvalidInput,
310 format!("Node name wasn't arg!\n{:?}", node),
311 )));
312 }
313
314 let mut arg = Arg::default();
315 for (index, entry) in node.entries().iter().enumerate() {
316 if index == 0 {
317 let entry_arg_name = entry
318 .value()
319 .as_string()
320 .ok_or_else(|| {
321 UError::InvalidNodeFirstArg(io::Error::new(
322 io::ErrorKind::NotFound,
323 format!("No arg found in {:?}", entry),
324 ))
325 })?
326 .to_string();
327
328 if entry_arg_name.starts_with("<") {
329 arg.required = true;
330 let end = entry_arg_name.find(">").unwrap_or(entry_arg_name.len());
331 arg.name = entry_arg_name[1..end].to_string();
332 } else if entry_arg_name.starts_with("[") {
333 arg.required = false;
334 let end = entry_arg_name.find("]").unwrap_or(entry_arg_name.len());
335 arg.name = entry_arg_name[1..end].to_string();
336 }
337 arg.repr = entry_arg_name;
338 }
339
340 if let Some(iden_name) = entry.name() {
341 match iden_name.value() {
342 "hide" => arg.hide = entry.value().as_bool().unwrap_or_default(),
343 "default" => arg.default = entry.value().as_string().map(String::from),
344 "var" => arg.var = entry.value().as_bool().unwrap_or_default(),
345 "var_max" => arg.max = entry.value().as_integer(),
346 "var_min" => arg.min = entry.value().as_integer(),
347 _ => {}
348 }
349 }
350 }
351
352 arg.max = arg.max.or(Some(-1));
353 arg.min = arg.min.or(Some(0));
354
355 if let Some(child_doc) = node.children() {
356 for child_node in child_doc.nodes() {
357 if child_node.name().value() == "choices" {
358 let mut choices: Vec<String> = vec![];
359 for cn_entry in child_node.entries() {
360 let choice = cn_entry
361 .value()
362 .as_string()
363 .expect(format!("No choice found in {:?}", cn_entry).as_str())
364 .to_string();
365 choices.push(choice);
366 }
367 arg.choices = choices;
368 }
369 }
370 }
371 Ok(arg)
372}
373
374pub fn parse_cmd(node: &KdlNode) -> Result<Cmd, UError> {
375 if node.name().value() != "cmd" {
376 return Err(UError::InvalidNodeName(io::Error::new(
377 io::ErrorKind::InvalidInput,
378 format!("Node name wasn't cmd!\n{:?}", node),
379 )));
380 }
381
382 let mut cmd = Cmd::default();
383 for (index, entry) in node.entries().iter().enumerate() {
384 if index == 0 {
385 let entry_cmd_name = entry
386 .value()
387 .as_string()
388 .ok_or_else(|| {
389 UError::InvalidNodeFirstArg(io::Error::new(
390 io::ErrorKind::NotFound,
391 format!("No cmd found in {:?}", entry),
392 ))
393 })?
394 .to_string();
395
396 cmd.name = entry_cmd_name;
397 }
398
399 if let Some(iden_name) = entry.name() {
400 match iden_name.value() {
401 "help" => {
402 cmd.help = entry
403 .value()
404 .as_string()
405 .map(String::from)
406 .unwrap_or_default()
407 }
408 "hide" => cmd.hide = entry.value().as_bool().unwrap_or_default(),
409 _ => {}
410 }
411 }
412 }
413
414 if let Some(child_doc) = node.children() {
415 for child_node in child_doc.nodes() {
416 match child_node.name().value() {
417 "alias" => {
418 let mut alias = parse_alias(child_node)?;
419 cmd.aliases.append(&mut alias);
420 }
421 "flag" => {
422 let flag = parse_flag(child_node)?;
423 cmd.flags.push(flag);
424 }
425 "arg" => {
426 let arg = parse_arg(child_node)?;
427 cmd.args.push(arg);
428 }
429 "cmd" => {
430 let child_cmd = parse_cmd(child_node)?;
431 cmd.cmds.push(Box::new(child_cmd));
432 }
433 _ => {}
434 }
435 }
436 }
437 Ok(cmd)
438}
439
440pub fn parse_complete(node: &KdlNode) -> Result<Complete, UError> {
441 if node.name().value() != "complete" {
442 return Err(UError::InvalidNodeName(io::Error::new(
443 io::ErrorKind::InvalidInput,
444 format!("Node name wasn't complete!\n{:?}", node),
445 )));
446 }
447
448 let mut complete = Complete::default();
449 for (index, entry) in node.entries().iter().enumerate() {
450 if index == 0 {
451 let entry_complete_name = entry
452 .value()
453 .as_string()
454 .ok_or_else(|| {
455 UError::InvalidNodeFirstArg(io::Error::new(
456 io::ErrorKind::NotFound,
457 format!("No complete found in {:?}", entry),
458 ))
459 })?
460 .to_string();
461 complete.name = entry_complete_name;
462 }
463
464 if let Some(iden_name) = entry.name() {
465 match iden_name.value() {
466 "descriptions" => complete.descs = entry.value().as_bool().unwrap_or_default(),
467 "run" => {
468 let run = entry
469 .value()
470 .as_string()
471 .map(String::from)
472 .unwrap_or_default();
473 complete.kind = CompleteKind::Run(run);
474 }
475 "type" => {
476 let arg_type = entry.value().as_string().unwrap_or_default();
477 match arg_type {
478 "file" => complete.kind = CompleteKind::File,
479 _ => {}
480 }
481 }
482 _ => {}
483 }
484 }
485 }
486 Ok(complete)
487}
488
489pub fn parse_usage(node: &KdlNode) -> Result<Option<Usage>, UError> {
490 match node.name().value() {
491 "flag" => Ok(Some(Usage::Flag(parse_flag(node)?))),
492 "arg" => Ok(Some(Usage::Arg(parse_arg(node)?))),
493 "cmd" => Ok(Some(Usage::Cmd(parse_cmd(node)?))),
494 "complete" => {
495 let complete = parse_complete(node)?;
496 if !complete.kind.is_none() {
497 Ok(Some(Usage::Complete(complete)))
498 } else {
499 Ok(None)
500 }
501 }
502 _ => Ok(None),
503 }
504}
505
506impl Complete {
507 pub fn file_complete() -> Self {
508 Self {
509 name: "file".to_string(),
510 kind: CompleteKind::File,
511 descs: false,
512 }
513 }
514
515 pub fn dir_complete() -> Self {
516 Self {
517 name: "file".to_string(),
518 kind: CompleteKind::Dir,
519 descs: false,
520 }
521 }
522}
523
524impl PartialEq for Flag {
525 fn eq(&self, other: &Self) -> bool {
526 self.name == other.name && self.names.len() == other.names.len() && {
527 let a: HashSet<_> = self.names.iter().collect();
528 let b: HashSet<_> = other.names.iter().collect();
529 a == b
530 }
531 }
532}
533impl Eq for Flag {}
534
535impl PartialEq for Arg {
536 fn eq(&self, other: &Self) -> bool {
537 self.name == other.name
538 }
539}
540impl Eq for Arg {}
541
542impl PartialEq for Cmd {
543 fn eq(&self, other: &Self) -> bool {
544 self.name == other.name
545 }
546}
547impl Eq for Cmd {}
548
549impl PartialEq for Complete {
550 fn eq(&self, other: &Self) -> bool {
551 self.name == other.name
552 }
553}
554impl Eq for Complete {}
555
556impl CompleteKind {
557 pub fn is_none(&self) -> bool {
558 match self {
559 Self::None => true,
560 _ => false,
561 }
562 }
563
564 pub fn is_file(&self) -> bool {
565 match self {
566 Self::File => true,
567 _ => false,
568 }
569 }
570
571 pub fn run(&self) -> Option<&String> {
572 match self {
573 Self::Run(run) => Some(run),
574 _ => None,
575 }
576 }
577}
578
579#[derive(Debug)]
580pub enum UError {
581 InvalidNodeName(io::Error),
582 InvalidNodeFirstArg(io::Error),
583}
584
585impl std::fmt::Display for UError {
586 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
587 match self {
588 UError::InvalidNodeName(error) => error.fmt(f),
589 UError::InvalidNodeFirstArg(error) => error.fmt(f),
590 }
591 }
592}
593
594impl std::error::Error for UError {}
595
596impl From<UError> for io::Error {
597 fn from(value: UError) -> Self {
598 match value {
599 UError::InvalidNodeName(error) => error,
600 UError::InvalidNodeFirstArg(error) => error,
601 }
602 }
603}
604
605impl AsRef<Cmd> for Cmd {
606 fn as_ref(&self) -> &Cmd {
607 self
608 }
609}
610
611impl Default for CompleteKind {
612 fn default() -> Self {
613 Self::None
614 }
615}
616
617impl From<bool> for GlobalFlag {
618 fn from(value: bool) -> Self {
619 match value {
620 true => Self::Itself,
621 false => Self::None,
622 }
623 }
624}
625
626impl Flag {
627 pub fn is_global(&self) -> bool {
628 match self.global {
629 GlobalFlag::None => false,
630 _ => true,
631 }
632 }
633
634 pub fn is_global_itself(&self) -> bool {
635 match self.global {
636 GlobalFlag::Itself => true,
637 _ => false,
638 }
639 }
640
641 pub fn is_global_imposed(&self) -> bool {
642 match self.global {
643 GlobalFlag::Imposed(_) => true,
644 _ => false,
645 }
646 }
647}