fli/command.rs
1// command.rs
2use std::collections::HashMap;
3
4use colored::Colorize;
5
6use crate::display;
7use crate::option_parser::{
8 CommandChain, CommandOptionsParser, CommandOptionsParserBuilder, InputArgsParser, Value,
9 ValueTypes,
10};
11
12use crate::error::{FliError, Result};
13
14/// Context data passed to command callbacks containing parsed arguments and options.
15///
16/// This struct provides a convenient API for accessing parsed command-line data
17/// without dealing with raw argument vectors.
18///
19/// # Examples
20///
21/// ```rust
22/// fn my_command(data: &FliCallbackData) {
23/// let name = data.get_option_value("name")
24/// .and_then(|v| v.as_str())
25/// .unwrap_or("World");
26/// println!("Hello, {}!", name);
27/// }
28/// ```
29#[derive(Debug, Clone)]
30pub struct FliCallbackData {
31 pub command: FliCommand,
32 pub option_parser: CommandOptionsParser,
33 pub arguments: Vec<String>,
34 pub arg_parser: InputArgsParser,
35}
36
37impl FliCallbackData {
38 /// Creates a new callback data context.
39 ///
40 /// # Arguments
41 ///
42 /// * `command` - The command being executed
43 /// * `option_parser` - Parser containing all parsed options
44 /// * `arguments` - Positional arguments passed to the command
45 /// * `arg_parser` - The argument parser with full parse state
46 ///
47 /// # Note
48 ///
49 /// This is typically created internally by the framework. Users rarely need
50 /// to construct this manually.
51 pub fn new(
52 command: FliCommand,
53 option_parser: CommandOptionsParser,
54 arguments: Vec<String>,
55 arg_parser: InputArgsParser,
56 ) -> Self {
57 Self {
58 command,
59 option_parser,
60 arguments,
61 arg_parser,
62 }
63 }
64
65 /// Retrieves the parsed value for a given option name.
66 ///
67 /// Supports multiple lookup formats:
68 /// - With dashes: "-v", "--verbose"
69 /// - Without dashes: "v", "verbose"
70 ///
71 /// # Arguments
72 ///
73 /// * `name` - The option name (with or without dashes)
74 ///
75 /// # Returns
76 ///
77 /// * `Some(&ValueTypes)` - The parsed value if the option was provided
78 /// * `None` - If the option wasn't provided or doesn't exist
79 ///
80 /// # Examples
81 ///
82 /// ```rust
83 /// // All of these work:
84 /// data.get_option_value("name");
85 /// data.get_option_value("-n");
86 /// data.get_option_value("--name");
87 /// ````
88 pub fn get_option_value(&self, name: &str) -> Option<&ValueTypes> {
89 if name.starts_with("-") {
90 return self.option_parser.get_option_expected_value_type(name);
91 }
92
93 // try single-dash prefix
94 let short = format!("-{}", name);
95 if let Some(val) = self.option_parser.get_option_expected_value_type(&short) {
96 return Some(val);
97 }
98
99 // try double-dash prefix
100 let long = format!("--{}", name);
101 if let Some(val) = self.option_parser.get_option_expected_value_type(&long) {
102 return Some(val);
103 }
104
105 // fallback to raw name
106 self.option_parser.get_option_expected_value_type(name)
107 }
108
109 /// Retrieves a positional argument by index.
110 ///
111 /// # Arguments
112 ///
113 /// * `index` - Zero-based index of the argument
114 ///
115 /// # Returns
116 ///
117 /// * `Some(&String)` - The argument at the given index
118 /// * `None` - If index is out of bounds
119 ///
120 /// # Examples
121 ///
122 /// ```rust
123 /// // For command: myapp copy file1.txt file2.txt
124 /// let source = data.get_argument_at(0); // Some("file1.txt")
125 /// let dest = data.get_argument_at(1); // Some("file2.txt")
126 /// ```
127 pub fn get_argument_at(&self, index: usize) -> Option<&String> {
128 self.arguments.get(index)
129 }
130
131 /// Returns all positional arguments as a vector.
132 ///
133 /// # Returns
134 ///
135 /// A reference to the vector of all positional arguments
136 ///
137 /// # Examples
138 ///
139 /// ```rust
140 /// for arg in data.get_arguments() {
141 /// println!("Argument: {}", arg);
142 /// }
143 /// ```
144 pub fn get_arguments(&self) -> &Vec<String> {
145 &self.arguments
146 }
147
148 /// Returns a reference to the command being executed.
149 ///
150 /// # Returns
151 ///
152 /// A reference to the `FliCommand` that matched this execution
153 pub fn get_command(&self) -> &FliCommand {
154 &self.command
155 }
156
157 /// Returns a reference to the argument parser.
158 ///
159 /// Provides access to low-level parsing details if needed.
160 ///
161 /// # Returns
162 ///
163 /// A reference to the `InputArgsParser` used for parsing
164 pub fn get_arg_parser(&self) -> &InputArgsParser {
165 &self.arg_parser
166 }
167}
168
169/// Metadata for options that have custom callbacks.
170///
171/// Preserved options trigger their callback immediately when encountered during
172/// parsing (e.g., --help exits early rather than continuing execution).
173#[derive(Debug, Clone)]
174pub struct PreservedOption {
175 pub long_flag: String,
176 pub short_flag: String,
177 pub value_type: ValueTypes,
178 pub callback: fn(&FliCallbackData),
179 /// If true, invoking this preserved option will prevent the command's main
180 /// callback from running; if false, both the preserved option callback and
181 /// the main callback will run.
182 pub stop_main_callback: bool,
183}
184
185/// Represents a CLI command with options, subcommands, and execution logic.
186///
187/// Commands form a tree structure where each command can have subcommands,
188/// creating a hierarchical CLI interface (like `git commit` or `docker run`).
189///
190/// # Examples
191///
192/// ```rust
193/// let mut cmd = FliCommand::new("serve", "Start the server");
194/// cmd.add_option("port", "Port to bind", "-p", "--port",
195/// ValueTypes::OptionalSingle(Some(Value::Int(8080))));
196/// cmd.set_callback(|data| {
197/// // Server logic here
198/// });
199/// ```
200#[derive(Debug, Clone)]
201pub struct FliCommand {
202 pub name: String,
203 pub description: String,
204 // pub arg_parser: InputArgsParser,
205 pub option_parser_builder: CommandOptionsParserBuilder,
206 pub sub_commands: HashMap<String, FliCommand>,
207 pub callback: Option<fn(&FliCallbackData)>,
208 pub preserved_options: Vec<PreservedOption>,
209 pub preserved_short_flags: HashMap<String, usize>, // map short flag to index in preserved_options
210 pub preserved_long_flags: HashMap<String, usize>, // map long flag to index in preserved_options
211 pub expected_positional_args: usize,
212 pub inheritable_options: Vec<usize>,
213}
214
215impl FliCommand {
216 /// Creates a new command.
217 ///
218 /// # Arguments
219 ///
220 /// * `name` - The command name (used to invoke it)
221 /// * `description` - Help text describing the command
222 ///
223 /// # Returns
224 ///
225 /// A new `FliCommand` with auto-generated help flag
226 pub fn new(name: &str, description: &str) -> Self {
227 let mut x = Self {
228 name: name.to_owned(),
229 description: description.to_owned(),
230 // arg_parser: InputArgsParser::new(name.to_string(), Vec::new()),
231 sub_commands: HashMap::new(),
232 callback: None,
233 option_parser_builder: CommandOptionsParserBuilder::new(),
234 preserved_options: Vec::new(),
235 preserved_short_flags: HashMap::new(),
236 preserved_long_flags: HashMap::new(),
237 expected_positional_args: 0,
238 inheritable_options: Vec::new(),
239 };
240 x.setup_help_flag();
241 x
242 }
243
244 /// Creates a new command with a pre-configured option parser builder.
245 ///
246 /// This method is useful for creating subcommands that inherit options from their
247 /// parent command. It initializes the command with options from the provided builder,
248 /// typically obtained via `parser.inheritable_options_builder()`.
249 ///
250 /// # Arguments
251 ///
252 /// * `name` - The command name (used to invoke it)
253 /// * `description` - Help text describing the command
254 /// * `parser_builder` - A pre-configured `CommandOptionsParserBuilder` with inherited options
255 ///
256 /// # Returns
257 ///
258 /// A new `FliCommand` with auto-generated help flag and inherited options
259 ///
260 /// # Examples
261 ///
262 /// ```rust
263 /// use fli::command::FliCommand;
264 /// use fli::option_parser::{CommandOptionsParser, ValueTypes};
265 ///
266 /// // Parent command with options
267 /// let mut parent_parser = CommandOptionsParser::new();
268 /// parent_parser.add_option("verbose", "Enable verbose", "-v", "--verbose", ValueTypes::None);
269 /// parent_parser.mark_inheritable("-v").unwrap();
270 ///
271 /// // Create subcommand that inherits the -v option
272 /// let builder = parent_parser.inheritable_options_builder();
273 /// let subcmd = FliCommand::with_parser("child", "Child command", builder);
274 /// // The child command now has -v/--verbose option automatically
275 /// ```
276 ///
277 /// # Notes
278 ///
279 /// - The help flag is automatically added
280 /// - Options from the builder are cloned into the new command
281 /// - This is typically called internally when creating subcommands
282 pub fn with_parser(
283 name: &str,
284 description: &str,
285 parser_builder: CommandOptionsParserBuilder,
286 ) -> Self {
287 let mut x = Self {
288 name: name.to_owned(),
289 description: description.to_owned(),
290 sub_commands: HashMap::new(),
291 callback: None,
292 option_parser_builder: parser_builder,
293 preserved_options: Vec::new(),
294 preserved_short_flags: HashMap::new(),
295 preserved_long_flags: HashMap::new(),
296 expected_positional_args: 0,
297 inheritable_options: Vec::new(),
298 };
299 x.setup_help_flag();
300 x
301 }
302
303 /// Sets the number of expected positional arguments for this command.
304 ///
305 /// Returns &mut Self for chaining.
306 pub fn set_expected_positional_args(&mut self, count: usize) -> &mut Self {
307 self.expected_positional_args = count;
308 self
309 }
310
311 /// Returns the number of expected positional arguments for this command.
312 pub fn get_expected_positional_args(&self) -> usize {
313 self.expected_positional_args
314 }
315
316 /// Adds a standard --help/-h flag to the command.
317 ///
318 /// This is called automatically in `new()`. The help flag displays:
319 /// - Command description
320 /// - Available options
321 /// - Subcommands
322 ///
323 /// # Note
324 ///
325 /// This is a preserved option, meaning it executes immediately and
326 /// exits the program.
327 pub fn setup_help_flag(&mut self) {
328 self.add_option_with_callback(
329 "help",
330 "Display help information",
331 "-h",
332 "--help",
333 ValueTypes::OptionalSingle(Some(Value::Bool(false))),
334 true,
335 |data| {
336 let cmd = data.get_command();
337
338 // Command header
339 display::print_section(&format!("Command: {}", cmd.get_name()));
340 display::print_info(cmd.get_description());
341
342 // Usage patterns
343 display::print_section("Usage");
344 let usage_patterns = Self::build_usage_patterns(cmd);
345 for pattern in usage_patterns {
346 display::print_info(&format!(" {}", pattern));
347 }
348
349 // Options table
350 Self::print_options_table(&data.option_parser);
351
352 // Subcommands
353 Self::print_subcommands_table(cmd);
354
355 // Arguments section
356 // Self::print_arguments_section(cmd);
357
358 std::process::exit(0);
359 },
360 );
361 }
362
363 /// Build usage pattern strings for the command
364 pub fn build_usage_patterns(cmd: &FliCommand) -> Vec<String> {
365 let name = cmd.get_name();
366 let mut patterns = Vec::new();
367
368 // Basic pattern with options
369 let mut basic = format!("{}", name);
370
371 if cmd.has_sub_commands() {
372 basic.push_str("[SUBCOMMANDS]");
373 }
374
375 let expected = cmd.get_expected_positional_args();
376 let args_pattern: String = if expected > 0 {
377 // keep a snapshot of the current prefix (may include [SUBCOMMANDS])
378 let prefix = basic.clone();
379
380 // grouped form: single placeholder showing count
381 // basic.push_str(&format!(" [ARGUMENTS({})]", expected));
382
383 // alternative form: repeat the argument placeholder `expected` times
384 let repeated = std::iter::repeat(" [ARGUMENT]")
385 .take(expected)
386 .collect::<Vec<_>>()
387 .join("");
388 let repeated_pattern = format!("{}", repeated);
389 repeated_pattern
390 // add the repeated-arguments pattern (OPTIONS is appended later for the main pattern,
391 // so include it here to be consistent)
392 // patterns.push(format!("{}", repeated_pattern));
393 } else {
394 String::new()
395 };
396
397 // If command can accept positional arguments
398 basic.push_str(&args_pattern);
399
400 basic.push_str(" [OPTIONS]");
401
402 patterns.push(basic);
403
404 // Pattern with double-dash separator
405 let with_separator = format!(
406 "[SUBCOMMANDS] [OPTIONS] {}",
407 if expected > 0 {
408 format!("-- {}", args_pattern)
409 } else {
410 String::new()
411 }
412 );
413 patterns.push(with_separator);
414
415 patterns
416 }
417
418 /// Print the options table
419 pub fn print_options_table(parser: &CommandOptionsParser) {
420 let options = parser.get_options();
421
422 if options.is_empty() {
423 return;
424 }
425
426 display::print_section("Options");
427
428 let headers = vec!["Flag", "Long Form", "Value Type", "Description"];
429 let rows: Vec<Vec<&str>> = options
430 .iter()
431 .map(|opt| {
432 let value_type = match &opt.value {
433 ValueTypes::OptionalSingle(Some(Value::Bool(_))) => "flag",
434 ValueTypes::RequiredSingle(_) => "single (required)",
435 ValueTypes::OptionalSingle(_) => "single (optional)",
436 ValueTypes::RequiredMultiple(_, Some(n)) => {
437 // Store in a thread-local or return a String
438 return vec![
439 opt.short_flag.as_str(),
440 opt.long_flag.as_str(),
441 Box::leak(format!("multiple (exactly {})", n).into_boxed_str()),
442 opt.description.as_str(),
443 ];
444 }
445 ValueTypes::RequiredMultiple(_, None) => "multiple (1+)",
446 ValueTypes::OptionalMultiple(_, Some(n)) => {
447 return vec![
448 opt.short_flag.as_str(),
449 opt.long_flag.as_str(),
450 Box::leak(format!("multiple (max {})", n).into_boxed_str()),
451 opt.description.as_str(),
452 ];
453 }
454 ValueTypes::OptionalMultiple(_, None) => "multiple (0+)",
455 };
456
457 vec![
458 opt.short_flag.as_str(),
459 opt.long_flag.as_str(),
460 value_type,
461 opt.description.as_str(),
462 ]
463 })
464 .collect();
465
466 display::print_table(&headers, &rows, None);
467 }
468
469 /// Print the subcommands table
470 pub fn print_subcommands_table(cmd: &FliCommand) {
471 if !cmd.has_sub_commands() {
472 return;
473 }
474
475 display::print_section("Subcommands");
476
477 let headers = vec!["Command", "Description"];
478 let rows: Vec<Vec<&str>> = cmd
479 .get_sub_commands()
480 .iter()
481 .map(|(name, sub_cmd)| vec![name.as_str(), sub_cmd.get_description().as_str()])
482 .collect();
483
484 display::print_table(&headers, &rows, None);
485
486 display::print_info("Run '<command> --help' for more information on a subcommand");
487 }
488
489 /// Print arguments section explanation
490 pub fn print_arguments_section(cmd: &FliCommand) {
491 display::print_section("Arguments");
492
493 let info = vec![
494 ("Positional", "Arguments passed after all options"),
495 (
496 "After --",
497 "All arguments after '--' separator are treated as positional",
498 ),
499 ];
500
501 display::print_key_value(&info);
502
503 display::print_divider(60, '─', Some(colored::Color::BrightBlack));
504
505 display::print_info("Examples:");
506 display::print_info(&format!(" {} file1.txt file2.txt -v", cmd.get_name()));
507 display::print_info(&format!(" {} -v -- file1.txt file2.txt", cmd.get_name()));
508 }
509
510 /// Sets the callback function for this command.
511 ///
512 /// The callback is invoked when this command is matched during parsing.
513 ///
514 /// # Arguments
515 ///
516 /// * `callback` - Function that receives `FliCallbackData` with parsed values
517 pub fn set_callback(&mut self, callback: fn(&FliCallbackData)) {
518 self.callback = Some(callback);
519 }
520
521 /// Returns the command name
522 pub fn get_name(&self) -> &String {
523 &self.name
524 }
525
526 /// Returns the command description.
527 pub fn get_description(&self) -> &String {
528 &self.description
529 }
530
531 /// Returns a reference to the option parser builder.
532 ///
533 /// Useful for inspecting configured options before parsing.
534 pub fn get_option_parser_builder(&self) -> &CommandOptionsParserBuilder {
535 &self.option_parser_builder
536 }
537
538 /// Returns a mutable reference to the built option parser.
539 ///
540 /// This builds the parser if it hasn't been built yet.
541 pub fn get_option_parser(&mut self) -> &mut CommandOptionsParser {
542 self.option_parser_builder.build()
543 }
544
545 /// Returns all subcommands as a HashMap.
546 pub fn get_sub_commands(&self) -> &HashMap<String, FliCommand> {
547 &self.sub_commands
548 }
549
550 /// Checks if this command has any subcommands
551 pub fn has_sub_commands(&self) -> bool {
552 !self.sub_commands.is_empty()
553 }
554
555 /// Retrieves a specific subcommand by name.
556 ///
557 /// # Arguments
558 ///
559 /// * `name` - The subcommand name
560 ///
561 /// # Returns
562 ///
563 /// * `Some(&FliCommand)` - If the subcommand exists
564 /// * `None` - If no subcommand with that name exists
565 pub fn get_sub_command(&self, name: &str) -> Option<&FliCommand> {
566 self.sub_commands.get(name)
567 }
568
569 /// Retrieves a mutable reference to a specific subcommand.
570 ///
571 /// # Arguments
572 ///
573 /// * `name` - The subcommand name
574 ///
575 /// # Returns
576 ///
577 /// * `Some(&mut FliCommand)` - If the subcommand exists
578 /// * `None` - If no subcommand with that name exists
579 pub fn get_sub_command_mut(&mut self, name: &str) -> Option<&mut FliCommand> {
580 self.sub_commands.get_mut(name)
581 }
582
583 /// Checks if a subcommand with the given name exists.
584 pub fn has_sub_command(&self, name: &str) -> bool {
585 self.sub_commands.contains_key(name)
586 }
587
588 /// Adds an option to this command.
589 ///
590 /// # Arguments
591 ///
592 /// * `name` - Internal identifier
593 /// * `description` - Help text
594 /// * `short_flag` - Short form (e.g., "-p")
595 /// * `long_flag` - Long form (e.g., "--port")
596 /// * `value` - Type and default value
597 ///
598 /// # Returns
599 ///
600 /// `&mut self` for method chaining
601 pub fn add_option(
602 &mut self,
603 name: &str,
604 description: &str,
605 short_flag: &str,
606 long_flag: &str,
607 value: ValueTypes,
608 ) -> &mut Self {
609 self.option_parser_builder
610 .add_option(name, description, short_flag, long_flag, value);
611 self
612 }
613
614 /// Adds an option with a custom callback.
615 ///
616 /// The callback executes immediately when this option is encountered,
617 /// useful for flags like --help or --version that should exit early.
618 ///
619 /// # Arguments
620 ///
621 /// * `name` - Internal identifier
622 /// * `description` - Help text
623 /// * `short_flag` - Short form
624 /// * `long_flag` - Long form
625 /// * `value` - Type and default
626 /// * `callback` - Function to execute when option is found
627 ///
628 /// # Returns
629 ///
630 /// `&mut self` for method chaining
631 pub fn add_option_with_callback(
632 &mut self,
633 name: &str,
634 description: &str,
635 short_flag: &str,
636 long_flag: &str,
637 value: ValueTypes,
638 stop_main_callback: bool,
639 callback: fn(&FliCallbackData),
640 ) -> &mut Self {
641 // register option with the normal option parser builder (clone value for the builder)
642 self.option_parser_builder.add_option(
643 name,
644 description,
645 short_flag,
646 long_flag,
647 value.clone(),
648 );
649
650 // create preserved option that will trigger the provided callback when encountered
651 let preserved = PreservedOption {
652 long_flag: long_flag.to_string(),
653 short_flag: short_flag.to_string(),
654 value_type: value,
655 callback,
656 stop_main_callback,
657 };
658
659 // record index and maps for quick lookup
660 let idx = self.preserved_options.len();
661 if !preserved.short_flag.is_empty() {
662 self.preserved_short_flags
663 .insert(preserved.short_flag.clone(), idx);
664 }
665 if !preserved.long_flag.is_empty() {
666 self.preserved_long_flags
667 .insert(preserved.long_flag.clone(), idx);
668 }
669
670 self.preserved_options.push(preserved);
671 self
672 }
673
674 /// Retrieves a preserved option by flag name.
675 ///
676 /// Supports lookup with or without dashes.
677 ///
678 /// # Arguments
679 ///
680 /// * `name` - The flag (e.g., "-h", "--help", "help")
681 ///
682 /// # Returns
683 ///
684 /// * `Some(&PreservedOption)` - If found
685 /// * `None` - If not found
686 pub fn get_preserved_option(&self, name: &str) -> Option<&PreservedOption> {
687 // try exact as provided (direct lookups on self to ensure correct lifetimes)
688 if let Some(idx) = self.preserved_short_flags.get(name) {
689 return self.preserved_options.get(*idx);
690 }
691 if let Some(idx) = self.preserved_long_flags.get(name) {
692 return self.preserved_options.get(*idx);
693 }
694
695 // normalize by trimming existing dashes and try common variants
696 let trimmed = name.trim_start_matches('-');
697 let variants = [
698 format!("-{}", trimmed),
699 format!("--{}", trimmed),
700 trimmed.to_string(),
701 ];
702
703 for v in &variants {
704 if let Some(idx) = self.preserved_short_flags.get(v.as_str()) {
705 return self.preserved_options.get(*idx);
706 }
707 if let Some(idx) = self.preserved_long_flags.get(v.as_str()) {
708 return self.preserved_options.get(*idx);
709 }
710 }
711
712 None
713 }
714
715 /// Checks if a preserved option exists.
716 pub fn has_preserved_option(&self, name: &str) -> bool {
717 self.get_preserved_option(name).is_some()
718 }
719
720 /// Creates and adds a new subcommand, returning a mutable reference.
721 ///
722 /// This method creates a subcommand that automatically inherits options marked
723 /// as inheritable from this parent command. Inherited options are cloned from
724 /// the parent's option parser, eliminating the need to redefine common options.
725 ///
726 /// # Arguments
727 ///
728 /// * `name` - Subcommand name
729 /// * `description` - Subcommand description
730 ///
731 /// # Returns
732 ///
733 /// Mutable reference to the newly created subcommand for chaining
734 ///
735 /// # Examples
736 ///
737 /// ```rust
738 /// use fli::command::FliCommand;
739 /// use fli::option_parser::ValueTypes;
740 ///
741 /// let mut cmd = FliCommand::new("app", "Main application");
742 /// cmd.add_option("verbose", "Enable verbose", "-v", "--verbose", ValueTypes::None);
743 /// cmd.parser_mut().mark_inheritable("-v").unwrap();
744 ///
745 /// // Subcommand automatically inherits -v/--verbose
746 /// cmd.subcommand("start", "Start service")
747 /// .add_option("daemon", "Run as daemon", "-d", "--daemon", ValueTypes::None);
748 /// ```
749 ///
750 /// # Notes
751 ///
752 /// - Inheritable options are automatically cloned to subcommands
753 /// - Each subcommand gets its own copy of inherited options
754 /// - Mark options as inheritable using `parser_mut().mark_inheritable()`
755 pub fn subcommand(&mut self, name: &str, description: &str) -> &mut FliCommand {
756 let inherited_builder = self.get_option_parser().inheritable_options_builder();
757 let command = FliCommand::with_parser(name, description, inherited_builder);
758 self.add_sub_command(command);
759 self.sub_commands.get_mut(name).unwrap()
760 }
761
762 /// Adds a pre-configured subcommand.
763 ///
764 /// # Arguments
765 ///
766 /// * `command` - A fully configured `FliCommand`
767 pub fn add_sub_command(&mut self, command: FliCommand) {
768 self.sub_commands
769 .insert(command.get_name().to_owned(), command);
770 }
771
772 /// Returns the callback function if one is set.
773 pub fn get_callback(&self) -> Option<fn(&FliCallbackData)> {
774 self.callback
775 }
776
777 /// Executes this command with the given argument parser.
778 ///
779 /// This method:
780 /// 1. Parses arguments using the provided parser
781 /// 2. Handles subcommand delegation recursively
782 /// 3. Executes preserved option callbacks (like --help)
783 /// 4. Executes the command's main callback
784 ///
785 /// # Arguments
786 ///
787 /// * `arg_parser` - Parser initialized with command-line arguments
788 ///
789 /// # Returns
790 ///
791 /// * `Ok(())` - If execution succeeded
792 /// * `Err(String)` - If parsing failed or unknown command/option encountered
793 ///
794 /// # Errors
795 ///
796 /// Returns an error if:
797 /// - Required option values are missing
798 /// - Unknown subcommands are specified
799 /// - Option parsing fails
800 pub fn run(&mut self, mut arg_parser: InputArgsParser) -> Result<()> {
801 // Prepare the parser with this command's options
802 arg_parser.prepare(self)?;
803
804 display::debug_print(
805 "App",
806 &format!("Parsed arguments: {:?}", arg_parser.get_command_chain()),
807 );
808
809 let chain = arg_parser.get_parsed_commands_chain().clone();
810
811 if chain.is_empty() {
812 return Err(FliError::InvalidUsage(
813 "No command or arguments provided".to_string(),
814 ));
815 }
816
817 let mut chain_iter = chain.iter();
818
819 // Collect arguments and check for subcommands
820 let mut arguments = Vec::new();
821 let mut next_subcommand: Option<(&String, Vec<CommandChain>, usize)> = None;
822 let mut preserved_option: Option<&String> = None;
823
824 for (idx, item) in chain.iter().enumerate() {
825 match item {
826 CommandChain::SubCommand(sub_name) => {
827 // Found a subcommand, collect remaining chain items
828 let remaining: Vec<CommandChain> = chain[idx + 1..].to_vec();
829 next_subcommand = Some((sub_name, remaining, idx));
830 break;
831 }
832 CommandChain::Argument(arg) => {
833 arguments.push(arg.clone());
834 }
835 CommandChain::Option(_, _) => {
836 // Options are already processed, just skip
837 }
838 CommandChain::IsPreservedOption(s) => {
839 // Preserved options are already processed, just skip
840 preserved_option = Some(s);
841 }
842 }
843 }
844
845 // If there's a subcommand, handle it recursively
846 if let Some((sub_name, remaining_chain, idx)) = next_subcommand {
847 if let Some(sub_command) = self.get_sub_command_mut(sub_name) {
848 // Create a new parser for the subcommand
849 let mut sub_parser = arg_parser.with_remaining_chain(idx);
850 sub_parser.command_chain = remaining_chain;
851
852 return sub_command.run(sub_parser);
853 } else {
854 let available: Vec<String> = self.get_sub_commands().keys().cloned().collect();
855 return Err(FliError::UnknownCommand(sub_name.clone(), available));
856 }
857 }
858
859 let mut callback: Option<fn(&FliCallbackData)> = None;
860 let callback_data = FliCallbackData::new(
861 self.clone(),
862 self.get_option_parser().clone(),
863 arguments,
864 arg_parser,
865 );
866
867 // No subcommand, execute this command's callback
868 if let Some(_callback) = self.get_callback() {
869 callback = Some(_callback);
870 }
871
872 if let Some(preserved_name) = preserved_option {
873 if let Some(preserved) = self.get_preserved_option(preserved_name) {
874 // Execute the preserved option's callback immediately
875 (preserved.callback)(&callback_data);
876
877 // If this preserved option should stop the main callback, return early.
878 // Otherwise allow the main callback to run (if set).
879 if preserved.stop_main_callback {
880 return Ok(());
881 }
882 }
883 }
884
885 if let Some(cb) = callback {
886 cb(&callback_data);
887 }
888
889 Ok(())
890 }
891}