cuc/
usage.rs

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            // the longest flag name is set as an identifier to flag.name
236            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}