flag_rs/
command.rs

1//! Command execution and management
2//!
3//! This module provides the core [`Command`] struct and [`CommandBuilder`] for creating
4//! CLI applications with subcommands, flags, and dynamic completions.
5
6use crate::completion::{CompletionFunc, CompletionResult};
7use crate::completion_format::CompletionFormat;
8use crate::context::Context;
9use crate::error::{Error, Result};
10use crate::flag::{Flag, FlagConstraint, FlagType, FlagValue};
11use crate::suggestion::{DEFAULT_SUGGESTION_DISTANCE, find_suggestions};
12use crate::terminal::{format_help_entry, get_terminal_width, wrap_text_to_terminal};
13use crate::validator::ArgValidator;
14use std::collections::{HashMap, HashSet};
15
16/// Type alias for the function that executes when a command runs
17pub type RunFunc = Box<dyn Fn(&mut Context) -> Result<()> + Send + Sync>;
18
19/// Type alias for lifecycle hook functions
20pub type HookFunc = Box<dyn Fn(&mut Context) -> Result<()> + Send + Sync>;
21
22/// Represents a command in the CLI application
23///
24/// Commands can have:
25/// - Subcommands for nested command structures
26/// - Flags that modify behavior
27/// - A run function that executes the command logic
28/// - Dynamic completion functions for arguments and flags
29/// - Help text and aliases
30///
31/// # Examples
32///
33/// ```rust
34/// use flag_rs::{Command, CommandBuilder, Context};
35///
36/// // Using the builder pattern (recommended)
37/// let cmd = CommandBuilder::new("serve")
38///     .short("Start the web server")
39///     .run(|ctx| {
40///         println!("Server starting...");
41///         Ok(())
42///     })
43///     .build();
44///
45/// // Direct construction
46/// let mut cmd = Command::new("serve");
47/// ```
48pub struct Command {
49    name: String,
50    aliases: Vec<String>,
51    short: String,
52    long: String,
53    examples: Vec<String>,
54    group_id: Option<String>,
55    subcommands: HashMap<String, Self>,
56    flags: HashMap<String, Flag>,
57    run: Option<RunFunc>,
58    parent: Option<*mut Self>,
59    arg_completions: Option<CompletionFunc>,
60    flag_completions: HashMap<String, CompletionFunc>,
61    arg_validator: Option<ArgValidator>,
62    suggestions_enabled: bool,
63    suggestion_distance: usize,
64    // Lifecycle hooks
65    persistent_pre_run: Option<HookFunc>,
66    pre_run: Option<HookFunc>,
67    post_run: Option<HookFunc>,
68    persistent_post_run: Option<HookFunc>,
69}
70
71unsafe impl Send for Command {}
72unsafe impl Sync for Command {}
73/// Collects all available flags with their descriptions for completion
74fn collect_all_flags_with_descriptions(
75    current: &Command,
76    result: &mut CompletionResult,
77    prefix: &str,
78) {
79    // Add current command's flags
80    for (flag_name, flag) in &current.flags {
81        if flag_name.starts_with(prefix) {
82            let formatted_flag = format!("--{flag_name}");
83            result.values.push(formatted_flag);
84            result.descriptions.push(flag.usage.clone());
85        }
86    }
87
88    // Add parent flags
89    if let Some(parent) = current.parent {
90        unsafe {
91            collect_all_flags_with_descriptions(&*parent, result, prefix);
92        }
93    }
94}
95
96impl Command {
97    /// Creates a new command with the given name
98    ///
99    /// # Examples
100    ///
101    /// ```rust
102    /// use flag_rs::Command;
103    ///
104    /// let cmd = Command::new("myapp");
105    /// ```
106    pub fn new(name: impl Into<String>) -> Self {
107        Self {
108            name: name.into(),
109            aliases: Vec::new(),
110            short: String::new(),
111            long: String::new(),
112            examples: Vec::new(),
113            group_id: None,
114            subcommands: HashMap::new(),
115            flags: HashMap::new(),
116            run: None,
117            parent: None,
118            arg_completions: None,
119            flag_completions: HashMap::new(),
120            arg_validator: None,
121            suggestions_enabled: true,
122            suggestion_distance: DEFAULT_SUGGESTION_DISTANCE,
123            persistent_pre_run: None,
124            pre_run: None,
125            post_run: None,
126            persistent_post_run: None,
127        }
128    }
129
130    /// Returns the command name
131    pub fn name(&self) -> &str {
132        &self.name
133    }
134
135    /// Returns the short description
136    pub fn short(&self) -> &str {
137        &self.short
138    }
139
140    /// Returns the long description
141    pub fn long(&self) -> &str {
142        &self.long
143    }
144
145    /// Returns a reference to all subcommands
146    pub fn subcommands(&self) -> &HashMap<String, Self> {
147        &self.subcommands
148    }
149
150    /// Returns a reference to all flags
151    pub fn flags(&self) -> &HashMap<String, Flag> {
152        &self.flags
153    }
154
155    /// Finds a subcommand by name or alias
156    ///
157    /// # Examples
158    ///
159    /// ```rust
160    /// # use flag_rs::{Command, CommandBuilder};
161    /// let mut root = Command::new("app");
162    /// let sub = CommandBuilder::new("server")
163    ///     .aliases(vec!["serve", "s"])
164    ///     .build();
165    /// root.add_command(sub);
166    ///
167    /// assert!(root.find_subcommand("server").is_some());
168    /// assert!(root.find_subcommand("serve").is_some());
169    /// assert!(root.find_subcommand("s").is_some());
170    /// ```
171    pub fn find_subcommand(&self, name: &str) -> Option<&Self> {
172        self.subcommands.get(name).or_else(|| {
173            self.subcommands
174                .values()
175                .find(|cmd| cmd.aliases.contains(&name.to_string()))
176        })
177    }
178
179    /// Finds a mutable reference to a subcommand by name or alias
180    pub fn find_subcommand_mut(&mut self, name: &str) -> Option<&mut Self> {
181        let name_string = name.to_string();
182        if self.subcommands.contains_key(name) {
183            self.subcommands.get_mut(name)
184        } else {
185            self.subcommands
186                .values_mut()
187                .find(|cmd| cmd.aliases.contains(&name_string))
188        }
189    }
190
191    /// Adds a subcommand to this command
192    ///
193    /// # Examples
194    ///
195    /// ```rust
196    /// use flag_rs::{Command, CommandBuilder};
197    ///
198    /// let mut root = Command::new("myapp");
199    /// let serve = CommandBuilder::new("serve")
200    ///     .short("Start the server")
201    ///     .build();
202    ///
203    /// root.add_command(serve);
204    /// ```
205    pub fn add_command(&mut self, mut cmd: Self) {
206        cmd.parent = Some(self as *mut Self);
207        self.subcommands.insert(cmd.name.clone(), cmd);
208    }
209
210    /// Executes the command with the given arguments
211    ///
212    /// This is the main entry point for running your CLI application.
213    /// It handles:
214    /// - Shell completion requests
215    /// - Flag parsing
216    /// - Subcommand routing
217    /// - Execution of the appropriate run function
218    ///
219    /// # Examples
220    ///
221    /// ```rust
222    /// use flag_rs::CommandBuilder;
223    ///
224    /// let app = CommandBuilder::new("myapp")
225    ///     .run(|ctx| {
226    ///         println!("Hello from myapp!");
227    ///         Ok(())
228    ///     })
229    ///     .build();
230    ///
231    /// // In main():
232    /// // let args: Vec<String> = std::env::args().skip(1).collect();
233    /// // if let Err(e) = app.execute(args) {
234    /// //     eprintln!("Error: {}", e);
235    /// //     std::process::exit(1);
236    /// // }
237    /// ```
238    pub fn execute(&self, args: Vec<String>) -> Result<()> {
239        // Check if we're in completion mode
240        if let Ok(_shell) = std::env::var(format!("{}_COMPLETE", self.name.to_uppercase())) {
241            // Disable colors during completion to avoid terminal rendering issues
242            unsafe { std::env::set_var("NO_COLOR", "1") };
243
244            match self.handle_completion_request(&args) {
245                Ok(suggestions) => {
246                    for suggestion in suggestions {
247                        println!("{suggestion}");
248                    }
249                    return Ok(());
250                }
251                Err(e) => {
252                    // Don't write to stderr during completion - it can mess up the terminal
253                    return Err(e);
254                }
255            }
256        }
257
258        let mut ctx = Context::new(args);
259        self.execute_with_context(&mut ctx)
260    }
261
262    /// Executes the command with an existing context
263    ///
264    /// This method is useful when you need to provide pre-configured context
265    /// or when implementing custom command routing.
266    pub fn execute_with_context(&self, ctx: &mut Context) -> Result<()> {
267        // Call the internal method with an empty hook chain
268        self.execute_with_context_and_hooks(ctx, &mut Vec::new())
269    }
270
271    /// Internal method that executes the command while collecting parent hooks
272    fn execute_with_context_and_hooks<'a>(
273        &'a self,
274        ctx: &mut Context,
275        parent_hooks: &mut Vec<(&'a Option<HookFunc>, &'a Option<HookFunc>)>,
276    ) -> Result<()> {
277        let args = ctx.args().to_vec();
278
279        // Parse flags first, before checking for empty args
280        let (flags, remaining_args) = self.parse_flags(&args)?;
281
282        *ctx.args_mut() = remaining_args;
283
284        // Check if we have a subcommand first
285        if let Some(subcommand_name) = ctx.args().first() {
286            if let Some(subcommand) = self.find_subcommand(subcommand_name) {
287                // If help flag is present, show help for the subcommand
288                if flags.contains_key("help") {
289                    subcommand.print_help();
290                    return Ok(());
291                }
292
293                // Validate flags before setting them
294                self.validate_flags(&flags)?;
295
296                // Set flags and execute subcommand
297                for (name, value) in flags {
298                    ctx.set_flag(name, value);
299                }
300
301                // Add our persistent hooks to the chain for subcommands
302                parent_hooks.push((&self.persistent_pre_run, &self.persistent_post_run));
303
304                ctx.args_mut().remove(0);
305                return subcommand.execute_with_context_and_hooks(ctx, parent_hooks);
306            }
307        }
308
309        // No subcommand found, check for help at this level
310        if flags.contains_key("help") {
311            self.print_help();
312            return Ok(());
313        }
314
315        // Validate flags before setting them
316        self.validate_flags(&flags)?;
317
318        // Set flags
319        for (name, value) in flags {
320            ctx.set_flag(name, value);
321        }
322
323        // No subcommand found, try to run this command's function
324        if let Some(ref run) = self.run {
325            // Validate arguments before running
326            if let Some(ref validator) = self.arg_validator {
327                validator.validate(ctx.args())?;
328            }
329            self.execute_with_parent_hooks(ctx, run, parent_hooks)
330        } else if ctx.args().is_empty() {
331            // No args and no run function - show help
332            Err(Error::SubcommandRequired(self.name.clone()))
333        } else {
334            let unknown_command = ctx.args().first().unwrap_or(&String::new()).clone();
335            let suggestions = if self.suggestions_enabled {
336                self.find_command_suggestions(&unknown_command)
337            } else {
338                Vec::new()
339            };
340
341            Err(Error::CommandNotFound {
342                command: unknown_command,
343                suggestions,
344            })
345        }
346    }
347
348    fn parse_flags(&self, args: &[String]) -> Result<(HashMap<String, String>, Vec<String>)> {
349        let mut flags = HashMap::new();
350        let mut remaining = Vec::new();
351        let mut i = 0;
352
353        while i < args.len() {
354            let arg = &args[i];
355
356            if arg == "--" {
357                remaining.extend_from_slice(&args[i + 1..]);
358                break;
359            } else if arg.starts_with("--") {
360                let flag_name = arg.trim_start_matches("--");
361
362                // Special handling for help
363                if flag_name == "help" {
364                    flags.insert("help".to_string(), "true".to_string());
365                } else if let Some((name, value)) = flag_name.split_once('=') {
366                    // Validate the flag value
367                    if let Some(flag) = self.find_flag(name) {
368                        flag.parse_value(value)?;
369                    }
370                    flags.insert(name.to_string(), value.to_string());
371                } else if let Some(flag) = self.find_flag(flag_name) {
372                    if i + 1 < args.len() && !args[i + 1].starts_with('-') {
373                        let value = &args[i + 1];
374                        // Validate the flag value
375                        flag.parse_value(value)?;
376                        flags.insert(flag_name.to_string(), value.clone());
377                        i += 1;
378                    } else {
379                        flags.insert(flag_name.to_string(), "true".to_string());
380                    }
381                } else {
382                    // Unknown flag - might belong to a subcommand
383                    remaining.push(arg.clone());
384                }
385            } else if arg.starts_with('-') && arg.len() > 1 {
386                let short_flags = arg.trim_start_matches('-');
387                let chars: Vec<char> = short_flags.chars().collect();
388
389                for (idx, ch) in chars.iter().enumerate() {
390                    // Special handling for -h as help
391                    if *ch == 'h' {
392                        flags.insert("help".to_string(), "true".to_string());
393                    } else if let Some(flag) = self.find_flag_by_short(*ch) {
394                        // If this is the last char and the flag takes a value
395                        if idx == chars.len() - 1
396                            && i + 1 < args.len()
397                            && !args[i + 1].starts_with('-')
398                        {
399                            let value = &args[i + 1];
400                            // Validate the flag value
401                            flag.parse_value(value)?;
402                            flags.insert(flag.name.clone(), value.clone());
403                            i += 1;
404                        } else {
405                            flags.insert(flag.name.clone(), "true".to_string());
406                        }
407                    } else {
408                        // Unknown short flag - might belong to a subcommand
409                        remaining.push(format!("-{}", chars[idx..].iter().collect::<String>()));
410                        break;
411                    }
412                }
413            } else {
414                remaining.push(arg.clone());
415            }
416
417            i += 1;
418        }
419
420        Ok((flags, remaining))
421    }
422
423    /// Sets the argument completion function for this command
424    ///
425    /// The completion function is called when the user presses TAB to complete
426    /// command arguments. It receives the current context and the prefix to complete.
427    ///
428    /// # Examples
429    ///
430    /// ```rust
431    /// use flag_rs::{Command, CompletionResult};
432    ///
433    /// let mut cmd = Command::new("get");
434    /// cmd.set_arg_completion(|ctx, prefix| {
435    ///     let items = vec!["users", "posts", "comments"];
436    ///     Ok(CompletionResult::new().extend(
437    ///         items.into_iter()
438    ///             .filter(|i| i.starts_with(prefix))
439    ///             .map(String::from)
440    ///     ))
441    /// });
442    /// ```
443    pub fn set_arg_completion<F>(&mut self, f: F)
444    where
445        F: Fn(&Context, &str) -> Result<CompletionResult> + Send + Sync + 'static,
446    {
447        self.arg_completions = Some(Box::new(f));
448    }
449
450    /// Sets the completion function for a specific flag
451    ///
452    /// This allows dynamic completion of flag values based on runtime state.
453    ///
454    /// # Examples
455    ///
456    /// ```rust
457    /// use flag_rs::{Command, CompletionResult};
458    ///
459    /// let mut cmd = Command::new("deploy");
460    /// cmd.set_flag_completion("environment", |ctx, prefix| {
461    ///     let envs = vec!["dev", "staging", "production"];
462    ///     Ok(CompletionResult::new().extend(
463    ///         envs.into_iter()
464    ///             .filter(|e| e.starts_with(prefix))
465    ///             .map(String::from)
466    ///     ))
467    /// });
468    /// ```
469    pub fn set_flag_completion<F>(&mut self, flag_name: impl Into<String>, f: F)
470    where
471        F: Fn(&Context, &str) -> Result<CompletionResult> + Send + Sync + 'static,
472    {
473        self.flag_completions.insert(flag_name.into(), Box::new(f));
474    }
475
476    /// Gets completion suggestions for the current context
477    ///
478    /// This method is primarily used internally by the shell completion system.
479    pub fn get_completions(
480        &self,
481        ctx: &Context,
482        to_complete: &str,
483        completing_flag: Option<&str>,
484    ) -> Result<CompletionResult> {
485        if let Some(flag_name) = completing_flag {
486            if let Some(completion_func) = self.flag_completions.get(flag_name) {
487                return completion_func(ctx, to_complete);
488            }
489        } else if let Some(ref completion_func) = self.arg_completions {
490            return completion_func(ctx, to_complete);
491        }
492
493        Ok(CompletionResult::new())
494    }
495
496    fn find_flag(&self, name: &str) -> Option<&Flag> {
497        self.flags.get(name).or_else(|| {
498            self.parent
499                .and_then(|parent| unsafe { (*parent).find_flag(name) })
500        })
501    }
502
503    fn find_flag_by_short(&self, short: char) -> Option<&Flag> {
504        self.flags
505            .values()
506            .find(|f| f.short == Some(short))
507            .or_else(|| {
508                self.parent
509                    .and_then(|parent| unsafe { (*parent).find_flag_by_short(short) })
510            })
511    }
512
513    /// Validates all flags including required flags and constraints
514    fn validate_flags(&self, provided_flags: &HashMap<String, String>) -> Result<()> {
515        let provided_flag_names: HashSet<String> = provided_flags.keys().cloned().collect();
516
517        // Check required flags
518        for (flag_name, flag) in &self.flags {
519            if flag.required && !provided_flag_names.contains(flag_name) {
520                return Err(Error::flag_parsing_with_suggestions(
521                    format!("Required flag '--{flag_name}' not provided"),
522                    flag_name.to_string(),
523                    vec![format!("add --{flag_name} <value>")],
524                ));
525            }
526        }
527
528        // TODO: Fix unsafe parent flag validation
529        // Check parent flags if any
530        // if let Some(parent) = self.parent {
531        //     unsafe {
532        //         for (flag_name, flag) in &(*parent).flags {
533        //             if flag.required && !provided_flag_names.contains(flag_name) {
534        //                 return Err(Error::flag_parsing_with_suggestions(
535        //                     format!("Required flag '--{flag_name}' not provided"),
536        //                     flag_name.to_string(),
537        //                     vec![format!("add --{flag_name} <value>")],
538        //                 ));
539        //             }
540        //         }
541        //     }
542        // }
543
544        // Validate constraints for all flags
545        for (flag_name, flag) in &self.flags {
546            flag.validate_constraints(flag_name, &provided_flag_names)?;
547        }
548
549        // TODO: Fix unsafe parent flag constraint validation
550        // The current approach with raw pointers can lead to undefined behavior
551        // when the parent Command is moved or when accessing heap-allocated data
552        // through the pointer (like Vec<FlagConstraint>).
553        //
554        // Validate parent flag constraints
555        // if let Some(parent) = self.parent {
556        //     unsafe {
557        //         for (flag_name, flag) in &(*parent).flags {
558        //             flag.validate_constraints(flag_name, &provided_flag_names)?;
559        //         }
560        //     }
561        // }
562
563        Ok(())
564    }
565
566    /// Executes the command with lifecycle hooks including parent hooks
567    fn execute_with_parent_hooks(
568        &self,
569        ctx: &mut Context,
570        run: &RunFunc,
571        parent_hooks: &[(&Option<HookFunc>, &Option<HookFunc>)],
572    ) -> Result<()> {
573        // Execute parent persistent pre-run hooks (from root to immediate parent)
574        for (pre_hook, _) in parent_hooks {
575            if let Some(hook) = pre_hook {
576                hook(ctx)?;
577            }
578        }
579
580        // Execute own persistent pre-run hook if present
581        if let Some(ref hook) = self.persistent_pre_run {
582            hook(ctx)?;
583        }
584
585        // Execute pre-run hook if present
586        if let Some(ref pre_run) = self.pre_run {
587            pre_run(ctx)?;
588        }
589
590        // Execute the main run function
591        let result = run(ctx);
592
593        // Execute post-run hook if present, but preserve the original error
594        let post_run_result = if let Some(ref post_run) = self.post_run {
595            match result {
596                Ok(()) => post_run(ctx),
597                Err(e) => {
598                    // Try to run post-run even if main failed, but return original error
599                    let _ = post_run(ctx);
600                    Err(e)
601                }
602            }
603        } else {
604            result
605        };
606
607        // Execute own persistent post-run hook if present
608        let persistent_result = if let Some(ref hook) = self.persistent_post_run {
609            let result = hook(ctx);
610            match post_run_result {
611                Ok(()) => result,
612                Err(e) => {
613                    // Try to run persistent post-run even if post-run failed
614                    let _ = result;
615                    Err(e)
616                }
617            }
618        } else {
619            post_run_result
620        };
621
622        // Execute parent persistent post-run hooks (from immediate parent to root)
623        let mut final_result = persistent_result;
624        for (_, post_hook) in parent_hooks.iter().rev() {
625            if let Some(hook) = post_hook {
626                match final_result {
627                    Ok(()) => final_result = hook(ctx),
628                    Err(e) => {
629                        // Try to run parent post-run even if child failed
630                        let _ = hook(ctx);
631                        final_result = Err(e);
632                    }
633                }
634            }
635        }
636
637        final_result
638    }
639
640    /// Prints the help message for this command
641    ///
642    /// The help message includes:
643    /// - Command description
644    /// - Usage information
645    /// - Available subcommands
646    /// - Local and global flags
647    ///
648    /// Help text is automatically colored when outputting to a TTY.
649    #[allow(clippy::cognitive_complexity)]
650    pub fn print_help(&self) {
651        use crate::color;
652
653        // Print description with text wrapping
654        if !self.long.is_empty() {
655            println!("{}", wrap_text_to_terminal(&self.long, None));
656            println!();
657        } else if !self.short.is_empty() {
658            println!("{}", wrap_text_to_terminal(&self.short, None));
659            println!();
660        }
661
662        // Print usage line
663        print!("{}:\n  {}", color::bold("Usage"), self.name);
664        if !self.flags.is_empty() {
665            print!(" {}", color::yellow("[flags]"));
666        }
667        if !self.subcommands.is_empty() {
668            print!(" {}", color::yellow("[command]"));
669        }
670
671        // Show if command requires args
672        if let Some(validator) = &self.arg_validator {
673            match validator {
674                ArgValidator::MinimumArgs(n) if n > &0 => {
675                    print!(" {}", color::yellow("<args>"));
676                }
677                ArgValidator::ExactArgs(n) if n > &0 => {
678                    let arg_str = if n == &1 { "<arg>" } else { "<args>" };
679                    print!(" {}", color::yellow(arg_str));
680                }
681                ArgValidator::RangeArgs(min, _) if min > &0 => {
682                    print!(" {}", color::yellow("<args>"));
683                }
684                _ => {}
685            }
686        }
687        println!("\n");
688
689        // Print available commands
690        if !self.subcommands.is_empty() {
691            let mut commands: Vec<_> = self.subcommands.values().collect();
692            commands.sort_by_key(|cmd| &cmd.name);
693
694            // Group commands by their group_id
695            let mut grouped: std::collections::BTreeMap<Option<String>, Vec<&Self>> =
696                std::collections::BTreeMap::new();
697            for cmd in commands {
698                grouped.entry(cmd.group_id.clone()).or_default().push(cmd);
699            }
700
701            let terminal_width = get_terminal_width();
702            let left_column_width = 24;
703
704            // Print commands without groups first
705            if let Some(ungrouped) = grouped.get(&None) {
706                println!("{}:", color::bold("Available Commands"));
707                for cmd in ungrouped {
708                    // Build command name with aliases
709                    let mut name_with_aliases = color::green(&cmd.name);
710                    if !cmd.aliases.is_empty() {
711                        let aliases = cmd.aliases.join(", ");
712                        name_with_aliases = format!(
713                            "{} {}",
714                            name_with_aliases,
715                            color::dim(&format!("({aliases})"))
716                        );
717                    }
718
719                    let formatted = format_help_entry(
720                        &format!("  {name_with_aliases}"),
721                        &cmd.short,
722                        left_column_width + 2, // account for the "  " prefix
723                        terminal_width,
724                    );
725                    println!("{formatted}");
726                }
727                println!();
728            }
729
730            // Print grouped commands
731            for (group_id, cmds) in grouped {
732                if let Some(group) = group_id {
733                    println!("{}:", color::bold(&group));
734                    for cmd in cmds {
735                        // Build command name with aliases
736                        let mut name_with_aliases = color::green(&cmd.name);
737                        if !cmd.aliases.is_empty() {
738                            let aliases = cmd.aliases.join(", ");
739                            name_with_aliases = format!(
740                                "{} {}",
741                                name_with_aliases,
742                                color::dim(&format!("({aliases})"))
743                            );
744                        }
745
746                        let formatted = format_help_entry(
747                            &format!("  {name_with_aliases}"),
748                            &cmd.short,
749                            left_column_width + 2, // account for the "  " prefix
750                            terminal_width,
751                        );
752                        println!("{formatted}");
753                    }
754                    println!();
755                }
756            }
757        }
758
759        // Print flags
760        if !self.flags.is_empty() || self.parent.is_some() {
761            // Separate required and optional flags
762            let mut required_flags: Vec<_> = self.flags.values().filter(|f| f.required).collect();
763            let mut optional_flags: Vec<_> = self.flags.values().filter(|f| !f.required).collect();
764
765            required_flags.sort_by_key(|f| &f.name);
766            optional_flags.sort_by_key(|f| &f.name);
767
768            // Print required flags first
769            if !required_flags.is_empty() {
770                println!("{} {}:", color::bold("Required Flags"), color::red("*"));
771                for flag in required_flags {
772                    Self::print_flag(flag);
773                }
774                if !optional_flags.is_empty() {
775                    println!();
776                }
777            }
778
779            // Print optional flags
780            if !optional_flags.is_empty() {
781                println!("{}:", color::bold("Flags"));
782                for flag in optional_flags {
783                    Self::print_flag(flag);
784                }
785            }
786        }
787
788        // Print global flags from parent
789        if let Some(parent) = self.parent {
790            unsafe {
791                let parent_flags = &(*parent).flags;
792                if !parent_flags.is_empty() {
793                    println!("\n{}:", color::bold("Global Flags"));
794                    let mut global_flags: Vec<_> = parent_flags.values().collect();
795                    global_flags.sort_by_key(|f| &f.name);
796
797                    for flag in global_flags {
798                        Self::print_flag(flag);
799                    }
800                }
801            }
802        }
803
804        // Print examples if available
805        if !self.examples.is_empty() {
806            println!("{}:", color::bold("Examples"));
807            for example in &self.examples {
808                println!("  {}", color::dim(example));
809            }
810            println!();
811        }
812
813        // Print help about help
814        println!(
815            "Use \"{} {} --help\" for more information about a command.",
816            self.name,
817            color::yellow("[command]")
818        );
819    }
820
821    fn print_flag(flag: &Flag) {
822        use crate::color;
823        use std::fmt::Write;
824
825        let short = flag
826            .short
827            .map_or_else(|| "    ".to_string(), |s| format!("-{s}, "));
828
829        // Build constraint indicators
830        let mut constraint_info = String::new();
831        for constraint in &flag.constraints {
832            match constraint {
833                FlagConstraint::RequiredIf(other) => {
834                    let _ = write!(
835                        &mut constraint_info,
836                        " {}",
837                        color::yellow(&format!("[required if --{other}]"))
838                    );
839                }
840                FlagConstraint::ConflictsWith(others) => {
841                    let conflicts = others.join(", --");
842                    let _ = write!(
843                        &mut constraint_info,
844                        " {}",
845                        color::yellow(&format!("[conflicts with --{conflicts}]"))
846                    );
847                }
848                FlagConstraint::Requires(others) => {
849                    let requires = others.join(", --");
850                    let _ = write!(
851                        &mut constraint_info,
852                        " {}",
853                        color::yellow(&format!("[requires --{requires}]"))
854                    );
855                }
856            }
857        }
858
859        // Handle special formatting for Choice and Range types
860        match &flag.value_type {
861            FlagType::Choice(choices) => {
862                let choices_str = choices.join("|");
863                let default = flag
864                    .default
865                    .as_ref()
866                    .map(|d| match d {
867                        FlagValue::String(s) => format!(" (default \"{s}\")"),
868                        _ => String::new(),
869                    })
870                    .unwrap_or_default();
871
872                let flag_name_formatted = format!("{} {{{}}}", flag.name, choices_str);
873                let left_part = format!(
874                    "      {}--{}",
875                    color::cyan(&short),
876                    color::cyan(&flag_name_formatted)
877                );
878
879                let description =
880                    format!("{}{}{}", flag.usage, color::dim(&default), constraint_info);
881                let terminal_width = get_terminal_width();
882                let left_column_width = 30;
883
884                let formatted =
885                    format_help_entry(&left_part, &description, left_column_width, terminal_width);
886                println!("{formatted}");
887                return;
888            }
889            FlagType::Range(min, max) => {
890                let default = flag
891                    .default
892                    .as_ref()
893                    .map(|d| match d {
894                        FlagValue::Int(i) => format!(" (default {i})"),
895                        _ => String::new(),
896                    })
897                    .unwrap_or_default();
898
899                let flag_name_formatted = format!("{} int[{}-{}]", flag.name, min, max);
900                let left_part = format!(
901                    "      {}--{}",
902                    color::cyan(&short),
903                    color::cyan(&flag_name_formatted)
904                );
905
906                let description =
907                    format!("{}{}{}", flag.usage, color::dim(&default), constraint_info);
908                let terminal_width = get_terminal_width();
909                let left_column_width = 30;
910
911                let formatted =
912                    format_help_entry(&left_part, &description, left_column_width, terminal_width);
913                println!("{formatted}");
914                return;
915            }
916            _ => {}
917        }
918
919        let flag_type = match &flag.value_type {
920            FlagType::String => " string",
921            FlagType::Int => " int",
922            FlagType::Float => " float",
923            FlagType::Bool => "",
924            FlagType::StringSlice | FlagType::StringArray => " strings",
925            FlagType::File => " file",
926            FlagType::Directory => " dir",
927            FlagType::Choice(_) | FlagType::Range(_, _) => unreachable!(),
928        };
929
930        let default = flag
931            .default
932            .as_ref()
933            .map(|d| match d {
934                FlagValue::String(s) => format!(" (default \"{s}\")"),
935                FlagValue::Bool(b) => format!(" (default {b})"),
936                FlagValue::Int(i) => format!(" (default {i})"),
937                FlagValue::Float(f) => format!(" (default {f})"),
938                FlagValue::StringSlice(v) => format!(" (default {v:?})"),
939            })
940            .unwrap_or_default();
941
942        let flag_name_formatted = format!("{}{flag_type}", flag.name);
943        let left_part = format!(
944            "      {}--{}",
945            color::cyan(&short),
946            color::cyan(&flag_name_formatted)
947        );
948
949        let description = format!("{}{}{}", flag.usage, color::dim(&default), constraint_info);
950        let terminal_width = get_terminal_width();
951        let left_column_width = 30; // Adjust based on typical flag length
952
953        let formatted =
954            format_help_entry(&left_part, &description, left_column_width, terminal_width);
955        println!("{formatted}");
956    }
957
958    /// Finds command suggestions based on similarity
959    fn find_command_suggestions(&self, input: &str) -> Vec<String> {
960        let candidates: Vec<String> = self.subcommands.keys().cloned().collect();
961        find_suggestions(input, &candidates, self.suggestion_distance)
962    }
963
964    /// Handles shell completion requests
965    ///
966    /// This method is called when the shell requests completions via the
967    /// environment variable (e.g., `MYAPP_COMPLETE=bash`).
968    pub fn handle_completion_request(&self, args: &[String]) -> Result<Vec<String>> {
969        // Detect shell type from environment variable
970        let shell_type = self.detect_completion_shell();
971
972        // args format: ["__complete", ...previous_args, current_word]
973        if args.is_empty() || args[0] != "__complete" {
974            return Err(Error::Completion("Invalid completion request".to_string()));
975        }
976
977        let args = &args[1..];
978        if args.is_empty() {
979            // Complete root level
980            return Ok(self.get_completion_suggestions("", None, shell_type.as_deref()));
981        }
982
983        let current_word = args.last().unwrap_or(&String::new()).clone();
984        let previous_args = &args[..args.len().saturating_sub(1)];
985
986        // Parse through the command hierarchy
987        let mut current_cmd = self;
988        let mut ctx = Context::new(vec![]);
989        let mut i = 0;
990
991        while i < previous_args.len() {
992            let arg = &previous_args[i];
993
994            if arg.starts_with("--") {
995                // Long flag
996                let flag_name = arg.trim_start_matches("--");
997                if let Some((name, _)) = flag_name.split_once('=') {
998                    // Flag with value
999                    ctx.set_flag(name.to_string(), String::new());
1000                } else if let Some(_flag) = current_cmd.find_flag(flag_name) {
1001                    // Flag that might need a value
1002                    if i + 1 < previous_args.len() && !previous_args[i + 1].starts_with('-') {
1003                        ctx.set_flag(flag_name.to_string(), previous_args[i + 1].clone());
1004                        i += 1;
1005                    }
1006                }
1007            } else if arg.starts_with('-') && arg.len() > 1 {
1008                // Short flags
1009                let chars = arg.chars().skip(1).collect::<Vec<_>>();
1010                for ch in chars {
1011                    if let Some(flag) = current_cmd.find_flag_by_short(ch) {
1012                        ctx.set_flag(flag.name.clone(), String::new());
1013                    }
1014                }
1015            } else {
1016                // Potential subcommand
1017                if let Some(subcmd) = current_cmd.find_subcommand(arg) {
1018                    current_cmd = subcmd;
1019                } else {
1020                    ctx.args_mut().push(arg.clone());
1021                }
1022            }
1023            i += 1;
1024        }
1025
1026        // Now determine what to complete
1027        if current_word.starts_with("--") {
1028            // Complete long flags only (when user explicitly started typing --)
1029            let prefix = current_word.trim_start_matches("--");
1030            let mut flag_completions = CompletionResult::new();
1031
1032            // Collect flags with descriptions from current command and parents
1033            collect_all_flags_with_descriptions(current_cmd, &mut flag_completions, prefix);
1034
1035            let format = CompletionFormat::from_shell_type(shell_type.as_deref());
1036            Ok(format.format(&flag_completions, Some(&ctx)))
1037        } else if current_word.starts_with('-') && current_word.len() > 1 {
1038            // For short flags, we don't complete (too complex)
1039            Ok(vec![])
1040        } else {
1041            // Check if previous arg was a flag that needs a value
1042            if let Some(prev) = previous_args.last() {
1043                if prev.starts_with("--") {
1044                    let flag_name = prev.trim_start_matches("--");
1045
1046                    // First check if the flag itself has a completion function
1047                    if let Some(flag) = current_cmd.flags.get(flag_name) {
1048                        if let Some(ref completion_func) = flag.completion {
1049                            let result = completion_func(&ctx, &current_word)?;
1050                            let format = CompletionFormat::from_shell_type(shell_type.as_deref());
1051                            return Ok(format.format(&result, Some(&ctx)));
1052                        }
1053                    }
1054
1055                    // Fall back to flag_completions HashMap
1056                    if let Some(completion_func) = current_cmd.flag_completions.get(flag_name) {
1057                        let result = completion_func(&ctx, &current_word)?;
1058                        let format = CompletionFormat::from_shell_type(shell_type.as_deref());
1059                        return Ok(format.format(&result, Some(&ctx)));
1060                    }
1061                } else if prev.starts_with('-') && prev.len() == 2 {
1062                    // Handle short flag completions
1063                    let Some(short_flag) = prev.chars().nth(1) else {
1064                        // This should not happen given the length check, but handle gracefully
1065                        return Ok(vec![]);
1066                    };
1067                    if let Some(flag) = current_cmd.find_flag_by_short(short_flag) {
1068                        if let Some(ref completion_func) = flag.completion {
1069                            let result = completion_func(&ctx, &current_word)?;
1070                            let format = CompletionFormat::from_shell_type(shell_type.as_deref());
1071                            return Ok(format.format(&result, Some(&ctx)));
1072                        }
1073
1074                        // Also check flag_completions HashMap by flag name
1075                        if let Some(completion_func) = current_cmd.flag_completions.get(&flag.name)
1076                        {
1077                            let result = completion_func(&ctx, &current_word)?;
1078                            let format = CompletionFormat::from_shell_type(shell_type.as_deref());
1079                            return Ok(format.format(&result, Some(&ctx)));
1080                        }
1081                    }
1082                }
1083            }
1084
1085            // Complete subcommands, arguments AND flags together
1086            let mut combined_completions = CompletionResult::new();
1087
1088            // Get subcommand/argument completions
1089            let subcommand_suggestions = current_cmd.get_completion_suggestions(
1090                &current_word,
1091                Some(&ctx),
1092                shell_type.as_deref(),
1093            );
1094
1095            // Add flags that don't start with current_word (so user can discover them)
1096            // Only add flags if current_word is empty or doesn't look like it's trying to complete a specific subcommand
1097            if current_word.is_empty()
1098                || !current_cmd
1099                    .subcommands
1100                    .keys()
1101                    .any(|name| name.starts_with(&current_word))
1102            {
1103                collect_all_flags_with_descriptions(current_cmd, &mut combined_completions, "");
1104            }
1105
1106            // Convert subcommand suggestions to CompletionResult format and combine
1107            let format = CompletionFormat::from_shell_type(shell_type.as_deref());
1108            let mut final_suggestions = subcommand_suggestions;
1109            let flag_suggestions = format.format(&combined_completions, Some(&ctx));
1110            final_suggestions.extend(flag_suggestions);
1111
1112            Ok(final_suggestions)
1113        }
1114    }
1115
1116    /// Detects the shell type from the environment variable
1117    fn detect_completion_shell(&self) -> Option<String> {
1118        use std::env;
1119
1120        // Look for shell-specific completion environment variables
1121        let env_var = format!("{}_COMPLETE", self.name.to_uppercase());
1122        env::var(&env_var).ok()
1123    }
1124
1125    fn get_completion_suggestions(
1126        &self,
1127        prefix: &str,
1128        ctx: Option<&Context>,
1129        shell_type: Option<&str>,
1130    ) -> Vec<String> {
1131        let mut completion_result = CompletionResult::new();
1132        let mut has_suggestions = false;
1133
1134        // Add subcommands with their descriptions
1135        for (name, cmd) in &self.subcommands {
1136            if name.starts_with(prefix) {
1137                completion_result =
1138                    completion_result.add_with_description(name.clone(), cmd.short.clone());
1139                has_suggestions = true;
1140            }
1141            // Also check aliases
1142            for alias in &cmd.aliases {
1143                if alias.starts_with(prefix) {
1144                    completion_result = completion_result
1145                        .add_with_description(alias.clone(), format!("Alias for {name}"));
1146                    has_suggestions = true;
1147                }
1148            }
1149        }
1150
1151        // If we have arg completions and no subcommands match, try those
1152        if !has_suggestions {
1153            if let Some(ref completion_func) = self.arg_completions {
1154                let default_ctx = Context::new(vec![]);
1155                let ctx = ctx.unwrap_or(&default_ctx);
1156                if let Ok(result) = completion_func(ctx, prefix) {
1157                    let format = CompletionFormat::from_shell_type(shell_type);
1158                    return format.format(&result, Some(ctx));
1159                }
1160            }
1161        }
1162
1163        // Format the results
1164        let format = CompletionFormat::from_shell_type(shell_type);
1165        let default_ctx = Context::new(vec![]);
1166        let ctx_to_use = ctx.unwrap_or(&default_ctx);
1167        let mut suggestions = format.format(&completion_result, Some(ctx_to_use));
1168        suggestions.sort();
1169        suggestions.dedup();
1170        suggestions
1171    }
1172}
1173
1174/// Builder for creating commands with a fluent API
1175///
1176/// `CommandBuilder` provides a convenient way to construct commands
1177/// with method chaining. This is the recommended way to create commands.
1178///
1179/// # Examples
1180///
1181/// ```rust
1182/// use flag_rs::{CommandBuilder, Flag, FlagType, FlagValue};
1183///
1184/// let cmd = CommandBuilder::new("serve")
1185///     .short("Start the web server")
1186///     .long("Start the web server on the specified port with the given configuration")
1187///     .aliases(vec!["server", "s"])
1188///     .flag(
1189///         Flag::new("port")
1190///             .short('p')
1191///             .usage("Port to listen on")
1192///             .value_type(FlagType::Int)
1193///             .default(FlagValue::Int(8080))
1194///     )
1195///     .flag(
1196///         Flag::new("config")
1197///             .short('c')
1198///             .usage("Configuration file path")
1199///             .value_type(FlagType::String)
1200///             .required()
1201///     )
1202///     .run(|ctx| {
1203///         let port = ctx.flag("port")
1204///             .and_then(|s| s.parse::<i64>().ok())
1205///             .unwrap_or(8080);
1206///         let config = ctx.flag("config")
1207///             .map(|s| s.as_str())
1208///             .unwrap_or("config.toml");
1209///
1210///         println!("Starting server on port {} with config {}", port, config);
1211///         Ok(())
1212///     })
1213///     .build();
1214/// ```
1215pub struct CommandBuilder {
1216    command: Command,
1217}
1218
1219impl CommandBuilder {
1220    /// Creates a new command builder with the given name
1221    pub fn new(name: impl Into<String>) -> Self {
1222        Self {
1223            command: Command::new(name),
1224        }
1225    }
1226
1227    /// Adds a single alias for this command
1228    ///
1229    /// # Examples
1230    ///
1231    /// ```rust
1232    /// use flag_rs::CommandBuilder;
1233    ///
1234    /// let cmd = CommandBuilder::new("remove")
1235    ///     .alias("rm")
1236    ///     .alias("delete")
1237    ///     .build();
1238    /// ```
1239    #[must_use]
1240    pub fn alias(mut self, alias: impl Into<String>) -> Self {
1241        self.command.aliases.push(alias.into());
1242        self
1243    }
1244
1245    /// Adds multiple aliases for this command
1246    ///
1247    /// # Examples
1248    ///
1249    /// ```rust
1250    /// use flag_rs::CommandBuilder;
1251    ///
1252    /// let cmd = CommandBuilder::new("remove")
1253    ///     .aliases(vec!["rm", "delete", "del"])
1254    ///     .build();
1255    /// ```
1256    #[must_use]
1257    pub fn aliases<I, S>(mut self, aliases: I) -> Self
1258    where
1259        I: IntoIterator<Item = S>,
1260        S: Into<String>,
1261    {
1262        self.command
1263            .aliases
1264            .extend(aliases.into_iter().map(Into::into));
1265        self
1266    }
1267
1268    /// Sets the short description for this command
1269    ///
1270    /// The short description is shown in the parent command's help output.
1271    #[must_use]
1272    pub fn short(mut self, short: impl Into<String>) -> Self {
1273        self.command.short = short.into();
1274        self
1275    }
1276
1277    /// Sets the long description for this command
1278    ///
1279    /// The long description is shown in this command's help output.
1280    #[must_use]
1281    pub fn long(mut self, long: impl Into<String>) -> Self {
1282        self.command.long = long.into();
1283        self
1284    }
1285
1286    /// Adds an example for this command
1287    ///
1288    /// Examples are shown in the help output to demonstrate command usage.
1289    ///
1290    /// # Examples
1291    ///
1292    /// ```rust
1293    /// use flag_rs::CommandBuilder;
1294    ///
1295    /// let cmd = CommandBuilder::new("deploy")
1296    ///     .short("Deploy the application")
1297    ///     .example("deploy --env production")
1298    ///     .example("deploy --env staging --dry-run")
1299    ///     .build();
1300    /// ```
1301    #[must_use]
1302    pub fn example(mut self, example: impl Into<String>) -> Self {
1303        self.command.examples.push(example.into());
1304        self
1305    }
1306
1307    /// Sets the group ID for this command
1308    ///
1309    /// Commands with the same group ID will be displayed together in help output.
1310    ///
1311    /// # Examples
1312    ///
1313    /// ```rust
1314    /// use flag_rs::CommandBuilder;
1315    ///
1316    /// let app = CommandBuilder::new("kubectl")
1317    ///     .subcommand(
1318    ///         CommandBuilder::new("get")
1319    ///             .short("Display resources")
1320    ///             .group_id("Basic Commands")
1321    ///             .build()
1322    ///     )
1323    ///     .subcommand(
1324    ///         CommandBuilder::new("create")
1325    ///             .short("Create resources")
1326    ///             .group_id("Basic Commands")
1327    ///             .build()
1328    ///     )
1329    ///     .subcommand(
1330    ///         CommandBuilder::new("config")
1331    ///             .short("Modify kubeconfig files")
1332    ///             .group_id("Settings Commands")
1333    ///             .build()
1334    ///     )
1335    ///     .build();
1336    /// ```
1337    #[must_use]
1338    pub fn group_id(mut self, group_id: impl Into<String>) -> Self {
1339        self.command.group_id = Some(group_id.into());
1340        self
1341    }
1342
1343    /// Adds a subcommand to this command
1344    ///
1345    /// # Examples
1346    ///
1347    /// ```rust
1348    /// use flag_rs::CommandBuilder;
1349    ///
1350    /// let app = CommandBuilder::new("myapp")
1351    ///     .subcommand(
1352    ///         CommandBuilder::new("init")
1353    ///             .short("Initialize a new project")
1354    ///             .build()
1355    ///     )
1356    ///     .subcommand(
1357    ///         CommandBuilder::new("build")
1358    ///             .short("Build the project")
1359    ///             .build()
1360    ///     )
1361    ///     .build();
1362    /// ```
1363    #[must_use]
1364    pub fn subcommand(mut self, cmd: Command) -> Self {
1365        self.command.add_command(cmd);
1366        self
1367    }
1368
1369    /// Adds multiple subcommands to this command at once
1370    ///
1371    /// # Examples
1372    ///
1373    /// ```rust
1374    /// use flag_rs::CommandBuilder;
1375    ///
1376    /// let cmd = CommandBuilder::new("git")
1377    ///     .subcommands(vec![
1378    ///         CommandBuilder::new("add")
1379    ///             .short("Add file contents to the index")
1380    ///             .build(),
1381    ///         CommandBuilder::new("commit")
1382    ///             .short("Record changes to the repository")
1383    ///             .build(),
1384    ///         CommandBuilder::new("push")
1385    ///             .short("Update remote refs along with associated objects")
1386    ///             .build(),
1387    ///     ])
1388    ///     .build();
1389    /// ```
1390    #[must_use]
1391    pub fn subcommands(mut self, cmds: Vec<Command>) -> Self {
1392        for cmd in cmds {
1393            self.command.add_command(cmd);
1394        }
1395        self
1396    }
1397
1398    /// Adds a flag to this command
1399    ///
1400    /// # Examples
1401    ///
1402    /// ```rust
1403    /// use flag_rs::{CommandBuilder, Flag, FlagType};
1404    ///
1405    /// let cmd = CommandBuilder::new("deploy")
1406    ///     .flag(
1407    ///         Flag::new("force")
1408    ///             .short('f')
1409    ///             .usage("Force deployment without confirmation")
1410    ///             .value_type(FlagType::Bool)
1411    ///     )
1412    ///     .build();
1413    /// ```
1414    #[must_use]
1415    pub fn flag(mut self, flag: Flag) -> Self {
1416        self.command.flags.insert(flag.name.clone(), flag);
1417        self
1418    }
1419
1420    /// Adds multiple flags to this command at once
1421    ///
1422    /// # Examples
1423    ///
1424    /// ```rust
1425    /// use flag_rs::{CommandBuilder, Flag};
1426    ///
1427    /// let cmd = CommandBuilder::new("server")
1428    ///     .flags(vec![
1429    ///         Flag::bool("verbose").short('v').usage("Enable verbose output"),
1430    ///         Flag::bool("quiet").short('q').usage("Suppress output"),
1431    ///         Flag::int("port").short('p').usage("Port to listen on").default_int(8080),
1432    ///     ])
1433    ///     .build();
1434    /// ```
1435    #[must_use]
1436    pub fn flags(mut self, flags: Vec<Flag>) -> Self {
1437        for flag in flags {
1438            self.command.flags.insert(flag.name.clone(), flag);
1439        }
1440        self
1441    }
1442
1443    /// Sets the function to run when this command is executed
1444    ///
1445    /// The run function receives a mutable reference to the [`Context`]
1446    /// which provides access to parsed flags and arguments.
1447    ///
1448    /// # Examples
1449    ///
1450    /// ```rust
1451    /// use flag_rs::CommandBuilder;
1452    ///
1453    /// let cmd = CommandBuilder::new("greet")
1454    ///     .run(|ctx| {
1455    ///         let name = ctx.args().first()
1456    ///             .map(|s| s.as_str())
1457    ///             .unwrap_or("World");
1458    ///         println!("Hello, {}!", name);
1459    ///         Ok(())
1460    ///     })
1461    ///     .build();
1462    /// ```
1463    #[must_use]
1464    pub fn run<F>(mut self, f: F) -> Self
1465    where
1466        F: Fn(&mut Context) -> Result<()> + Send + Sync + 'static,
1467    {
1468        self.command.run = Some(Box::new(f));
1469        self
1470    }
1471
1472    /// Sets the argument validator for this command
1473    ///
1474    /// The validator will be called before the run function to ensure
1475    /// arguments meet the specified constraints.
1476    ///
1477    /// # Examples
1478    ///
1479    /// ```rust
1480    /// use flag_rs::{CommandBuilder, ArgValidator};
1481    ///
1482    /// let cmd = CommandBuilder::new("delete")
1483    ///     .args(ArgValidator::MinimumArgs(1))
1484    ///     .run(|ctx| {
1485    ///         for file in ctx.args() {
1486    ///             println!("Deleting: {}", file);
1487    ///         }
1488    ///         Ok(())
1489    ///     })
1490    ///     .build();
1491    /// ```
1492    #[must_use]
1493    pub fn args(mut self, validator: ArgValidator) -> Self {
1494        self.command.arg_validator = Some(validator);
1495        self
1496    }
1497
1498    /// Sets the persistent pre-run hook for this command
1499    ///
1500    /// This hook runs before the command and all its subcommands.
1501    /// It's inherited by all subcommands and runs in parent-to-child order.
1502    ///
1503    /// # Examples
1504    ///
1505    /// ```rust
1506    /// use flag_rs::CommandBuilder;
1507    ///
1508    /// let cmd = CommandBuilder::new("app")
1509    ///     .persistent_pre_run(|ctx| {
1510    ///         println!("Setting up logging...");
1511    ///         Ok(())
1512    ///     })
1513    ///     .build();
1514    /// ```
1515    #[must_use]
1516    pub fn persistent_pre_run<F>(mut self, f: F) -> Self
1517    where
1518        F: Fn(&mut Context) -> Result<()> + Send + Sync + 'static,
1519    {
1520        self.command.persistent_pre_run = Some(Box::new(f));
1521        self
1522    }
1523
1524    /// Sets the pre-run hook for this command
1525    ///
1526    /// This hook runs only for this specific command, after any persistent
1527    /// pre-run hooks but before the main run function.
1528    ///
1529    /// # Examples
1530    ///
1531    /// ```rust
1532    /// use flag_rs::CommandBuilder;
1533    ///
1534    /// let cmd = CommandBuilder::new("deploy")
1535    ///     .pre_run(|ctx| {
1536    ///         println!("Validating deployment configuration...");
1537    ///         Ok(())
1538    ///     })
1539    ///     .run(|ctx| {
1540    ///         println!("Deploying application...");
1541    ///         Ok(())
1542    ///     })
1543    ///     .build();
1544    /// ```
1545    #[must_use]
1546    pub fn pre_run<F>(mut self, f: F) -> Self
1547    where
1548        F: Fn(&mut Context) -> Result<()> + Send + Sync + 'static,
1549    {
1550        self.command.pre_run = Some(Box::new(f));
1551        self
1552    }
1553
1554    /// Sets the post-run hook for this command
1555    ///
1556    /// This hook runs only for this specific command, after the main run
1557    /// function but before any persistent post-run hooks.
1558    ///
1559    /// # Examples
1560    ///
1561    /// ```rust
1562    /// use flag_rs::CommandBuilder;
1563    ///
1564    /// let cmd = CommandBuilder::new("test")
1565    ///     .run(|ctx| {
1566    ///         println!("Running tests...");
1567    ///         Ok(())
1568    ///     })
1569    ///     .post_run(|ctx| {
1570    ///         println!("Generating test report...");
1571    ///         Ok(())
1572    ///     })
1573    ///     .build();
1574    /// ```
1575    #[must_use]
1576    pub fn post_run<F>(mut self, f: F) -> Self
1577    where
1578        F: Fn(&mut Context) -> Result<()> + Send + Sync + 'static,
1579    {
1580        self.command.post_run = Some(Box::new(f));
1581        self
1582    }
1583
1584    /// Sets the persistent post-run hook for this command
1585    ///
1586    /// This hook runs after the command and all its subcommands.
1587    /// It's inherited by all subcommands and runs in child-to-parent order.
1588    ///
1589    /// # Examples
1590    ///
1591    /// ```rust
1592    /// use flag_rs::CommandBuilder;
1593    ///
1594    /// let cmd = CommandBuilder::new("app")
1595    ///     .persistent_post_run(|ctx| {
1596    ///         println!("Cleaning up resources...");
1597    ///         Ok(())
1598    ///     })
1599    ///     .build();
1600    /// ```
1601    #[must_use]
1602    pub fn persistent_post_run<F>(mut self, f: F) -> Self
1603    where
1604        F: Fn(&mut Context) -> Result<()> + Send + Sync + 'static,
1605    {
1606        self.command.persistent_post_run = Some(Box::new(f));
1607        self
1608    }
1609
1610    /// Sets the argument completion function
1611    ///
1612    /// This function is called when the user presses TAB to complete arguments.
1613    /// It enables dynamic completions based on runtime state.
1614    ///
1615    /// # Examples
1616    ///
1617    /// ```rust
1618    /// use flag_rs::{CommandBuilder, CompletionResult};
1619    ///
1620    /// let cmd = CommandBuilder::new("edit")
1621    ///     .arg_completion(|ctx, prefix| {
1622    ///         // In a real app, list files from the filesystem
1623    ///         let files = vec!["main.rs", "lib.rs", "Cargo.toml"];
1624    ///         Ok(CompletionResult::new().extend(
1625    ///             files.into_iter()
1626    ///                 .filter(|f| f.starts_with(prefix))
1627    ///                 .map(String::from)
1628    ///         ))
1629    ///     })
1630    ///     .build();
1631    /// ```
1632    #[must_use]
1633    pub fn arg_completion<F>(mut self, f: F) -> Self
1634    where
1635        F: Fn(&Context, &str) -> Result<CompletionResult> + Send + Sync + 'static,
1636    {
1637        self.command.set_arg_completion(f);
1638        self
1639    }
1640
1641    /// Sets the completion function for a specific flag
1642    ///
1643    /// # Examples
1644    ///
1645    /// ```rust
1646    /// use flag_rs::{CommandBuilder, CompletionResult, Flag, FlagType};
1647    ///
1648    /// let cmd = CommandBuilder::new("connect")
1649    ///     .flag(
1650    ///         Flag::new("server")
1651    ///             .usage("Server to connect to")
1652    ///             .value_type(FlagType::String)
1653    ///     )
1654    ///     .flag_completion("server", |ctx, prefix| {
1655    ///         // In a real app, discover available servers
1656    ///         let servers = vec!["prod-1", "prod-2", "staging", "dev"];
1657    ///         Ok(CompletionResult::new().extend(
1658    ///             servers.into_iter()
1659    ///                 .filter(|s| s.starts_with(prefix))
1660    ///                 .map(String::from)
1661    ///         ))
1662    ///     })
1663    ///     .build();
1664    /// ```
1665    #[must_use]
1666    pub fn flag_completion<F>(mut self, flag_name: impl Into<String>, f: F) -> Self
1667    where
1668        F: Fn(&Context, &str) -> Result<CompletionResult> + Send + Sync + 'static,
1669    {
1670        self.command.set_flag_completion(flag_name, f);
1671        self
1672    }
1673
1674    /// Enables or disables command suggestions
1675    ///
1676    /// When enabled, the framework will suggest similar commands when
1677    /// a user types an unknown command.
1678    ///
1679    /// # Examples
1680    ///
1681    /// ```rust
1682    /// use flag_rs::CommandBuilder;
1683    ///
1684    /// let cmd = CommandBuilder::new("myapp")
1685    ///     .suggestions(true)  // Enable suggestions (default)
1686    ///     .build();
1687    /// ```
1688    #[must_use]
1689    pub fn suggestions(mut self, enabled: bool) -> Self {
1690        self.command.suggestions_enabled = enabled;
1691        self
1692    }
1693
1694    /// Sets the maximum Levenshtein distance for suggestions
1695    ///
1696    /// Commands within this distance will be suggested as alternatives.
1697    /// Default is 2.
1698    ///
1699    /// # Examples
1700    ///
1701    /// ```rust
1702    /// use flag_rs::CommandBuilder;
1703    ///
1704    /// let cmd = CommandBuilder::new("myapp")
1705    ///     .suggestion_distance(3)  // Allow more distant suggestions
1706    ///     .build();
1707    /// ```
1708    #[must_use]
1709    pub fn suggestion_distance(mut self, distance: usize) -> Self {
1710        self.command.suggestion_distance = distance;
1711        self
1712    }
1713
1714    /// Builds and returns the completed [`Command`]
1715    #[must_use]
1716    pub fn build(self) -> Command {
1717        self.command
1718    }
1719}
1720
1721#[cfg(test)]
1722mod tests {
1723    use super::*;
1724    use crate::flag::FlagType;
1725    use std::sync::{Arc, Mutex};
1726
1727    #[test]
1728    fn test_simple_command_execution() {
1729        let executed = Arc::new(Mutex::new(false));
1730        let executed_clone = executed.clone();
1731
1732        let cmd = CommandBuilder::new("test")
1733            .run(move |_ctx| {
1734                *executed_clone.lock().unwrap() = true;
1735                Ok(())
1736            })
1737            .build();
1738
1739        cmd.execute(vec![]).unwrap();
1740        assert!(*executed.lock().unwrap());
1741    }
1742
1743    #[test]
1744    fn test_command_with_args() {
1745        let received_args = Arc::new(Mutex::new(Vec::new()));
1746        let args_clone = received_args.clone();
1747
1748        let cmd = CommandBuilder::new("test")
1749            .run(move |ctx| {
1750                *args_clone.lock().unwrap() = ctx.args().to_vec();
1751                Ok(())
1752            })
1753            .build();
1754
1755        cmd.execute(vec!["arg1".to_string(), "arg2".to_string()])
1756            .unwrap();
1757        assert_eq!(*received_args.lock().unwrap(), vec!["arg1", "arg2"]);
1758    }
1759
1760    #[test]
1761    fn test_subcommand_execution() {
1762        let main_executed = Arc::new(Mutex::new(false));
1763        let sub_executed = Arc::new(Mutex::new(false));
1764        let sub_clone = sub_executed.clone();
1765
1766        let subcmd = CommandBuilder::new("sub")
1767            .run(move |_ctx| {
1768                *sub_clone.lock().unwrap() = true;
1769                Ok(())
1770            })
1771            .build();
1772
1773        let main_clone = main_executed.clone();
1774        let cmd = CommandBuilder::new("main")
1775            .run(move |_ctx| {
1776                *main_clone.lock().unwrap() = true;
1777                Ok(())
1778            })
1779            .subcommand(subcmd)
1780            .build();
1781
1782        // Execute subcommand
1783        cmd.execute(vec!["sub".to_string()]).unwrap();
1784        assert!(*sub_executed.lock().unwrap());
1785        assert!(!*main_executed.lock().unwrap());
1786    }
1787
1788    #[test]
1789    fn test_flag_parsing() {
1790        let cmd = CommandBuilder::new("test")
1791            .flag(Flag::new("verbose").short('v').value_type(FlagType::Bool))
1792            .flag(Flag::new("output").short('o').value_type(FlagType::String))
1793            .flag(Flag::new("count").value_type(FlagType::Int))
1794            .run(|ctx| {
1795                assert_eq!(ctx.flag("verbose"), Some(&"true".to_string()));
1796                assert_eq!(ctx.flag("output"), Some(&"file.txt".to_string()));
1797                assert_eq!(ctx.flag("count"), Some(&"42".to_string()));
1798                assert_eq!(ctx.args(), &["remaining"]);
1799                Ok(())
1800            })
1801            .build();
1802
1803        cmd.execute(vec![
1804            "-v".to_string(),
1805            "--output".to_string(),
1806            "file.txt".to_string(),
1807            "--count=42".to_string(),
1808            "remaining".to_string(),
1809        ])
1810        .unwrap();
1811    }
1812
1813    #[test]
1814    fn test_flag_inheritance() {
1815        let sub_executed = Arc::new(Mutex::new(false));
1816        let sub_clone = sub_executed.clone();
1817
1818        let subcmd = CommandBuilder::new("sub")
1819            .run(move |ctx| {
1820                assert_eq!(ctx.flag("global"), Some(&"value".to_string()));
1821                *sub_clone.lock().unwrap() = true;
1822                Ok(())
1823            })
1824            .build();
1825
1826        let cmd = CommandBuilder::new("main")
1827            .flag(Flag::new("global").value_type(FlagType::String))
1828            .subcommand(subcmd)
1829            .build();
1830
1831        cmd.execute(vec![
1832            "--global".to_string(),
1833            "value".to_string(),
1834            "sub".to_string(),
1835        ])
1836        .unwrap();
1837
1838        assert!(*sub_executed.lock().unwrap());
1839    }
1840
1841    #[test]
1842    fn test_command_aliases() {
1843        let executed = Arc::new(Mutex::new(String::new()));
1844        let exec_clone = executed.clone();
1845
1846        let subcmd = CommandBuilder::new("subcommand")
1847            .aliases(vec!["sub", "s"])
1848            .run(move |_ctx| {
1849                *exec_clone.lock().unwrap() = "subcommand".to_string();
1850                Ok(())
1851            })
1852            .build();
1853
1854        let cmd = CommandBuilder::new("main").subcommand(subcmd).build();
1855
1856        // Test main name
1857        cmd.execute(vec!["subcommand".to_string()]).unwrap();
1858        assert_eq!(*executed.lock().unwrap(), "subcommand");
1859
1860        // Test alias
1861        cmd.execute(vec!["sub".to_string()]).unwrap();
1862        assert_eq!(*executed.lock().unwrap(), "subcommand");
1863
1864        // Test short alias
1865        cmd.execute(vec!["s".to_string()]).unwrap();
1866        assert_eq!(*executed.lock().unwrap(), "subcommand");
1867    }
1868
1869    #[test]
1870    fn test_error_cases() {
1871        let cmd = CommandBuilder::new("main").build();
1872
1873        // No subcommand when required
1874        let result = cmd.execute(vec![]);
1875        assert!(result.is_err());
1876        assert!(matches!(result.unwrap_err(), Error::SubcommandRequired(_)));
1877
1878        // Unknown subcommand
1879        let result = cmd.execute(vec!["unknown".to_string()]);
1880        assert!(result.is_err());
1881        assert!(matches!(result.unwrap_err(), Error::CommandNotFound { .. }));
1882
1883        // Unknown flag (now treated as argument, so it becomes unknown command)
1884        let result = cmd.execute(vec!["--unknown".to_string()]);
1885        assert!(result.is_err());
1886        assert!(matches!(result.unwrap_err(), Error::CommandNotFound { .. }));
1887    }
1888
1889    #[test]
1890    fn test_completion() {
1891        let cmd = CommandBuilder::new("test")
1892            .arg_completion(|_ctx, prefix| {
1893                Ok(CompletionResult::new().extend(
1894                    vec!["file1.txt", "file2.txt", "folder/"]
1895                        .into_iter()
1896                        .filter(|f| f.starts_with(prefix))
1897                        .map(String::from),
1898                ))
1899            })
1900            .flag_completion("type", |_ctx, prefix| {
1901                Ok(CompletionResult::new().extend(
1902                    vec!["json", "yaml", "xml"]
1903                        .into_iter()
1904                        .filter(|t| t.starts_with(prefix))
1905                        .map(String::from),
1906                ))
1907            })
1908            .build();
1909
1910        let ctx = Context::new(vec![]);
1911
1912        // Test arg completion
1913        let result = cmd.get_completions(&ctx, "fi", None).unwrap();
1914        assert_eq!(result.values, vec!["file1.txt", "file2.txt"]);
1915
1916        // Test flag completion
1917        let result = cmd.get_completions(&ctx, "j", Some("type")).unwrap();
1918        assert_eq!(result.values, vec!["json"]);
1919    }
1920
1921    #[test]
1922    fn test_flag_with_equals() {
1923        let cmd = CommandBuilder::new("test")
1924            .flag(Flag::new("output").value_type(FlagType::String))
1925            .run(|ctx| {
1926                assert_eq!(
1927                    ctx.flag("output"),
1928                    Some(&"/path/with=equals.txt".to_string())
1929                );
1930                Ok(())
1931            })
1932            .build();
1933
1934        cmd.execute(vec!["--output=/path/with=equals.txt".to_string()])
1935            .unwrap();
1936    }
1937
1938    #[test]
1939    fn test_help_flag() {
1940        let cmd = CommandBuilder::new("test")
1941            .short("Test command")
1942            .long("This is a test command")
1943            .flag(
1944                Flag::new("verbose")
1945                    .short('v')
1946                    .usage("Enable verbose output"),
1947            )
1948            .build();
1949
1950        // Test --help
1951        let result = cmd.execute(vec!["--help".to_string()]);
1952        assert!(result.is_ok());
1953
1954        // Test -h
1955        let result = cmd.execute(vec!["-h".to_string()]);
1956        assert!(result.is_ok());
1957    }
1958
1959    #[test]
1960    fn test_subcommand_help() {
1961        let subcmd = CommandBuilder::new("sub")
1962            .short("Subcommand")
1963            .flag(Flag::new("subflag").usage("A flag for the subcommand"))
1964            .build();
1965
1966        let cmd = CommandBuilder::new("main")
1967            .flag(Flag::new("global").usage("A global flag"))
1968            .subcommand(subcmd)
1969            .build();
1970
1971        // Test help on subcommand
1972        let result = cmd.execute(vec!["sub".to_string(), "--help".to_string()]);
1973        assert!(result.is_ok());
1974    }
1975}