commander_core/
lib.rs

1//! This crate is using for `commander_rust`.
2//!
3//! Only `Application`, `Cli`, `Raw` you will use.
4//!
5//! `Application` will be returned by macro `run!()`.
6//! It's readonly. You can get application information through it.
7//! `Application` contains all information you defined using `#[option]` ,`#[command]` and `#[entry]`.
8//! See `Application` for more details.
9//!
10//! `Cli` is an interface of CLI. You can get all argument of options through it.
11//! `Cli` offered two convenient method to get argument of options.
12//! They are `get(idx: &str) -> Raw` and `get_or<T: From<Raw>>(&self, idx: &str, d: T) -> T`.
13//! See `Cli` for more details.
14//!
15//!
16//! `Raw` is encapsulation of something. It a sequence of String.
17//! You can regard it as `Raw(<Vec<String>>)`. In fact, it is.
18//! `Raw` is using for types convert.
19//! Any type implemented `From<Raw>` can be types of command processing functions' parameter.
20//! For example, `Vec<i32>` implemented `From<Raw>`. So you can use it like `fn method(v: Vec<i32>)`.
21//! But `slice` is not implemented `From<Raw>`, so you can not use it like `fn method(s: [i32])`.
22//! Once type implemented `From<Raw>`, you can covert it's type using `let right: bool = raw.into()`.
23//!
24
25
26mod raw;
27mod fmt;
28mod pattern;
29
30use std::ops::Index;
31use std::collections::HashMap;
32
33use pattern::{ Pattern, PatternType };
34pub use raw::Raw;
35use std::process::exit;
36
37/// The type of argument.
38///
39/// they are:
40/// - <rs>    -- RequiredSingle
41/// - [os]    -- OptionalSingle
42/// - <rm...> -- RequiredMultiple
43/// - [om...] -- OptionalMultiple
44///
45///
46/// For most of the times, you will not use it.
47///
48///
49#[doc(hidden)]
50#[derive(PartialEq, Eq)]
51#[derive(Debug, Clone)]
52pub enum ArgumentType {
53    RequiredSingle,
54    OptionalSingle,
55    RequiredMultiple,
56    OptionalMultiple,
57}
58
59/// Represents a parameter.
60///
61/// For example, `<dir>` will represents as
62/// ```ignore
63/// Argument {
64///     name: "dir",
65///     ty: ArgumentType::RequiredSingle,
66/// }
67/// ```
68///
69/// For most of the time, you will not use it.
70#[doc(hidden)]
71#[derive(PartialEq, Eq)]
72#[derive(Clone)]
73pub struct Argument {
74    pub name: String,
75    pub ty: ArgumentType,
76}
77
78/// Represents an application.
79///
80/// Application is what? Application is generated from your code.
81/// If you use `#[command]`, application will get a `Command`.
82/// If you use `#[options]`, application will get a `Options`.
83/// If you write descriptions in your `Cargo.toml`, application will get a `desc`.
84/// If you write version in your `Cargo.toml`, application will get a `ver`.
85///
86/// For most of the time, you will use all of them.
87///
88/// And we offer a way to get the only application of your CLI.
89/// Using `commander_rust::run!()`(instead of `commander_rust::run()`, it's a proc_macro) to get it.
90///
91/// # Note
92/// It's generated by `commander_rust`, and it should be readonly.
93///
94pub struct Application {
95    pub name: String,
96    pub desc: String,
97    pub cmds: Vec<Command>,
98    pub opts: Vec<Options>,
99    pub direct_args: Vec<Argument>,
100}
101
102/// Represents a instance defined by `#[command]`.
103///
104/// For example, `#[command(rmir <dir> [others...], "remove files")]` will generate a instance like:
105/// ```ignore
106/// Command {
107///     name: "rmdir",
108///     args: [
109///         Argument {
110///             name: "dir",
111///             ty: ArgumentType::RequiredSingle,
112///         },
113///         Argument {
114///             name: "others",
115///             ty: ArgumentType::OptionalMultiple,
116///         }
117///     ],
118///     desc: Some("remove files"),
119///     opts: ...
120/// }
121/// ```
122///
123/// `opts` is determined by `#[option]` before `#[command]`.
124///
125/// # Note
126/// `#[command]` should be and only should be defined after all `#[option]`.
127/// It means:
128/// ```ignore
129/// // correct
130/// #[option(...)]
131/// #[command(test ...)]
132/// fn test(...) {}
133///
134/// // fault
135/// #[command(test ...)]
136/// #[option(...)]
137/// fn test(...) {}
138/// ```
139/// And the name in `#[command]` have to be same as the name of corresponding functions.
140/// In this example, they are `test`.
141/// For most of the time, you will not use it.
142///
143#[doc(hidden)]
144pub struct Command {
145    pub name: String,
146    pub args: Vec<Argument>,
147    pub desc: Option<String>,
148    pub opts: Vec<Options>,
149}
150
151/// Represents a instance defined by `#[option]`.
152///
153/// # Note
154/// `#[option]` only accepts up to one argument. And one `#[command]` can accept many `#[option]`.
155/// It's similar with `Command`. See `Command` for more detail.
156/// For most of the time, you will not use it.
157#[derive(Debug)]
158#[doc(hidden)]
159pub struct Options {
160    pub short: String,
161    pub long: String,
162    pub arg: Option<Argument>,
163    pub desc: Option<String>,
164}
165
166/// A divided set of user's inputs.
167///
168///
169/// For example, when you input `[pkg-name] rmdir /test/ -rf`, `commander_rust` will generate something like
170/// ```ignore
171/// [
172///     Instance {
173///         name: "rmdir",
174///         args: ["/test/"],
175///     },
176///     Instance {
177///         name: "r",
178///         args: vec![],
179///     },
180///     Instance {
181///         name: "f",
182///         args: vec![],
183///     }
184/// ]
185/// ```
186/// For most of the time, you will not use it.
187#[derive(Debug, Eq, PartialEq)]
188#[doc(hidden)]
189pub struct Instance {
190    pub name: String,
191    pub args: Vec<String>,
192}
193
194/// `Cmd` is not `Command`. It's defined by the user's input.
195///
196/// `name` is the first elements of inputs if the first element is one of the name of any `Command`.
197/// `raws` is followed element after the first element, until something like `-rf`,`--simple=yes` appears.
198/// `raws` is `Vec<Raw>`, because we one command maybe has more than one arguments.
199/// See `Raw` for more details.
200/// `opt_raws` is a `HashMap`. It stores elements user input that `Options` might use.
201/// It's hard to understand what `Cmd' is for. Many people get confused.
202/// But fortunately, for most of the time(even forever), you will not use it.
203#[derive(Debug)]
204#[doc(hidden)]
205pub struct Cmd {
206    pub name: String,
207    pub raws: Vec<Raw>,
208    pub opt_raws: HashMap<String, Raw>,
209}
210
211
212/// Something like `Cmd`.
213///
214/// But unfortunately, you will use it frequently.
215///
216/// `commander_rust` will generate a instance of `Application` according your code. It happens in compile-time.
217/// `commander_rust` will generate a instance of `Cli` according user's input. It happens in run-time.
218
219/// What' the difference?
220/// Content in `Application` will be replaced by something concrete through user's input.
221/// For example, If your code is like this:
222/// ```ignore
223/// #[option(-r, --recursive [dir...], "recursively")]
224/// #[command(rmdir <dir> [otherDirs...], "remove files and directories")]
225/// fn rmdir(dir: i32, other_dirs: Option<Vec<bool>>, cli: Cli) {
226///     let r: bool = cli.get("recursive").into();
227/// }
228/// ```
229/// Let's see. The last argument of function is `Cli` type(you can miss it).
230/// So when we want to do something if `--recursive` is offered by user, how can we?
231/// You just need to code like `let r: ? = cli.get("recursive").into()`,
232/// then you can get contents of `recursive` options if user has inputted it.
233///
234/// That's why `Cli` will be used frequently.
235///
236#[derive(Debug)]
237pub struct Cli {
238    pub cmd: Option<Cmd>,
239    pub global_raws: HashMap<String, Raw>,
240    pub direct_args: Vec<Raw>,
241}
242
243impl Application {
244    /// Deriving `#[option(-h, --help, "output usage information")]`
245    /// and `#[option(-V, --version, "output the version number")]` for all `Command` and `Application`.
246    /// Dont use it!
247    #[doc(hidden)]
248    pub fn derive(&mut self) {
249        self.opts.push(Options {
250            short: String::from("h"),
251            long: String::from("help"),
252            arg: None,
253            desc: Some(String::from("output usage information")),
254        });
255        self.opts.push(Options {
256            short: String::from("V"),
257            long: String::from("version"),
258            arg: None,
259            desc: Some(String::from("output the version number")),
260        });
261        self.derive_cmds();
262    }
263
264    /// Deriving `#[option(-h, --help, "output usage information")]`
265    /// and `#[option(-V, --version, "output the version number")]` for all `Command`.
266    /// Dont use it!
267    #[doc(hidden)]
268    fn derive_cmds(&mut self) {
269        for cmd in &mut self.cmds {
270            cmd.derive();
271        }
272    }
273
274    pub fn contains_key(&self, idx: &str) -> bool {
275        for opt in self.opts.iter() {
276            if opt.long == idx || opt.short == idx {
277                return true;
278            }
279        }
280
281        for cmd in self.cmds.iter() {
282            for opt in cmd.opts.iter() {
283                if opt.long == idx || opt.short == idx {
284                    return true;
285                }
286            }
287        }
288
289        false
290    }
291}
292
293impl Command {
294    /// Deriving `#[option(-h, --help, "output usage information")]`
295    /// and `#[option(-V, --version, "output the version number")]` for `Command`.
296    /// Dont use it!
297    #[doc(hidden)]
298    pub fn derive(&mut self) {
299        self.opts.push(Options {
300            short: String::from("h"),
301            long: String::from("help"),
302            arg: None,
303            desc: Some(String::from("output usage information")),
304        });
305    }
306}
307
308impl Cli {
309    /// Create a empty `Cli`.
310    #[doc(hidden)]
311    pub fn empty() -> Cli {
312        Cli {
313            cmd: None,
314            global_raws: HashMap::new(),
315            direct_args: vec![],
316        }
317    }
318
319    /// Get the content of `Options`.
320    /// `Options` has two types, one is private, the other is global. Of course they are same.
321    /// Private means they belong to the command.
322    /// Global means they belong to the global.
323    ///
324    /// Private is more weight than global.
325    pub fn get(&self, idx: &str) -> Raw {
326        if self.cmd.is_some() && self.cmd.as_ref().unwrap().has(idx) {
327            self.cmd.as_ref().unwrap()[idx].clone()
328        } else if self.global_raws.contains_key(idx) {
329            self.global_raws[idx].clone()
330        } else {
331            Raw::new(vec![])
332        }
333    }
334
335    /// Getting contents of `Options`. if `idx` dont exist, return `default`.
336    pub fn get_or<T: From<Raw>>(&self, idx: &str, d: T) -> T {
337        if self.has(idx) {
338            self.get(idx).into()
339        } else {
340            d
341        }
342    }
343
344    /// Get contents of `Options`. if `idx` dont exist, call f.
345    ///
346    /// f should return a value of type T.
347    pub fn get_or_else<T: From<Raw>, F>(&self, idx: &str, f: F) -> T
348    where F: FnOnce() -> T {
349        if self.has(idx) {
350            self.get(idx).into()
351        } else {
352            f()
353        }
354    }
355
356    /// Check user input a option or not.
357    pub fn has(&self, idx: &str) -> bool {
358        (self.cmd.is_some() && self.cmd.as_ref().unwrap().has(idx)) || self.global_raws.contains_key(idx)
359    }
360
361    /// Inner function, dont use it.
362    #[doc(hidden)]
363    pub fn from(instances: &Vec<Instance>, app: &Application) -> Option<Cli> {
364        if instances.is_empty() {
365            None
366        } else {
367            let cmd = Cmd::from(instances, &app.cmds);
368            let mut global_raws = HashMap::new();
369
370            // if sub-command is offered, add options that doesn't exist in this sub-command into global_raws
371            // if it doesn't, all options should belong to the global_raws
372            // both case, options should be checked that if they are defined or not
373            // for using easily, short & long are pushed into global_raws, so does `Cmd`
374            for ins in instances.iter() {
375                let matched = app.opts.iter().find(|o| o.long == ins.name || o.short == ins.name);
376
377                if let Some(matched) = matched {
378                    if let Some(cmd) = &cmd {
379                        if !cmd.has(&matched.long) {
380                            let raw = Raw::divide_opt(ins, &matched.arg);
381
382                            global_raws.insert(matched.long.clone(), raw.clone());
383                            global_raws.insert(matched.short.clone(), raw);
384                        }
385                    } else {
386                        let raw = Raw::divide_opt(ins, &matched.arg);
387
388                        global_raws.insert(matched.long.clone(), raw.clone());
389                        global_raws.insert(matched.short.clone(), raw);
390                    }
391                }
392            }
393
394            Some(Cli {
395                cmd,
396                global_raws,
397                direct_args: {
398                    if instances[0].is_empty() && !instances[0].args.is_empty() {
399                        Raw::divide_cmd(&instances[0], &app.direct_args)
400                    } else {
401                        vec![]
402                    }
403                }
404            })
405        }
406    }
407
408    #[doc(hidden)]
409    pub fn get_raws(&self) -> Vec<Raw> {
410        if let Some(cmd) = &self.cmd {
411            cmd.raws.clone()
412        } else {
413            vec![]
414        }
415    }
416
417    /// Get the name of the only command inputted by user.
418    ///
419    /// For Exanple, If user input `[pkg-name] rmdir -rf ./*`,
420    /// then the name is `rmdir`.
421    ///
422    #[doc(hidden)]
423    pub fn get_name(&self) -> String {
424        if let Some(cmd) = &self.cmd {
425            cmd.name.clone()
426        } else {
427            String::new()
428        }
429    }
430}
431
432impl Cmd {
433    /// Create a `Cmd` using offered name.
434    #[doc(hidden)]
435    fn new(name: String) -> Cmd {
436        Cmd {
437            name,
438            raws: vec![],
439            opt_raws: HashMap::new(),
440        }
441    }
442
443    #[doc(hidden)]
444    fn push(&mut self, arg: Raw) {
445        self.raws.push(arg);
446    }
447
448    #[doc(hidden)]
449    fn insert(&mut self, key: String, arg: Raw) {
450        if !self.opt_raws.contains_key(&key) {
451            self.opt_raws.insert(key, arg);
452        }
453    }
454
455    #[doc(hidden)]
456    fn append(&mut self, raws: Vec<Raw>) {
457        raws.into_iter().for_each(|r| self.push(r));
458    }
459
460    fn get_cmd_idx(instances: &Vec<Instance>, commands: &Vec<Command>) -> Option<usize> {
461        for (idx, ins) in instances.iter().enumerate() {
462            if commands.iter().any(|c| c.name == ins.name) {
463                return Some(idx);
464            }
465        }
466
467        None
468    }
469
470    /// Check user input a option or not. Used by `Cli::has`.
471    ///
472    /// Dont use it.
473    #[doc(hidden)]
474    pub fn has(&self, idx: &str) -> bool {
475        self.opt_raws.contains_key(idx)
476    }
477
478    /// Inner function, don't use it.
479    #[doc(hidden)]
480    pub fn from(instances: &Vec<Instance>, commands: &Vec<Command>) -> Option<Cmd> {
481        let mut result = Cmd::new(String::new());
482
483        if instances.is_empty() {
484            None
485        } else {
486            let idx = Cmd::get_cmd_idx(instances, commands);
487            let head;
488            let n;
489
490            if let Some(idx) = idx {
491                head = instances.get(idx).unwrap();
492                n = idx + 1;
493            } else {
494                return None;
495            }
496
497            let cmd = commands.iter().find(|c| c.name == head.name);
498
499            // user calls sub-command or not
500            if let Some(sub_cmd) = cmd {
501                let raws = Raw::divide_cmd(head, &sub_cmd.args);
502
503                result.name = sub_cmd.name.clone();
504                // get raws of arguments
505                result.append(raws);
506
507                // get all raws of all options
508                for ins in instances.iter().skip(n) {
509                    let matched = sub_cmd.opts.iter().find(|o| (o.long == ins.name || o.short == ins.name));
510
511                    if let Some(matched) = matched {
512                        let raw = Raw::divide_opt(ins, &matched.arg);
513
514                        result.insert(matched.long.clone(), raw.clone());
515                        result.insert(matched.short.clone(), raw);
516                    }
517                }
518
519                Some(result)
520            } else {
521                None
522            }
523        }
524    }
525}
526
527impl Index<&str> for Cmd {
528    type Output = Raw;
529
530    fn index(&self, idx: &str) -> &Raw {
531        &self.opt_raws[idx]
532    }
533}
534
535impl Instance {
536    /// Check instance is empty or not.
537    #[doc(hidden)]
538    pub fn is_empty(&self) -> bool {
539        self.name.is_empty()
540    }
541
542    /// Create an empty `Instance`.
543    #[doc(hidden)]
544    pub fn empty() -> Instance {
545        Instance {
546            name: String::new(),
547            args: vec![],
548        }
549    }
550
551    /// Create an `Instance` using offered name.
552    #[doc(hidden)]
553    pub fn new(name: &str) -> Instance {
554        Instance {
555            name: String::from(name),
556            args: vec![],
557        }
558    }
559}
560
561pub fn normalize(args: Vec<String>, app: &Application) -> Vec<Instance> {
562    let mut instances = vec![];
563    let mut head = Instance::empty();
564    let mut args = args.into_iter().skip(1);
565    let mut flag = false;
566
567    while let Some(arg) = args.next() {
568        let reg = Pattern::match_str(&arg);
569
570        match reg.ty {
571            PatternType::Stmt => {
572                if app.contains_key(reg.groups[0]) {
573                    let mut all_opts: Vec<&str> = reg.groups[1].split_terminator(" ").collect();
574
575                    if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
576                        instances.push(head);
577                    }
578
579                    head = Instance::empty();
580                    all_opts.dedup_by(|a, b| a == b);
581                    all_opts.retain(|x| !x.is_empty());
582
583                    instances.push(Instance {
584                        name: String::from(reg.groups[0]),
585                        args: all_opts.into_iter().map(|x| String::from(x)).collect(),
586                    });
587                } else {
588                    eprintln!("Unknown option: --{}", reg.groups[0]);
589                    exit(-1);
590                }
591            },
592            PatternType::Short => {
593                let mut all_opts: Vec<&str> = reg.groups[0].split("").collect();
594
595                all_opts.dedup_by(|a, b| a == b);
596                all_opts.retain(|x| !x.is_empty());
597
598                if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
599                    instances.push(head);
600                }
601
602                for x in all_opts.into_iter() {
603                    if x.len() == 1 {
604                        if app.contains_key(x) {
605                            instances.push(Instance::new(x));
606                        } else {
607                            eprintln!("Unknown option: -{}", x);
608                            exit(-1);
609                        }
610                    }
611                }
612
613                head = instances.pop().unwrap_or(Instance::empty());
614            },
615            PatternType::Long => {
616                if app.contains_key(reg.groups[0]) {
617                    if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
618                        instances.push(head);
619                    }
620
621                    head = Instance::new(reg.groups[0]);
622                } else {
623                    eprintln!("Unknown option: --{}", reg.groups[0]);
624                    exit(-1);
625                }
626            },
627            PatternType::Word => {
628                if app.cmds.iter().any(|c| c.name == arg) && !flag {
629                    if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
630                        instances.push(head);
631                    }
632
633                    head = Instance::new(&arg);
634                    flag = true;
635                } else {
636                    head.args.push(arg);
637                }
638            },
639            _ => {
640                head.args.push(arg);
641            },
642        }
643    }
644
645    if !head.is_empty() || (!head.args.is_empty() && instances.is_empty()) {
646        instances.push(head);
647    }
648
649    instances
650}