1use crate::command::Command;
2use crate::option::CommandOption;
3use crate::suggestion::SuggestionSource;
4use std::fmt::{Debug, Formatter};
5use crate::utils::debug_option;
6use crate::Argument;
7use crate::help::HelpSource;
8
9#[derive(Clone)]
37pub struct Context {
38 root: Command,
39 suggestions: Option<SuggestionSource>,
40 help: HelpSource,
41 name_prefixes: Vec<String>,
42 alias_prefixes: Vec<String>,
43 assign_operators: Vec<char>,
44 delimiter: char,
45 help_option: Option<CommandOption>,
46 help_command: Option<Command>,
47 version_option: Option<CommandOption>,
48 version_command: Option<Command>,
49}
50
51impl Context {
52 #[inline]
54 pub fn new(root: Command) -> Self {
55 ContextBuilder::new(root).build()
56 }
57
58 #[inline]
60 pub fn builder(root: Command) -> ContextBuilder {
61 ContextBuilder::new(root)
62 }
63
64 pub fn root(&self) -> &Command {
66 &self.root
67 }
68
69 pub fn name_prefixes(&self) -> Prefixes<'_> {
71 Prefixes {
72 iter: self.name_prefixes.iter()
73 }
74 }
75
76 pub fn alias_prefixes(&self) -> Prefixes<'_> {
78 Prefixes {
79 iter: self.alias_prefixes.iter()
80 }
81 }
82
83 pub fn assign_operators(&self) -> impl ExactSizeIterator<Item = &char> {
85 self.assign_operators.iter()
86 }
87
88 pub fn delimiter(&self) -> char {
90 self.delimiter
91 }
92
93 pub fn suggestions(&self) -> Option<&SuggestionSource> {
95 self.suggestions.as_ref()
96 }
97
98 pub fn help(&self) -> &HelpSource {
100 &self.help
101 }
102
103 pub fn help_option(&self) -> Option<&CommandOption> {
105 self.help_option.as_ref()
106 }
107
108 pub fn help_command(&self) -> Option<&Command> {
110 self.help_command.as_ref()
111 }
112
113 pub fn version_option(&self) -> Option<&CommandOption> {
115 self.version_option.as_ref()
116 }
117
118 pub fn version_command(&self) -> Option<&Command> {
120 self.version_command.as_ref()
121 }
122
123 pub fn set_suggestions(&mut self, suggestions: SuggestionSource) {
125 self.suggestions = Some(suggestions);
126 }
127
128 pub fn set_help(&mut self, help: HelpSource) {
130 self.help = help;
131 }
132
133 pub fn set_help_option(&mut self, option: CommandOption) {
135 assert!(self.help_option.is_none(), "`Context` already contains a help option");
136 self.help_option = Some(option);
137 add_command_builtin_help_option(self);
138 }
139
140 pub fn set_help_command(&mut self, command: Command) {
142 assert!(self.help_command.is_none(), "`Context` already contains a help command");
143 self.help_command = Some(command);
144 add_command_builtin_help_command(self);
145 }
146
147 pub fn set_version_option(&mut self, option: CommandOption) {
149 assert!(self.version_option.is_none(), "`Context` already contains a version option");
150 self.version_option = Some(option);
151 add_command_builtin_version_option(self);
152 }
153
154 pub fn set_version_command(&mut self, command: Command) {
156 assert!(self.version_command.is_none(), "`Context` already contains a version command");
157 self.version_command = Some(command);
158 add_command_builtin_version_command(self);
159 }
160
161 pub fn get_option(&self, name_or_alias: &str) -> Option<&CommandOption> {
163 if let Some(opt) = self.root().get_options().get(name_or_alias) {
164 return Some(opt);
165 }
166
167 for child in self.root().get_subcommands() {
168 if let Some(opt) = child.get_options().get(name_or_alias) {
169 return Some(opt);
170 }
171 }
172
173 None
174 }
175
176 pub fn get_command(&self, name: &str) -> Option<&Command> {
178 self.root().get_subcommands().find(|c| c.get_name() == name)
179 }
180
181 pub fn is_name_prefix(&self, value: &str) -> bool {
183 self.name_prefixes.iter().any(|s| s == value)
184 }
185
186 pub fn is_alias_prefix(&self, value: &str) -> bool {
188 self.alias_prefixes.iter().any(|s| s == value)
189 }
190
191 pub fn trim_prefix<'a>(&self, option: &'a str) -> &'a str {
193 self.name_prefixes.iter()
194 .chain(self.alias_prefixes.iter())
195 .find(|prefix| option.starts_with(prefix.as_str()))
196 .map(|prefix| option.strip_prefix(prefix))
197 .flatten()
198 .unwrap_or(option)
199 }
200}
201
202impl Debug for Context {
203 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204 f.debug_struct("Context")
205 .field("root", &self.root)
206 .field("suggestions", &debug_option(&self.suggestions, "SuggestionSource"))
207 .field("help", &"HelpSource")
208 .field("name_prefixes", &self.name_prefixes)
209 .field("alias_prefixes", &self.alias_prefixes)
210 .field("assign_operators", &self.assign_operators)
211 .field("delimiter", &self.delimiter)
212 .field("help_option", &self.help_option)
213 .field("help_command", &self.help_command)
214 .field("version_option", &self.version_option)
215 .field("version_command", &self.version_command)
216 .finish()
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct Prefixes<'a> {
223 iter: std::slice::Iter<'a, String>
224}
225
226impl<'a> Iterator for Prefixes<'a> {
227 type Item = &'a String;
228
229 fn next(&mut self) -> Option<Self::Item> {
230 self.iter.next()
231 }
232}
233
234impl<'a> ExactSizeIterator for Prefixes<'a>{
235 fn len(&self) -> usize {
236 self.iter.len()
237 }
238}
239
240#[derive(Clone)]
242pub struct ContextBuilder {
243 root: Command,
244 suggestions: Option<SuggestionSource>,
245 help: Option<HelpSource>,
246 name_prefixes: Vec<String>,
247 alias_prefixes: Vec<String>,
248 assign_operators: Vec<char>,
249 delimiter: Option<char>,
250 help_option: Option<CommandOption>,
251 help_command: Option<Command>,
252 version_option: Option<CommandOption>,
253 version_command: Option<Command>,
254}
255
256impl ContextBuilder {
257 pub fn new(root: Command) -> Self {
259 ContextBuilder {
260 root,
261 suggestions: None,
262 help: None,
263 name_prefixes: Default::default(),
264 alias_prefixes: Default::default(),
265 assign_operators: Default::default(),
266 delimiter: None,
267 help_option: None,
268 help_command: None,
269 version_option: None,
270 version_command: None,
271 }
272 }
273
274 pub fn name_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
276 let prefix = prefix.into();
277 assert_valid_symbol("prefixes", prefix.as_str());
278 self.name_prefixes.push(prefix);
279 self
280 }
281
282 pub fn alias_prefix<S: Into<String>>(mut self, prefix: S) -> Self {
284 let prefix = prefix.into();
285 assert_valid_symbol("prefixes", prefix.as_str());
286 self.alias_prefixes.push(prefix);
287 self
288 }
289
290 pub fn assign_operator(mut self, value: char) -> Self {
292 assert_valid_symbol("assign chars", value.encode_utf8(&mut [0;4]));
294 self.assign_operators.push(value);
295 self
296 }
297
298 pub fn delimiter(mut self, value: char) -> Self {
300 assert_valid_symbol("delimiters", value.encode_utf8(&mut [0;4]));
302 self.delimiter = Some(value);
303 self
304 }
305
306 pub fn suggestions(mut self, suggestions: SuggestionSource) -> Self {
308 self.suggestions = Some(suggestions);
309 self
310 }
311
312 pub fn help(mut self, help: HelpSource) -> Self {
314 self.help = Some(help);
315 self
316 }
317
318 pub fn help_option(mut self, option: CommandOption) -> Self {
320 assert_is_help_option(&option);
321 self.help_option = Some(option);
322 self
323 }
324
325 pub fn help_command(mut self, command: Command) -> Self {
327 assert_is_help_command(&command);
328 self.help_command = Some(command);
329 self
330 }
331
332 pub fn version_option(mut self, option: CommandOption) -> Self {
334 assert_is_version_option(&option);
335 self.version_option = Some(option);
336 self
337 }
338
339 pub fn version_command(mut self, command: Command) -> Self {
341 assert_is_version_command(&command);
342 self.version_command = Some(command);
343 self
344 }
345
346 pub fn build(mut self) -> Context {
348 let mut context = Context {
349 root: self.root,
351
352 suggestions: self.suggestions,
354
355 help: self.help.unwrap_or_else(|| HelpSource::default()),
357
358 name_prefixes: {
360 if self.name_prefixes.is_empty() {
361 self.name_prefixes.push("--".to_owned());
362 }
363 self.name_prefixes
364 },
365
366 alias_prefixes: {
368 if self.alias_prefixes.is_empty() {
369 self.alias_prefixes.push("-".to_owned());
370 }
371 self.alias_prefixes
372 },
373
374 assign_operators: {
376 if self.assign_operators.is_empty() {
377 self.assign_operators.push('=');
378 }
379 self.assign_operators
380 },
381
382 delimiter: self.delimiter.unwrap_or(','),
384
385 help_option: self.help_option,
387
388 help_command: self.help_command,
390
391 version_option: self.version_option,
393
394 version_command: self.version_command
396 };
397
398 add_command_builtin_help_option(&mut context);
399 add_command_builtin_help_command(&mut context);
400 add_command_builtin_version_option(&mut context);
401 add_command_builtin_version_command(&mut context);
402 context
403 }
404}
405
406#[inline]
407#[doc(hidden)]
408pub fn default_version_option() -> CommandOption {
409 CommandOption::new("version")
410 .alias("v")
411 .description("Shows the version of the command")
412}
413
414#[inline]
415#[doc(hidden)]
416pub fn default_version_command() -> Command {
417 Command::new("version")
418 .description("Shows the version of the command")
419}
420
421#[inline]
422#[doc(hidden)]
423pub fn default_help_option() -> CommandOption {
424 CommandOption::new("help")
425 .alias("h")
426 .description("Shows help information about a command")
427 .hidden(true)
428 .arg(Argument::zero_or_more("command"))
429}
430
431#[inline]
432#[doc(hidden)]
433pub fn default_help_command() -> Command {
434 Command::new("help")
435 .description("Shows help information about a command")
436 .arg(Argument::zero_or_more("command"))
437}
438
439#[inline]
440fn assert_valid_symbol(source: &str, value: &str) {
441 for c in value.chars() {
442 if c.is_whitespace() {
443 panic!("{} cannot contains whitespaces: `{}`", source, value);
444 }
445
446 if c.is_ascii_alphanumeric() {
447 panic!("{} cannot contains numbers or letters: `{}`", source, value);
448 }
449 }
450}
451
452#[inline]
453fn assert_is_help_option(option: &CommandOption) {
454 let arg = option.get_arg().expect("help option must take only 1 argument");
455 assert_eq!(arg.get_values_count().min(), Some(0), "help option argument must take any count of values");
456 assert_eq!(arg.get_values_count().max(), None, "help option argument must take any count of values");
457}
458
459#[inline]
460fn assert_is_help_command(command: &Command) {
461 let arg = command.get_arg().expect("help command must take only 1 argument");
462 assert_eq!(arg.get_values_count().min(), Some(0), "help command argument must take any count of values");
463 assert_eq!(arg.get_values_count().max(), None, "help command argument must take any count of values");
464}
465
466#[inline]
467fn assert_is_version_option(option: &CommandOption) {
468 if option.get_arg().is_some() {
469 panic!("version option must take no arguments");
470 }
471}
472
473#[inline]
474fn assert_is_version_command(command: &Command) {
475 if command.get_arg().is_some() {
476 panic!("version command must take no arguments");
477 }
478}
479
480#[inline]
481fn add_command_builtin_help_option(context: &mut Context) {
482 if context.root.get_subcommands().count() > 0 {
483 if let Some(help_option) = context.help_option.as_ref().cloned() {
484 let command = &mut context.root;
485 add_option_recursive(command, help_option);
486 }
487 }
488}
489
490#[inline]
491fn add_command_builtin_help_command(context: &mut Context) {
492 if let Some(help_command) = context.help_command.as_ref().cloned() {
493 context.root.add_command(help_command);
494 }
495}
496
497#[inline]
498fn add_command_builtin_version_option(context: &mut Context) {
499 if context.root.get_subcommands().count() > 0 {
500 if let Some(version_option) = context.version_option.as_ref().cloned() {
501 let command = &mut context.root;
502 add_option_recursive(command, version_option);
503 }
504 }
505}
506
507#[inline]
508fn add_command_builtin_version_command(context: &mut Context) {
509 if let Some(version_command) = context.version_command.as_ref().cloned() {
510 context.root.add_command(version_command);
511 }
512}
513
514fn add_option_recursive(command: &mut Command, option: CommandOption) {
515 for subcommand in command.get_subcommands_mut() {
516 add_option_recursive(subcommand, option.clone());
517 }
518
519 command.add_option(option);
520}
521
522pub(crate) fn is_help_command(context: &Context, name: &str) -> bool {
524 if let Some(help_command) = context.help_command.as_ref() {
525 help_command.get_name() == name
526 } else {
527 false
528 }
529}
530
531pub(crate) fn is_help_option(context: &Context, name: &str) -> bool {
533 if let Some(help_option) = context.help_option.as_ref() {
534 help_option.get_name() == name || help_option.has_alias(name)
535 } else {
536 false
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn context_test(){
546 let context = Context::new(Command::root());
547 assert!(context.is_name_prefix("--"));
548 assert!(context.is_alias_prefix("-"));
549 assert!(context.assign_operators().any(|c| *c == '='));
550 assert_eq!(context.delimiter(), ',');
551 }
552
553 #[test]
554 fn context_builder_test(){
555 let context = Context::builder(Command::root())
556 .name_prefix("#")
557 .alias_prefix("##")
558 .assign_operator('>')
559 .delimiter('-')
560 .build();
561
562 assert!(context.is_name_prefix("#"));
563 assert!(context.is_alias_prefix("##"));
564 assert!(context.assign_operators().any(|c| *c == '>'));
565 assert_eq!(context.delimiter(), '-');
566 }
567
568 #[test]
569 #[should_panic(expected="prefixes cannot contains numbers or letters: `1`")]
570 fn invalid_name_prefix_test() {
571 Context::builder(Command::root()).name_prefix("1");
572 }
573
574 #[test]
575 #[should_panic(expected="prefixes cannot contains whitespaces: `\t ab`")]
576 fn invalid_alias_prefix_test() {
577 Context::builder(Command::root()).alias_prefix("\t ab");
578 }
579
580 #[test]
581 #[should_panic(expected="assign chars cannot contains whitespaces: `\t`")]
582 fn invalid_assign_chars_test() {
583 Context::builder(Command::root()).assign_operator('\t');
584 }
585
586 #[test]
587 #[should_panic(expected="delimiters cannot contains whitespaces: `\t`")]
588 fn invalid_delimiter_test() {
589 Context::builder(Command::root()).delimiter('\t');
590 }
591}