Skip to main content

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    /// Parses a string value according to this flag's type
546    ///
547    /// # Arguments
548    ///
549    /// * `input` - The string value to parse
550    ///
551    /// # Returns
552    ///
553    /// Returns the parsed `FlagValue` on success
554    ///
555    /// # Errors
556    ///
557    /// Returns `Error::FlagParsing` if the input cannot be parsed as the expected type
558    ///
559    /// # Examples
560    ///
561    /// ```
562    /// use flag_rs::flag::{Flag, FlagType, FlagValue};
563    ///
564    /// let int_flag = Flag::new("count").value_type(FlagType::Int);
565    /// match int_flag.parse_value("42") {
566    ///     Ok(FlagValue::Int(n)) => assert_eq!(n, 42),
567    ///     _ => panic!("Expected Int value"),
568    /// }
569    ///
570    /// let bool_flag = Flag::new("verbose").value_type(FlagType::Bool);
571    /// match bool_flag.parse_value("true") {
572    ///     Ok(FlagValue::Bool(b)) => assert!(b),
573    ///     _ => panic!("Expected Bool value"),
574    /// }
575    /// ```
576    pub fn parse_value(&self, input: &str) -> Result<FlagValue> {
577        match &self.value_type {
578            FlagType::String => Ok(FlagValue::String(input.to_string())),
579            FlagType::Bool => match input.to_lowercase().as_str() {
580                "true" | "t" | "1" | "yes" | "y" => Ok(FlagValue::Bool(true)),
581                "false" | "f" | "0" | "no" | "n" => Ok(FlagValue::Bool(false)),
582                _ => Err(Error::flag_parsing_with_suggestions(
583                    format!("Invalid boolean value: '{input}'"),
584                    self.name.clone(),
585                    vec![
586                        "true, false".to_string(),
587                        "yes, no".to_string(),
588                        "1, 0".to_string(),
589                    ],
590                )),
591            },
592            FlagType::Int => input.parse::<i64>().map(FlagValue::Int).map_err(|_| {
593                Error::flag_parsing_with_suggestions(
594                    format!("Invalid integer value: '{input}'"),
595                    self.name.clone(),
596                    vec!["a whole number (e.g., 42, -10, 0)".to_string()],
597                )
598            }),
599            FlagType::Float => input.parse::<f64>().map(FlagValue::Float).map_err(|_| {
600                Error::flag_parsing_with_suggestions(
601                    format!("Invalid float value: '{input}'"),
602                    self.name.clone(),
603                    vec!["a decimal number (e.g., 3.14, -0.5, 1e10)".to_string()],
604                )
605            }),
606            FlagType::StringSlice | FlagType::StringArray => {
607                Ok(FlagValue::StringSlice(vec![input.to_string()]))
608            }
609            FlagType::Choice(choices) => {
610                if choices.contains(&input.to_string()) {
611                    Ok(FlagValue::String(input.to_string()))
612                } else {
613                    Err(Error::flag_parsing_with_suggestions(
614                        format!("Invalid choice: '{input}'"),
615                        self.name.clone(),
616                        choices.clone(),
617                    ))
618                }
619            }
620            FlagType::Range(min, max) => {
621                let value = input.parse::<i64>().map_err(|_| {
622                    Error::flag_parsing_with_suggestions(
623                        format!("Invalid integer value: '{input}'"),
624                        self.name.clone(),
625                        vec![format!("a number between {min} and {max}")],
626                    )
627                })?;
628                if value >= *min && value <= *max {
629                    Ok(FlagValue::Int(value))
630                } else {
631                    Err(Error::flag_parsing_with_suggestions(
632                        format!("Value {value} is out of range"),
633                        self.name.clone(),
634                        vec![format!("a number between {min} and {max} (inclusive)")],
635                    ))
636                }
637            }
638            FlagType::File => Self::parse_path(input, &self.name, PathKind::File),
639            FlagType::Directory => Self::parse_path(input, &self.name, PathKind::Directory),
640        }
641    }
642
643    fn parse_path(input: &str, name: &str, kind: PathKind) -> Result<FlagValue> {
644        use std::path::Path;
645        let path = Path::new(input);
646        if path.exists() && kind.matches(path) {
647            Ok(FlagValue::String(input.to_string()))
648        } else if !path.exists() {
649            Err(Error::flag_parsing_with_suggestions(
650                format!("{} not found: '{input}'", kind.capitalized()),
651                name.to_string(),
652                vec![kind.not_found_suggestion().to_string()],
653            ))
654        } else {
655            Err(Error::flag_parsing_with_suggestions(
656                format!("Path exists but is not a {}: '{input}'", kind.lowercase()),
657                name.to_string(),
658                vec![kind.wrong_kind_suggestion().to_string()],
659            ))
660        }
661    }
662
663    /// Validates this flag's constraints against the provided flags
664    ///
665    /// # Arguments
666    ///
667    /// * `flag_name` - The name of this flag
668    /// * `provided_flags` - Set of flag names that were provided
669    ///
670    /// # Returns
671    ///
672    /// Returns `Ok(())` if all constraints are satisfied
673    ///
674    /// # Errors
675    ///
676    /// Returns `Error::FlagParsing` if any constraint is violated
677    pub fn validate_constraints(
678        &self,
679        flag_name: &str,
680        provided_flags: &HashSet<String>,
681    ) -> Result<()> {
682        for constraint in &self.constraints {
683            match constraint {
684                FlagConstraint::RequiredIf(other_flag) => {
685                    if provided_flags.contains(other_flag) && !provided_flags.contains(flag_name) {
686                        return Err(Error::flag_parsing_with_suggestions(
687                            format!(
688                                "Flag '--{flag_name}' is required when '--{other_flag}' is set"
689                            ),
690                            flag_name.to_string(),
691                            vec![format!("add --{flag_name} <value>")],
692                        ));
693                    }
694                }
695                FlagConstraint::ConflictsWith(conflicting_flags) => {
696                    if provided_flags.contains(flag_name) {
697                        for conflict in conflicting_flags {
698                            if provided_flags.contains(conflict) {
699                                return Err(Error::flag_parsing_with_suggestions(
700                                    format!("Flag '--{flag_name}' conflicts with '--{conflict}'"),
701                                    flag_name.to_string(),
702                                    vec![format!(
703                                        "use either --{flag_name} or --{conflict}, not both"
704                                    )],
705                                ));
706                            }
707                        }
708                    }
709                }
710                FlagConstraint::Requires(required_flags) => {
711                    if provided_flags.contains(flag_name) {
712                        for required in required_flags {
713                            if !provided_flags.contains(required) {
714                                return Err(Error::flag_parsing_with_suggestions(
715                                    format!(
716                                        "Flag '--{flag_name}' requires '--{required}' to be set"
717                                    ),
718                                    flag_name.to_string(),
719                                    vec![format!("add --{required} <value>")],
720                                ));
721                            }
722                        }
723                    }
724                }
725            }
726        }
727        Ok(())
728    }
729}
730
731impl Clone for Flag {
732    fn clone(&self) -> Self {
733        Self {
734            name: self.name.clone(),
735            short: self.short,
736            usage: self.usage.clone(),
737            default: self.default.clone(),
738            required: self.required,
739            value_type: self.value_type.clone(),
740            constraints: self.constraints.clone(),
741            completion: None, // Don't clone the completion function
742        }
743    }
744}
745
746#[derive(Clone, Copy)]
747enum PathKind {
748    File,
749    Directory,
750}
751
752impl PathKind {
753    fn matches(self, path: &std::path::Path) -> bool {
754        match self {
755            Self::File => path.is_file(),
756            Self::Directory => path.is_dir(),
757        }
758    }
759
760    const fn capitalized(self) -> &'static str {
761        match self {
762            Self::File => "File",
763            Self::Directory => "Directory",
764        }
765    }
766
767    const fn lowercase(self) -> &'static str {
768        match self {
769            Self::File => "file",
770            Self::Directory => "directory",
771        }
772    }
773
774    const fn not_found_suggestion(self) -> &'static str {
775        match self {
776            Self::File => "path to an existing file",
777            Self::Directory => "path to an existing directory",
778        }
779    }
780
781    const fn wrong_kind_suggestion(self) -> &'static str {
782        match self {
783            Self::File => "path to a regular file (not a directory)",
784            Self::Directory => "path to a directory (not a file)",
785        }
786    }
787}
788
789#[cfg(test)]
790mod tests {
791    use super::*;
792    #[allow(clippy::approx_constant)]
793    const PI: f64 = 3.14;
794
795    #[test]
796    fn test_flag_value_conversions() {
797        let string_val = FlagValue::String("hello".to_string());
798        assert_eq!(string_val.as_string().unwrap(), "hello");
799        assert!(string_val.as_bool().is_err());
800
801        let bool_val = FlagValue::Bool(true);
802        assert!(bool_val.as_bool().unwrap());
803        assert!(bool_val.as_string().is_err());
804
805        let int_val = FlagValue::Int(42);
806        assert_eq!(int_val.as_int().unwrap(), 42);
807        assert!(int_val.as_float().is_err());
808
809        let float_val = FlagValue::Float(PI);
810        assert!((float_val.as_float().unwrap() - PI).abs() < f64::EPSILON);
811        assert!(float_val.as_int().is_err());
812
813        let slice_val = FlagValue::StringSlice(vec!["a".to_string(), "b".to_string()]);
814        assert_eq!(
815            slice_val.as_string_slice().unwrap(),
816            &vec!["a".to_string(), "b".to_string()]
817        );
818        assert!(slice_val.as_string().is_err());
819    }
820
821    #[test]
822    fn test_flag_parsing() {
823        let string_flag = Flag::new("name").value_type(FlagType::String);
824        assert_eq!(
825            string_flag.parse_value("test").unwrap(),
826            FlagValue::String("test".to_string())
827        );
828
829        let bool_flag = Flag::new("verbose").value_type(FlagType::Bool);
830        assert_eq!(
831            bool_flag.parse_value("true").unwrap(),
832            FlagValue::Bool(true)
833        );
834        assert_eq!(
835            bool_flag.parse_value("false").unwrap(),
836            FlagValue::Bool(false)
837        );
838        assert_eq!(bool_flag.parse_value("1").unwrap(), FlagValue::Bool(true));
839        assert_eq!(bool_flag.parse_value("0").unwrap(), FlagValue::Bool(false));
840        assert_eq!(bool_flag.parse_value("yes").unwrap(), FlagValue::Bool(true));
841        assert_eq!(bool_flag.parse_value("no").unwrap(), FlagValue::Bool(false));
842        assert!(bool_flag.parse_value("invalid").is_err());
843
844        let int_flag = Flag::new("count").value_type(FlagType::Int);
845        assert_eq!(int_flag.parse_value("42").unwrap(), FlagValue::Int(42));
846        assert_eq!(int_flag.parse_value("-10").unwrap(), FlagValue::Int(-10));
847        assert!(int_flag.parse_value("not_a_number").is_err());
848
849        let float_flag = Flag::new("ratio").value_type(FlagType::Float);
850        assert_eq!(
851            float_flag.parse_value("3.14").unwrap(),
852            FlagValue::Float(PI)
853        );
854        assert_eq!(
855            float_flag.parse_value("-2.5").unwrap(),
856            FlagValue::Float(-2.5)
857        );
858        assert!(float_flag.parse_value("not_a_float").is_err());
859    }
860
861    #[test]
862    fn test_flag_builder() {
863        let flag = Flag::new("verbose")
864            .short('v')
865            .usage("Enable verbose output")
866            .default(FlagValue::Bool(false))
867            .value_type(FlagType::Bool);
868
869        assert_eq!(flag.name, "verbose");
870        assert_eq!(flag.short, Some('v'));
871        assert_eq!(flag.usage, "Enable verbose output");
872        assert_eq!(flag.default, Some(FlagValue::Bool(false)));
873        assert!(!flag.required);
874    }
875
876    #[test]
877    fn test_choice_flag() {
878        let choice_flag = Flag::new("environment").value_type(FlagType::Choice(vec![
879            "dev".to_string(),
880            "staging".to_string(),
881            "prod".to_string(),
882        ]));
883
884        assert_eq!(
885            choice_flag.parse_value("dev").unwrap(),
886            FlagValue::String("dev".to_string())
887        );
888        assert_eq!(
889            choice_flag.parse_value("staging").unwrap(),
890            FlagValue::String("staging".to_string())
891        );
892        assert!(choice_flag.parse_value("test").is_err());
893    }
894
895    #[test]
896    fn test_range_flag() {
897        let range_flag = Flag::new("port").value_type(FlagType::Range(1024, 65535));
898
899        assert_eq!(
900            range_flag.parse_value("8080").unwrap(),
901            FlagValue::Int(8080)
902        );
903        assert_eq!(
904            range_flag.parse_value("1024").unwrap(),
905            FlagValue::Int(1024)
906        );
907        assert_eq!(
908            range_flag.parse_value("65535").unwrap(),
909            FlagValue::Int(65535)
910        );
911        assert!(range_flag.parse_value("80").is_err());
912        assert!(range_flag.parse_value("70000").is_err());
913        assert!(range_flag.parse_value("not_a_number").is_err());
914    }
915
916    #[test]
917    fn test_file_flag() {
918        use std::fs::File;
919        use std::io::Write;
920        let temp_file = "test_file_flag.tmp";
921        let mut file = File::create(temp_file).unwrap();
922        writeln!(file, "test").unwrap();
923
924        let file_flag = Flag::new("config").value_type(FlagType::File);
925        assert_eq!(
926            file_flag.parse_value(temp_file).unwrap(),
927            FlagValue::String(temp_file.to_string())
928        );
929        assert!(file_flag.parse_value("nonexistent.file").is_err());
930
931        std::fs::remove_file(temp_file).unwrap();
932    }
933
934    #[test]
935    fn test_directory_flag() {
936        let dir_flag = Flag::new("output").value_type(FlagType::Directory);
937
938        // Test with current directory
939        assert_eq!(
940            dir_flag.parse_value(".").unwrap(),
941            FlagValue::String(".".to_string())
942        );
943
944        // Test with src directory (should exist in the project)
945        assert_eq!(
946            dir_flag.parse_value("src").unwrap(),
947            FlagValue::String("src".to_string())
948        );
949
950        assert!(dir_flag.parse_value("nonexistent_directory").is_err());
951    }
952
953    #[test]
954    fn test_string_array_flag() {
955        let array_flag = Flag::new("tags").value_type(FlagType::StringArray);
956
957        assert_eq!(
958            array_flag.parse_value("tag1").unwrap(),
959            FlagValue::StringSlice(vec!["tag1".to_string()])
960        );
961    }
962
963    #[test]
964    fn test_flag_constraints() {
965        let mut provided_flags = HashSet::new();
966
967        // Test RequiredIf constraint
968        let ssl_flag = Flag::new("ssl").constraint(FlagConstraint::RequiredIf("port".to_string()));
969
970        // Should pass when port flag is not set
971        assert!(
972            ssl_flag
973                .validate_constraints("ssl", &provided_flags)
974                .is_ok()
975        );
976
977        // Should fail when port is set but ssl is not
978        provided_flags.insert("port".to_string());
979        assert!(
980            ssl_flag
981                .validate_constraints("ssl", &provided_flags)
982                .is_err()
983        );
984
985        // Should pass when both are set
986        provided_flags.insert("ssl".to_string());
987        assert!(
988            ssl_flag
989                .validate_constraints("ssl", &provided_flags)
990                .is_ok()
991        );
992
993        // Test ConflictsWith constraint
994        let encrypt_flag = Flag::new("encrypt").constraint(FlagConstraint::ConflictsWith(vec![
995            "no-encrypt".to_string(),
996        ]));
997
998        provided_flags.clear();
999        provided_flags.insert("encrypt".to_string());
1000        assert!(
1001            encrypt_flag
1002                .validate_constraints("encrypt", &provided_flags)
1003                .is_ok()
1004        );
1005
1006        provided_flags.insert("no-encrypt".to_string());
1007        assert!(
1008            encrypt_flag
1009                .validate_constraints("encrypt", &provided_flags)
1010                .is_err()
1011        );
1012
1013        // Test Requires constraint
1014        let output_flag =
1015            Flag::new("output").constraint(FlagConstraint::Requires(vec!["format".to_string()]));
1016
1017        provided_flags.clear();
1018        provided_flags.insert("output".to_string());
1019        assert!(
1020            output_flag
1021                .validate_constraints("output", &provided_flags)
1022                .is_err()
1023        );
1024
1025        provided_flags.insert("format".to_string());
1026        assert!(
1027            output_flag
1028                .validate_constraints("output", &provided_flags)
1029                .is_ok()
1030        );
1031    }
1032}