Skip to main content

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                "negate" => {
279                    let negate_flag = entry.value().as_string().unwrap_or_default().to_string();
280                    if !negate_flag.is_empty() {
281                        flag.names.push(negate_flag);
282                    }
283                }
284                _ => {}
285            }
286        }
287    }
288
289    if let Some(child_doc) = node.children() {
290        for child_node in child_doc.nodes() {
291            match child_node.name().value() {
292                "arg" => flag.arg = Some(parse_arg(child_node)?),
293                "alias" => flag.aliases = parse_alias(child_node)?,
294                "choices" => {
295                    if let Some(arg_name) = flag.names.pop() {
296                        let mut arg = Arg::default();
297                        arg.name = arg_name;
298                        arg.choices = parse_choices(child_node)?;
299                        if arg.name.starts_with("<") {
300                            arg.required = true;
301                        }
302                        flag.arg = Some(arg);
303                    }
304                }
305                _ => {}
306            }
307        }
308    }
309    Ok(flag)
310}
311
312pub fn parse_arg(node: &KdlNode) -> Result<Arg, UError> {
313    if node.name().value() != "arg" {
314        return Err(UError::InvalidNodeName(io::Error::new(
315            io::ErrorKind::InvalidInput,
316            format!("Node name wasn't arg!\n{:?}", node),
317        )));
318    }
319
320    let mut arg = Arg::default();
321    for (index, entry) in node.entries().iter().enumerate() {
322        if index == 0 {
323            let entry_arg_name = entry
324                .value()
325                .as_string()
326                .ok_or_else(|| {
327                    UError::InvalidNodeFirstArg(io::Error::new(
328                        io::ErrorKind::NotFound,
329                        format!("No arg found in {:?}", entry),
330                    ))
331                })?
332                .to_string();
333
334            if entry_arg_name.starts_with("<") {
335                arg.required = true;
336                let end = entry_arg_name.find(">").unwrap_or(entry_arg_name.len());
337                arg.name = entry_arg_name[1..end].to_string();
338            } else if entry_arg_name.starts_with("[") {
339                arg.required = false;
340                let end = entry_arg_name.find("]").unwrap_or(entry_arg_name.len());
341                arg.name = entry_arg_name[1..end].to_string();
342            }
343            arg.repr = entry_arg_name;
344        }
345
346        if let Some(iden_name) = entry.name() {
347            match iden_name.value() {
348                "hide" => arg.hide = entry.value().as_bool().unwrap_or_default(),
349                "default" => arg.default = entry.value().as_string().map(String::from),
350                "var" => arg.var = entry.value().as_bool().unwrap_or_default(),
351                "var_max" => arg.max = entry.value().as_integer(),
352                "var_min" => arg.min = entry.value().as_integer(),
353                _ => {}
354            }
355        }
356    }
357
358    arg.max = arg.max.or(Some(-1));
359    arg.min = arg.min.or(Some(0));
360
361    if let Some(child_doc) = node.children() {
362        for child_node in child_doc.nodes() {
363            if child_node.name().value() == "choices" {
364                let mut choices: Vec<String> = vec![];
365                for cn_entry in child_node.entries() {
366                    let choice = cn_entry
367                        .value()
368                        .as_string()
369                        .expect(format!("No choice found in {:?}", cn_entry).as_str())
370                        .to_string();
371                    choices.push(choice);
372                }
373                arg.choices = choices;
374            }
375        }
376    }
377    Ok(arg)
378}
379
380pub fn parse_cmd(node: &KdlNode) -> Result<Cmd, UError> {
381    if node.name().value() != "cmd" {
382        return Err(UError::InvalidNodeName(io::Error::new(
383            io::ErrorKind::InvalidInput,
384            format!("Node name wasn't cmd!\n{:?}", node),
385        )));
386    }
387
388    let mut cmd = Cmd::default();
389    for (index, entry) in node.entries().iter().enumerate() {
390        if index == 0 {
391            let entry_cmd_name = entry
392                .value()
393                .as_string()
394                .ok_or_else(|| {
395                    UError::InvalidNodeFirstArg(io::Error::new(
396                        io::ErrorKind::NotFound,
397                        format!("No cmd found in {:?}", entry),
398                    ))
399                })?
400                .to_string();
401
402            cmd.name = entry_cmd_name;
403        }
404
405        if let Some(iden_name) = entry.name() {
406            match iden_name.value() {
407                "help" => {
408                    cmd.help = entry
409                        .value()
410                        .as_string()
411                        .map(String::from)
412                        .unwrap_or_default()
413                }
414                "hide" => cmd.hide = entry.value().as_bool().unwrap_or_default(),
415                _ => {}
416            }
417        }
418    }
419
420    if let Some(child_doc) = node.children() {
421        for child_node in child_doc.nodes() {
422            match child_node.name().value() {
423                "alias" => {
424                    let mut alias = parse_alias(child_node)?;
425                    cmd.aliases.append(&mut alias);
426                }
427                "flag" => {
428                    let flag = parse_flag(child_node)?;
429                    cmd.flags.push(flag);
430                }
431                "arg" => {
432                    let arg = parse_arg(child_node)?;
433                    cmd.args.push(arg);
434                }
435                "cmd" => {
436                    let child_cmd = parse_cmd(child_node)?;
437                    cmd.cmds.push(Box::new(child_cmd));
438                }
439                _ => {}
440            }
441        }
442    }
443    Ok(cmd)
444}
445
446pub fn parse_complete(node: &KdlNode) -> Result<Complete, UError> {
447    if node.name().value() != "complete" {
448        return Err(UError::InvalidNodeName(io::Error::new(
449            io::ErrorKind::InvalidInput,
450            format!("Node name wasn't complete!\n{:?}", node),
451        )));
452    }
453
454    let mut complete = Complete::default();
455    for (index, entry) in node.entries().iter().enumerate() {
456        if index == 0 {
457            let entry_complete_name = entry
458                .value()
459                .as_string()
460                .ok_or_else(|| {
461                    UError::InvalidNodeFirstArg(io::Error::new(
462                        io::ErrorKind::NotFound,
463                        format!("No complete found in {:?}", entry),
464                    ))
465                })?
466                .to_string();
467            complete.name = entry_complete_name;
468        }
469
470        if let Some(iden_name) = entry.name() {
471            match iden_name.value() {
472                "descriptions" => complete.descs = entry.value().as_bool().unwrap_or_default(),
473                "run" => {
474                    let run = entry
475                        .value()
476                        .as_string()
477                        .map(String::from)
478                        .unwrap_or_default();
479                    complete.kind = CompleteKind::Run(run);
480                }
481                "type" => {
482                    let arg_type = entry.value().as_string().unwrap_or_default();
483                    match arg_type {
484                        "file" => complete.kind = CompleteKind::File,
485                        _ => {}
486                    }
487                }
488                _ => {}
489            }
490        }
491    }
492    Ok(complete)
493}
494
495pub fn parse_usage(node: &KdlNode) -> Result<Option<Usage>, UError> {
496    match node.name().value() {
497        "flag" => Ok(Some(Usage::Flag(parse_flag(node)?))),
498        "arg" => Ok(Some(Usage::Arg(parse_arg(node)?))),
499        "cmd" => Ok(Some(Usage::Cmd(parse_cmd(node)?))),
500        "complete" => {
501            let complete = parse_complete(node)?;
502            if !complete.kind.is_none() {
503                Ok(Some(Usage::Complete(complete)))
504            } else {
505                Ok(None)
506            }
507        }
508        _ => Ok(None),
509    }
510}
511
512impl Complete {
513    pub fn file_complete() -> Self {
514        Self {
515            name: "file".to_string(),
516            kind: CompleteKind::File,
517            descs: false,
518        }
519    }
520
521    pub fn dir_complete() -> Self {
522        Self {
523            name: "file".to_string(),
524            kind: CompleteKind::Dir,
525            descs: false,
526        }
527    }
528}
529
530impl PartialEq for Flag {
531    fn eq(&self, other: &Self) -> bool {
532        self.name == other.name && self.names.len() == other.names.len() && {
533            let a: HashSet<_> = self.names.iter().collect();
534            let b: HashSet<_> = other.names.iter().collect();
535            a == b
536        }
537    }
538}
539impl Eq for Flag {}
540
541impl PartialEq for Arg {
542    fn eq(&self, other: &Self) -> bool {
543        self.name == other.name
544    }
545}
546impl Eq for Arg {}
547
548impl PartialEq for Cmd {
549    fn eq(&self, other: &Self) -> bool {
550        self.name == other.name
551    }
552}
553impl Eq for Cmd {}
554
555impl PartialEq for Complete {
556    fn eq(&self, other: &Self) -> bool {
557        self.name == other.name
558    }
559}
560impl Eq for Complete {}
561
562impl CompleteKind {
563    pub fn is_none(&self) -> bool {
564        match self {
565            Self::None => true,
566            _ => false,
567        }
568    }
569
570    pub fn is_file(&self) -> bool {
571        match self {
572            Self::File => true,
573            _ => false,
574        }
575    }
576
577    pub fn run(&self) -> Option<&String> {
578        match self {
579            Self::Run(run) => Some(run),
580            _ => None,
581        }
582    }
583}
584
585#[derive(Debug)]
586pub enum UError {
587    InvalidNodeName(io::Error),
588    InvalidNodeFirstArg(io::Error),
589}
590
591impl std::fmt::Display for UError {
592    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
593        match self {
594            UError::InvalidNodeName(error) => error.fmt(f),
595            UError::InvalidNodeFirstArg(error) => error.fmt(f),
596        }
597    }
598}
599
600impl std::error::Error for UError {}
601
602impl From<UError> for io::Error {
603    fn from(value: UError) -> Self {
604        match value {
605            UError::InvalidNodeName(error) => error,
606            UError::InvalidNodeFirstArg(error) => error,
607        }
608    }
609}
610
611impl AsRef<Cmd> for Cmd {
612    fn as_ref(&self) -> &Cmd {
613        self
614    }
615}
616
617impl Default for CompleteKind {
618    fn default() -> Self {
619        Self::None
620    }
621}
622
623impl From<bool> for GlobalFlag {
624    fn from(value: bool) -> Self {
625        match value {
626            true => Self::Itself,
627            false => Self::None,
628        }
629    }
630}
631
632impl Flag {
633    pub fn is_global(&self) -> bool {
634        match self.global {
635            GlobalFlag::None => false,
636            _ => true,
637        }
638    }
639
640    pub fn is_global_itself(&self) -> bool {
641        match self.global {
642            GlobalFlag::Itself => true,
643            _ => false,
644        }
645    }
646
647    pub fn is_global_imposed(&self) -> bool {
648        match self.global {
649            GlobalFlag::Imposed(_) => true,
650            _ => false,
651        }
652    }
653}