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}