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