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}