flag_rs/
flag.rs

1//! Flag system for command-line argument parsing
2//!
3//! This module provides a flexible flag parsing system that supports:
4//! - Multiple value types (string, bool, int, float, string slice)
5//! - Short and long flag names
6//! - Required and optional flags
7//! - Default values
8//! - Hierarchical flag inheritance from parent commands
9
10use crate::completion::{CompletionFunc, CompletionResult};
11use crate::error::{Error, Result};
12use std::collections::HashSet;
13
14/// Represents the value of a parsed flag
15///
16/// `FlagValue` is an enum that can hold different types of values
17/// that flags can have. This allows for type-safe access to flag values.
18///
19/// # Examples
20///
21/// ```
22/// use flag_rs::flag::{FlagValue, FlagType, Flag};
23///
24/// // Parse different types of values
25/// let string_flag = Flag::new("name").value_type(FlagType::String);
26/// let value = string_flag.parse_value("John").unwrap();
27/// assert_eq!(value.as_string().unwrap(), "John");
28///
29/// let bool_flag = Flag::new("verbose").value_type(FlagType::Bool);
30/// let value = bool_flag.parse_value("true").unwrap();
31/// assert!(value.as_bool().unwrap());
32/// ```
33#[derive(Clone, Debug, PartialEq)]
34pub enum FlagValue {
35    /// A string value
36    String(String),
37    /// A boolean value
38    Bool(bool),
39    /// An integer value
40    Int(i64),
41    /// A floating-point value
42    Float(f64),
43    /// A slice of strings (for repeated flags)
44    StringSlice(Vec<String>),
45}
46
47impl FlagValue {
48    /// Returns the value as a string reference
49    ///
50    /// # Errors
51    ///
52    /// Returns `Error::FlagParsing` if the value is not a string
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use flag_rs::flag::FlagValue;
58    ///
59    /// let value = FlagValue::String("hello".to_string());
60    /// assert_eq!(value.as_string().unwrap(), "hello");
61    ///
62    /// let value = FlagValue::Bool(true);
63    /// assert!(value.as_string().is_err());
64    /// ```
65    pub fn as_string(&self) -> Result<&String> {
66        match self {
67            Self::String(s) => Ok(s),
68            _ => Err(Error::flag_parsing("Flag value is not a string")),
69        }
70    }
71
72    /// Returns the value as a boolean
73    ///
74    /// # Errors
75    ///
76    /// Returns `Error::FlagParsing` if the value is not a boolean
77    pub fn as_bool(&self) -> Result<bool> {
78        match self {
79            Self::Bool(b) => Ok(*b),
80            _ => Err(Error::flag_parsing("Flag value is not a boolean")),
81        }
82    }
83
84    /// Returns the value as an integer
85    ///
86    /// # Errors
87    ///
88    /// Returns `Error::FlagParsing` if the value is not an integer
89    pub fn as_int(&self) -> Result<i64> {
90        match self {
91            Self::Int(i) => Ok(*i),
92            _ => Err(Error::flag_parsing("Flag value is not an integer")),
93        }
94    }
95
96    /// Returns the value as a float
97    ///
98    /// # Errors
99    ///
100    /// Returns `Error::FlagParsing` if the value is not a float
101    pub fn as_float(&self) -> Result<f64> {
102        match self {
103            Self::Float(f) => Ok(*f),
104            _ => Err(Error::flag_parsing("Flag value is not a float")),
105        }
106    }
107
108    /// Returns the value as a string slice reference
109    ///
110    /// # Errors
111    ///
112    /// Returns `Error::FlagParsing` if the value is not a string slice
113    pub fn as_string_slice(&self) -> Result<&Vec<String>> {
114        match self {
115            Self::StringSlice(v) => Ok(v),
116            _ => Err(Error::flag_parsing("Flag value is not a string slice")),
117        }
118    }
119}
120
121/// Represents constraints that can be applied to flags
122///
123/// Flag constraints allow you to define relationships between flags,
124/// such as mutual exclusivity or dependencies.
125#[derive(Clone, Debug, PartialEq, Eq)]
126pub enum FlagConstraint {
127    /// This flag is required if another flag is set
128    RequiredIf(String),
129    /// This flag conflicts with other flags (mutually exclusive)
130    ConflictsWith(Vec<String>),
131    /// This flag requires other flags to be set
132    Requires(Vec<String>),
133}
134
135/// Represents a command-line flag
136///
137/// A `Flag` defines a command-line option that can be passed to a command.
138/// Flags can have both long names (e.g., `--verbose`) and short names (e.g., `-v`).
139///
140/// # Examples
141///
142/// ```
143/// use flag_rs::flag::{Flag, FlagType, FlagValue};
144///
145/// // Create a boolean flag
146/// let verbose = Flag::new("verbose")
147///     .short('v')
148///     .usage("Enable verbose output")
149///     .value_type(FlagType::Bool)
150///     .default(FlagValue::Bool(false));
151///
152/// // Create a string flag with validation
153/// let name = Flag::new("name")
154///     .short('n')
155///     .usage("Name of the resource")
156///     .value_type(FlagType::String)
157///     .required();
158/// ```
159pub struct Flag {
160    /// The long name of the flag (e.g., "verbose" for --verbose)
161    pub name: String,
162    /// The optional short name of the flag (e.g., 'v' for -v)
163    pub short: Option<char>,
164    /// A description of what the flag does
165    pub usage: String,
166    /// The default value if the flag is not provided
167    pub default: Option<FlagValue>,
168    /// Whether this flag must be provided
169    pub required: bool,
170    /// The type of value this flag accepts
171    pub value_type: FlagType,
172    /// Constraints applied to this flag
173    pub constraints: Vec<FlagConstraint>,
174    /// Optional completion function for this flag's values
175    pub completion: Option<CompletionFunc>,
176}
177
178/// Represents the type of value a flag accepts
179///
180/// This enum determines how flag values are parsed from string input.
181#[derive(Clone, Debug, PartialEq, Eq)]
182pub enum FlagType {
183    /// Accepts any string value
184    String,
185    /// Accepts boolean values (true/false, yes/no, 1/0)
186    Bool,
187    /// Accepts integer values
188    Int,
189    /// Accepts floating-point values
190    Float,
191    /// Accepts multiple string values (can be specified multiple times)
192    StringSlice,
193    /// Accepts multiple string values with accumulation (--tag=a --tag=b)
194    StringArray,
195    /// Must be one of a predefined set of values
196    Choice(Vec<String>),
197    /// Numeric value within a specific range
198    Range(i64, i64),
199    /// Must be a valid file path
200    File,
201    /// Must be a valid directory path
202    Directory,
203}
204
205impl Flag {
206    /// Creates a new flag with the given name
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use flag_rs::flag::Flag;
212    ///
213    /// let flag = Flag::new("verbose");
214    /// assert_eq!(flag.name, "verbose");
215    /// ```
216    #[must_use]
217    pub fn new(name: impl Into<String>) -> Self {
218        Self {
219            name: name.into(),
220            short: None,
221            usage: String::new(),
222            default: None,
223            required: false,
224            value_type: FlagType::String,
225            constraints: Vec::new(),
226            completion: None,
227        }
228    }
229
230    /// Creates a new boolean flag
231    ///
232    /// # Examples
233    ///
234    /// ```
235    /// use flag_rs::flag::Flag;
236    ///
237    /// let flag = Flag::bool("verbose");
238    /// ```
239    #[must_use]
240    pub fn bool(name: impl Into<String>) -> Self {
241        Self::new(name).value_type(FlagType::Bool)
242    }
243
244    /// Creates a new integer flag
245    ///
246    /// # Examples
247    ///
248    /// ```
249    /// use flag_rs::flag::Flag;
250    ///
251    /// let flag = Flag::int("port");
252    /// ```
253    #[must_use]
254    pub fn int(name: impl Into<String>) -> Self {
255        Self::new(name).value_type(FlagType::Int)
256    }
257
258    /// Creates a new float flag
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// use flag_rs::flag::Flag;
264    ///
265    /// let flag = Flag::float("ratio");
266    /// ```
267    #[must_use]
268    pub fn float(name: impl Into<String>) -> Self {
269        Self::new(name).value_type(FlagType::Float)
270    }
271
272    /// Creates a new string flag
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use flag_rs::flag::Flag;
278    ///
279    /// let flag = Flag::string("name");
280    /// ```
281    #[must_use]
282    pub fn string(name: impl Into<String>) -> Self {
283        Self::new(name) // String is the default type
284    }
285
286    /// Creates a new string slice flag (can be specified multiple times)
287    ///
288    /// # Examples
289    ///
290    /// ```
291    /// use flag_rs::flag::Flag;
292    ///
293    /// let flag = Flag::string_slice("tag");
294    /// ```
295    #[must_use]
296    pub fn string_slice(name: impl Into<String>) -> Self {
297        Self::new(name).value_type(FlagType::StringSlice)
298    }
299
300    /// Creates a new choice flag with allowed values
301    ///
302    /// # Examples
303    ///
304    /// ```
305    /// use flag_rs::flag::Flag;
306    ///
307    /// let flag = Flag::choice("format", &["json", "yaml", "xml"]);
308    /// ```
309    #[must_use]
310    pub fn choice(name: impl Into<String>, choices: &[&str]) -> Self {
311        let choices: Vec<String> = choices.iter().map(|&s| s.to_string()).collect();
312        Self::new(name).value_type(FlagType::Choice(choices))
313    }
314
315    /// Creates a new range flag with min and max values
316    ///
317    /// # Examples
318    ///
319    /// ```
320    /// use flag_rs::flag::Flag;
321    ///
322    /// let flag = Flag::range("workers", 1, 16);
323    /// ```
324    #[must_use]
325    pub fn range(name: impl Into<String>, min: i64, max: i64) -> Self {
326        Self::new(name).value_type(FlagType::Range(min, max))
327    }
328
329    /// Creates a new file flag
330    ///
331    /// # Examples
332    ///
333    /// ```
334    /// use flag_rs::flag::Flag;
335    ///
336    /// let flag = Flag::file("config");
337    /// ```
338    #[must_use]
339    pub fn file(name: impl Into<String>) -> Self {
340        Self::new(name).value_type(FlagType::File)
341    }
342
343    /// Creates a new directory flag
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use flag_rs::flag::Flag;
349    ///
350    /// let flag = Flag::directory("output");
351    /// ```
352    #[must_use]
353    pub fn directory(name: impl Into<String>) -> Self {
354        Self::new(name).value_type(FlagType::Directory)
355    }
356
357    /// Sets the short name for this flag
358    ///
359    /// # Examples
360    ///
361    /// ```
362    /// use flag_rs::flag::Flag;
363    ///
364    /// let flag = Flag::new("verbose").short('v');
365    /// assert_eq!(flag.short, Some('v'));
366    /// ```
367    #[must_use]
368    pub const fn short(mut self, short: char) -> Self {
369        self.short = Some(short);
370        self
371    }
372
373    /// Sets the usage description for this flag
374    ///
375    /// # Examples
376    ///
377    /// ```
378    /// use flag_rs::flag::Flag;
379    ///
380    /// let flag = Flag::new("verbose").usage("Enable verbose output");
381    /// assert_eq!(flag.usage, "Enable verbose output");
382    /// ```
383    #[must_use]
384    pub fn usage(mut self, usage: impl Into<String>) -> Self {
385        self.usage = usage.into();
386        self
387    }
388
389    /// Sets the default value for this flag
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// use flag_rs::flag::{Flag, FlagValue};
395    ///
396    /// let flag = Flag::new("count").default(FlagValue::Int(10));
397    /// assert_eq!(flag.default, Some(FlagValue::Int(10)));
398    /// ```
399    #[must_use]
400    pub fn default(mut self, value: FlagValue) -> Self {
401        self.default = Some(value);
402        self
403    }
404
405    /// Sets a default boolean value
406    ///
407    /// # Examples
408    ///
409    /// ```
410    /// use flag_rs::flag::{Flag, FlagValue};
411    ///
412    /// let flag = Flag::bool("verbose").default_bool(true);
413    /// assert_eq!(flag.default, Some(FlagValue::Bool(true)));
414    /// ```
415    #[must_use]
416    pub fn default_bool(self, value: bool) -> Self {
417        self.default(FlagValue::Bool(value))
418    }
419
420    /// Sets a default string value
421    ///
422    /// # Examples
423    ///
424    /// ```
425    /// use flag_rs::flag::{Flag, FlagValue};
426    ///
427    /// let flag = Flag::string("name").default_str("anonymous");
428    /// assert_eq!(flag.default, Some(FlagValue::String("anonymous".to_string())));
429    /// ```
430    #[must_use]
431    pub fn default_str(self, value: &str) -> Self {
432        self.default(FlagValue::String(value.to_string()))
433    }
434
435    /// Sets a default integer value
436    ///
437    /// # Examples
438    ///
439    /// ```
440    /// use flag_rs::flag::{Flag, FlagValue};
441    ///
442    /// let flag = Flag::int("port").default_int(8080);
443    /// assert_eq!(flag.default, Some(FlagValue::Int(8080)));
444    /// ```
445    #[must_use]
446    pub fn default_int(self, value: i64) -> Self {
447        self.default(FlagValue::Int(value))
448    }
449
450    /// Sets a default float value
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use flag_rs::flag::{Flag, FlagValue};
456    ///
457    /// let flag = Flag::float("ratio").default_float(0.5);
458    /// assert_eq!(flag.default, Some(FlagValue::Float(0.5)));
459    /// ```
460    #[must_use]
461    pub fn default_float(self, value: f64) -> Self {
462        self.default(FlagValue::Float(value))
463    }
464
465    /// Marks this flag as required
466    ///
467    /// # Examples
468    ///
469    /// ```
470    /// use flag_rs::flag::Flag;
471    ///
472    /// let flag = Flag::new("name").required();
473    /// assert!(flag.required);
474    /// ```
475    #[must_use]
476    pub const fn required(mut self) -> Self {
477        self.required = true;
478        self
479    }
480
481    /// Sets the value type for this flag
482    ///
483    /// # Examples
484    ///
485    /// ```
486    /// use flag_rs::flag::{Flag, FlagType};
487    ///
488    /// let flag = Flag::new("count").value_type(FlagType::Int);
489    /// ```
490    #[must_use]
491    pub fn value_type(mut self, value_type: FlagType) -> Self {
492        self.value_type = value_type;
493        self
494    }
495
496    /// Adds a constraint to this flag
497    ///
498    /// # Examples
499    ///
500    /// ```
501    /// use flag_rs::flag::{Flag, FlagConstraint};
502    ///
503    /// let flag = Flag::new("ssl")
504    ///     .constraint(FlagConstraint::RequiredIf("port".to_string()))
505    ///     .constraint(FlagConstraint::ConflictsWith(vec!["no-ssl".to_string()]));
506    /// ```
507    #[must_use]
508    pub fn constraint(mut self, constraint: FlagConstraint) -> Self {
509        self.constraints.push(constraint);
510        self
511    }
512
513    /// Sets a completion function for this flag's values
514    ///
515    /// # Arguments
516    ///
517    /// * `completion` - A function that generates completions for flag values
518    ///
519    /// # Examples
520    ///
521    /// ```
522    /// use flag_rs::flag::Flag;
523    /// use flag_rs::completion::CompletionResult;
524    ///
525    /// let flag = Flag::file("config")
526    ///     .completion(|ctx, prefix| {
527    ///         // In a real application, you might list config files
528    ///         let configs = vec!["default.conf", "production.conf", "test.conf"];
529    ///         Ok(CompletionResult::new().extend(
530    ///             configs.into_iter()
531    ///                 .filter(|c| c.starts_with(prefix))
532    ///                 .map(String::from)
533    ///         ))
534    ///     });
535    /// ```
536    #[must_use]
537    pub fn completion<F>(mut self, completion: F) -> Self
538    where
539        F: Fn(&crate::Context, &str) -> Result<CompletionResult> + Send + Sync + 'static,
540    {
541        self.completion = Some(Box::new(completion));
542        self
543    }
544}
545
546impl Clone for Flag {
547    fn clone(&self) -> Self {
548        Self {
549            name: self.name.clone(),
550            short: self.short,
551            usage: self.usage.clone(),
552            default: self.default.clone(),
553            required: self.required,
554            value_type: self.value_type.clone(),
555            constraints: self.constraints.clone(),
556            completion: None, // Don't clone the completion function
557        }
558    }
559}
560
561impl Flag {
562    /// Parses a string value according to this flag's type
563    ///
564    /// # Arguments
565    ///
566    /// * `input` - The string value to parse
567    ///
568    /// # Returns
569    ///
570    /// Returns the parsed `FlagValue` on success
571    ///
572    /// # Errors
573    ///
574    /// Returns `Error::FlagParsing` if the input cannot be parsed as the expected type
575    ///
576    /// # Examples
577    ///
578    /// ```
579    /// use flag_rs::flag::{Flag, FlagType, FlagValue};
580    ///
581    /// let int_flag = Flag::new("count").value_type(FlagType::Int);
582    /// match int_flag.parse_value("42") {
583    ///     Ok(FlagValue::Int(n)) => assert_eq!(n, 42),
584    ///     _ => panic!("Expected Int value"),
585    /// }
586    ///
587    /// let bool_flag = Flag::new("verbose").value_type(FlagType::Bool);
588    /// match bool_flag.parse_value("true") {
589    ///     Ok(FlagValue::Bool(b)) => assert!(b),
590    ///     _ => panic!("Expected Bool value"),
591    /// }
592    /// ```
593    pub fn parse_value(&self, input: &str) -> Result<FlagValue> {
594        match &self.value_type {
595            FlagType::String => Ok(FlagValue::String(input.to_string())),
596            FlagType::Bool => match input.to_lowercase().as_str() {
597                "true" | "t" | "1" | "yes" | "y" => Ok(FlagValue::Bool(true)),
598                "false" | "f" | "0" | "no" | "n" => Ok(FlagValue::Bool(false)),
599                _ => Err(Error::flag_parsing_with_suggestions(
600                    format!("Invalid boolean value: '{input}'"),
601                    self.name.clone(),
602                    vec![
603                        "true, false".to_string(),
604                        "yes, no".to_string(),
605                        "1, 0".to_string(),
606                    ],
607                )),
608            },
609            FlagType::Int => input.parse::<i64>().map(FlagValue::Int).map_err(|_| {
610                Error::flag_parsing_with_suggestions(
611                    format!("Invalid integer value: '{input}'"),
612                    self.name.clone(),
613                    vec!["a whole number (e.g., 42, -10, 0)".to_string()],
614                )
615            }),
616            FlagType::Float => input.parse::<f64>().map(FlagValue::Float).map_err(|_| {
617                Error::flag_parsing_with_suggestions(
618                    format!("Invalid float value: '{input}'"),
619                    self.name.clone(),
620                    vec!["a decimal number (e.g., 3.14, -0.5, 1e10)".to_string()],
621                )
622            }),
623            FlagType::StringSlice | FlagType::StringArray => {
624                Ok(FlagValue::StringSlice(vec![input.to_string()]))
625            }
626            FlagType::Choice(choices) => {
627                if choices.contains(&input.to_string()) {
628                    Ok(FlagValue::String(input.to_string()))
629                } else {
630                    Err(Error::flag_parsing_with_suggestions(
631                        format!("Invalid choice: '{input}'"),
632                        self.name.clone(),
633                        choices.clone(),
634                    ))
635                }
636            }
637            FlagType::Range(min, max) => {
638                let value = input.parse::<i64>().map_err(|_| {
639                    Error::flag_parsing_with_suggestions(
640                        format!("Invalid integer value: '{input}'"),
641                        self.name.clone(),
642                        vec![format!("a number between {min} and {max}")],
643                    )
644                })?;
645                if value >= *min && value <= *max {
646                    Ok(FlagValue::Int(value))
647                } else {
648                    Err(Error::flag_parsing_with_suggestions(
649                        format!("Value {value} is out of range"),
650                        self.name.clone(),
651                        vec![format!("a number between {min} and {max} (inclusive)")],
652                    ))
653                }
654            }
655            FlagType::File => {
656                use std::path::Path;
657                let path = Path::new(input);
658                if path.exists() && path.is_file() {
659                    Ok(FlagValue::String(input.to_string()))
660                } else if !path.exists() {
661                    Err(Error::flag_parsing_with_suggestions(
662                        format!("File not found: '{input}'"),
663                        self.name.clone(),
664                        vec!["path to an existing file".to_string()],
665                    ))
666                } else {
667                    Err(Error::flag_parsing_with_suggestions(
668                        format!("Path exists but is not a file: '{input}'"),
669                        self.name.clone(),
670                        vec!["path to a regular file (not a directory)".to_string()],
671                    ))
672                }
673            }
674            FlagType::Directory => {
675                use std::path::Path;
676                let path = Path::new(input);
677                if path.exists() && path.is_dir() {
678                    Ok(FlagValue::String(input.to_string()))
679                } else if !path.exists() {
680                    Err(Error::flag_parsing_with_suggestions(
681                        format!("Directory not found: '{input}'"),
682                        self.name.clone(),
683                        vec!["path to an existing directory".to_string()],
684                    ))
685                } else {
686                    Err(Error::flag_parsing_with_suggestions(
687                        format!("Path exists but is not a directory: '{input}'"),
688                        self.name.clone(),
689                        vec!["path to a directory (not a file)".to_string()],
690                    ))
691                }
692            }
693        }
694    }
695
696    /// Validates this flag's constraints against the provided flags
697    ///
698    /// # Arguments
699    ///
700    /// * `flag_name` - The name of this flag
701    /// * `provided_flags` - Set of flag names that were provided
702    ///
703    /// # Returns
704    ///
705    /// Returns `Ok(())` if all constraints are satisfied
706    ///
707    /// # Errors
708    ///
709    /// Returns `Error::FlagParsing` if any constraint is violated
710    pub fn validate_constraints(
711        &self,
712        flag_name: &str,
713        provided_flags: &HashSet<String>,
714    ) -> Result<()> {
715        for constraint in &self.constraints {
716            match constraint {
717                FlagConstraint::RequiredIf(other_flag) => {
718                    if provided_flags.contains(other_flag) && !provided_flags.contains(flag_name) {
719                        return Err(Error::flag_parsing_with_suggestions(
720                            format!(
721                                "Flag '--{flag_name}' is required when '--{other_flag}' is set"
722                            ),
723                            flag_name.to_string(),
724                            vec![format!("add --{flag_name} <value>")],
725                        ));
726                    }
727                }
728                FlagConstraint::ConflictsWith(conflicting_flags) => {
729                    if provided_flags.contains(flag_name) {
730                        for conflict in conflicting_flags {
731                            if provided_flags.contains(conflict) {
732                                return Err(Error::flag_parsing_with_suggestions(
733                                    format!("Flag '--{flag_name}' conflicts with '--{conflict}'"),
734                                    flag_name.to_string(),
735                                    vec![format!(
736                                        "use either --{flag_name} or --{conflict}, not both"
737                                    )],
738                                ));
739                            }
740                        }
741                    }
742                }
743                FlagConstraint::Requires(required_flags) => {
744                    if provided_flags.contains(flag_name) {
745                        for required in required_flags {
746                            if !provided_flags.contains(required) {
747                                return Err(Error::flag_parsing_with_suggestions(
748                                    format!(
749                                        "Flag '--{flag_name}' requires '--{required}' to be set"
750                                    ),
751                                    flag_name.to_string(),
752                                    vec![format!("add --{required} <value>")],
753                                ));
754                            }
755                        }
756                    }
757                }
758            }
759        }
760        Ok(())
761    }
762}
763
764#[cfg(test)]
765mod tests {
766    use super::*;
767    #[allow(clippy::approx_constant)]
768    const PI: f64 = 3.14;
769
770    #[test]
771    fn test_flag_value_conversions() {
772        let string_val = FlagValue::String("hello".to_string());
773        assert_eq!(string_val.as_string().unwrap(), "hello");
774        assert!(string_val.as_bool().is_err());
775
776        let bool_val = FlagValue::Bool(true);
777        assert!(bool_val.as_bool().unwrap());
778        assert!(bool_val.as_string().is_err());
779
780        let int_val = FlagValue::Int(42);
781        assert_eq!(int_val.as_int().unwrap(), 42);
782        assert!(int_val.as_float().is_err());
783
784        let float_val = FlagValue::Float(PI);
785        assert!((float_val.as_float().unwrap() - PI).abs() < f64::EPSILON);
786        assert!(float_val.as_int().is_err());
787
788        let slice_val = FlagValue::StringSlice(vec!["a".to_string(), "b".to_string()]);
789        assert_eq!(
790            slice_val.as_string_slice().unwrap(),
791            &vec!["a".to_string(), "b".to_string()]
792        );
793        assert!(slice_val.as_string().is_err());
794    }
795
796    #[test]
797    fn test_flag_parsing() {
798        let string_flag = Flag::new("name").value_type(FlagType::String);
799        assert_eq!(
800            string_flag.parse_value("test").unwrap(),
801            FlagValue::String("test".to_string())
802        );
803
804        let bool_flag = Flag::new("verbose").value_type(FlagType::Bool);
805        assert_eq!(
806            bool_flag.parse_value("true").unwrap(),
807            FlagValue::Bool(true)
808        );
809        assert_eq!(
810            bool_flag.parse_value("false").unwrap(),
811            FlagValue::Bool(false)
812        );
813        assert_eq!(bool_flag.parse_value("1").unwrap(), FlagValue::Bool(true));
814        assert_eq!(bool_flag.parse_value("0").unwrap(), FlagValue::Bool(false));
815        assert_eq!(bool_flag.parse_value("yes").unwrap(), FlagValue::Bool(true));
816        assert_eq!(bool_flag.parse_value("no").unwrap(), FlagValue::Bool(false));
817        assert!(bool_flag.parse_value("invalid").is_err());
818
819        let int_flag = Flag::new("count").value_type(FlagType::Int);
820        assert_eq!(int_flag.parse_value("42").unwrap(), FlagValue::Int(42));
821        assert_eq!(int_flag.parse_value("-10").unwrap(), FlagValue::Int(-10));
822        assert!(int_flag.parse_value("not_a_number").is_err());
823
824        let float_flag = Flag::new("ratio").value_type(FlagType::Float);
825        assert_eq!(
826            float_flag.parse_value("3.14").unwrap(),
827            FlagValue::Float(PI)
828        );
829        assert_eq!(
830            float_flag.parse_value("-2.5").unwrap(),
831            FlagValue::Float(-2.5)
832        );
833        assert!(float_flag.parse_value("not_a_float").is_err());
834    }
835
836    #[test]
837    fn test_flag_builder() {
838        let flag = Flag::new("verbose")
839            .short('v')
840            .usage("Enable verbose output")
841            .default(FlagValue::Bool(false))
842            .value_type(FlagType::Bool);
843
844        assert_eq!(flag.name, "verbose");
845        assert_eq!(flag.short, Some('v'));
846        assert_eq!(flag.usage, "Enable verbose output");
847        assert_eq!(flag.default, Some(FlagValue::Bool(false)));
848        assert!(!flag.required);
849    }
850
851    #[test]
852    fn test_choice_flag() {
853        let choice_flag = Flag::new("environment").value_type(FlagType::Choice(vec![
854            "dev".to_string(),
855            "staging".to_string(),
856            "prod".to_string(),
857        ]));
858
859        assert_eq!(
860            choice_flag.parse_value("dev").unwrap(),
861            FlagValue::String("dev".to_string())
862        );
863        assert_eq!(
864            choice_flag.parse_value("staging").unwrap(),
865            FlagValue::String("staging".to_string())
866        );
867        assert!(choice_flag.parse_value("test").is_err());
868    }
869
870    #[test]
871    fn test_range_flag() {
872        let range_flag = Flag::new("port").value_type(FlagType::Range(1024, 65535));
873
874        assert_eq!(
875            range_flag.parse_value("8080").unwrap(),
876            FlagValue::Int(8080)
877        );
878        assert_eq!(
879            range_flag.parse_value("1024").unwrap(),
880            FlagValue::Int(1024)
881        );
882        assert_eq!(
883            range_flag.parse_value("65535").unwrap(),
884            FlagValue::Int(65535)
885        );
886        assert!(range_flag.parse_value("80").is_err());
887        assert!(range_flag.parse_value("70000").is_err());
888        assert!(range_flag.parse_value("not_a_number").is_err());
889    }
890
891    #[test]
892    fn test_file_flag() {
893        use std::fs::File;
894        use std::io::Write;
895        let temp_file = "test_file_flag.tmp";
896        let mut file = File::create(temp_file).unwrap();
897        writeln!(file, "test").unwrap();
898
899        let file_flag = Flag::new("config").value_type(FlagType::File);
900        assert_eq!(
901            file_flag.parse_value(temp_file).unwrap(),
902            FlagValue::String(temp_file.to_string())
903        );
904        assert!(file_flag.parse_value("nonexistent.file").is_err());
905
906        std::fs::remove_file(temp_file).unwrap();
907    }
908
909    #[test]
910    fn test_directory_flag() {
911        let dir_flag = Flag::new("output").value_type(FlagType::Directory);
912
913        // Test with current directory
914        assert_eq!(
915            dir_flag.parse_value(".").unwrap(),
916            FlagValue::String(".".to_string())
917        );
918
919        // Test with src directory (should exist in the project)
920        assert_eq!(
921            dir_flag.parse_value("src").unwrap(),
922            FlagValue::String("src".to_string())
923        );
924
925        assert!(dir_flag.parse_value("nonexistent_directory").is_err());
926    }
927
928    #[test]
929    fn test_string_array_flag() {
930        let array_flag = Flag::new("tags").value_type(FlagType::StringArray);
931
932        assert_eq!(
933            array_flag.parse_value("tag1").unwrap(),
934            FlagValue::StringSlice(vec!["tag1".to_string()])
935        );
936    }
937
938    #[test]
939    fn test_flag_constraints() {
940        let mut provided_flags = HashSet::new();
941
942        // Test RequiredIf constraint
943        let ssl_flag = Flag::new("ssl").constraint(FlagConstraint::RequiredIf("port".to_string()));
944
945        // Should pass when port flag is not set
946        assert!(
947            ssl_flag
948                .validate_constraints("ssl", &provided_flags)
949                .is_ok()
950        );
951
952        // Should fail when port is set but ssl is not
953        provided_flags.insert("port".to_string());
954        assert!(
955            ssl_flag
956                .validate_constraints("ssl", &provided_flags)
957                .is_err()
958        );
959
960        // Should pass when both are set
961        provided_flags.insert("ssl".to_string());
962        assert!(
963            ssl_flag
964                .validate_constraints("ssl", &provided_flags)
965                .is_ok()
966        );
967
968        // Test ConflictsWith constraint
969        let encrypt_flag = Flag::new("encrypt").constraint(FlagConstraint::ConflictsWith(vec![
970            "no-encrypt".to_string(),
971        ]));
972
973        provided_flags.clear();
974        provided_flags.insert("encrypt".to_string());
975        assert!(
976            encrypt_flag
977                .validate_constraints("encrypt", &provided_flags)
978                .is_ok()
979        );
980
981        provided_flags.insert("no-encrypt".to_string());
982        assert!(
983            encrypt_flag
984                .validate_constraints("encrypt", &provided_flags)
985                .is_err()
986        );
987
988        // Test Requires constraint
989        let output_flag =
990            Flag::new("output").constraint(FlagConstraint::Requires(vec!["format".to_string()]));
991
992        provided_flags.clear();
993        provided_flags.insert("output".to_string());
994        assert!(
995            output_flag
996                .validate_constraints("output", &provided_flags)
997                .is_err()
998        );
999
1000        provided_flags.insert("format".to_string());
1001        assert!(
1002            output_flag
1003                .validate_constraints("output", &provided_flags)
1004                .is_ok()
1005        );
1006    }
1007}