Skip to main content

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