Skip to main content

figue_attrs/
lib.rs

1//! Attribute macros for figue CLI argument parsing.
2//!
3//! This crate provides the attribute grammar definitions for figue.
4//! It exists as a separate crate to work around Rust's restriction on
5//! accessing macro-expanded `#[macro_export]` macros by absolute paths
6//! within the same crate.
7//!
8//! Users should depend on `figue` directly, which re-exports everything
9//! from this crate.
10
11#![warn(missing_docs)]
12#![deny(unsafe_code)]
13
14extern crate self as figue_attrs;
15
16// Args extension attributes for use with #[facet(args::attr)] syntax.
17//
18// After importing `use figue as args;`, users can write:
19//   #[facet(args::positional)]
20//   #[facet(args::short = 'v')]
21//   #[facet(args::named)]
22
23// Generate args attribute grammar using the grammar DSL.
24// This generates:
25// - `Attr` enum with all args attribute variants
26// - `__attr!` macro that dispatches to attribute handlers and returns ExtensionAttr
27// - `__parse_attr!` macro for parsing (internal use)
28facet::define_attr_grammar! {
29    ns "args";
30    crate_path ::figue;
31
32    /// Args attribute types for field configuration.
33    pub enum Attr {
34        /// Marks a field as a positional argument.
35        ///
36        /// Usage: `#[facet(args::positional)]`
37        Positional,
38        /// Marks a field as a named argument.
39        ///
40        /// Usage: `#[facet(args::named)]`
41        Named,
42        /// Specifies a short flag character for the field.
43        ///
44        /// Usage: `#[facet(args::short = 'v')]` or just `#[facet(args::short)]`
45        Short(Option<char>),
46        /// Marks a field as a subcommand.
47        ///
48        /// The field type must be an enum where each variant represents a subcommand.
49        /// Variant names are converted to kebab-case for matching.
50        ///
51        /// Usage: `#[facet(args::subcommand)]`
52        Subcommand,
53        /// Marks a field as a counted flag.
54        ///
55        /// Each occurrence of the flag increments the count. Works with both short
56        /// flags (`-vvv` or `-v -v -v`) and long flags (`--verbose --verbose`).
57        /// The field type must be an integer type (u8, u16, u32, u64, usize, i8, i16, i32, i64, isize).
58        /// Uses saturating arithmetic to avoid overflow.
59        ///
60        /// Usage: `#[facet(args::named, args::short = 'v', args::counted)]`
61        Counted,
62        /// Marks a field as a layered configuration field.
63        ///
64        /// The field will be populated from merged configuration sources (CLI overrides,
65        /// environment variables, config files) in priority order: CLI > env > file > default.
66        ///
67        /// This automatically generates:
68        /// - `--{field_name} <PATH>` flag to specify config file path
69        /// - `--{field_name}.foo.bar <VALUE>` style CLI overrides
70        /// - Environment variable parsing
71        /// - Config file loading with multiple format support
72        ///
73        /// Usage: `#[facet(args::config)]`
74        Config,
75        /// Specifies the environment variable prefix for a config field.
76        ///
77        /// Must be used together with `#[facet(args::config)]`.
78        ///
79        /// Usage: `#[facet(args::env_prefix = "MYAPP")]`
80        ///
81        /// Example: `env_prefix = "MYAPP"` results in `MYAPP__FIELD__NAME` env vars.
82        EnvPrefix(Option<&'static str>),
83        /// Specifies an additional environment variable name for a config field.
84        ///
85        /// This allows a field to be read from standard environment variables
86        /// like `DATABASE_URL` or `PORT` in addition to the prefixed form.
87        ///
88        /// The prefixed env var takes priority over aliases when both are set.
89        /// Multiple aliases can be specified by using the attribute multiple times.
90        ///
91        /// Usage: `#[facet(args::env_alias = "DATABASE_URL")]`
92        ///
93        /// Example:
94        /// ```ignore
95        /// #[derive(Facet)]
96        /// struct Config {
97        ///     /// Also reads from $DATABASE_URL
98        ///     #[facet(args::env_alias = "DATABASE_URL")]
99        ///     database_url: String,
100        ///
101        ///     /// Reads from $PORT or $HTTP_PORT
102        ///     #[facet(args::env_alias = "PORT", args::env_alias = "HTTP_PORT")]
103        ///     port: u16,
104        /// }
105        /// ```
106        EnvAlias(&'static str),
107        /// Enables environment variable substitution for this field.
108        ///
109        /// When enabled, `${VAR}` patterns in the field's value will be replaced
110        /// with the corresponding environment variable. Supports default values
111        /// with `${VAR:-default}` syntax. Use `$$` to escape a literal `$`.
112        ///
113        /// Usage: `#[facet(args::env_subst)]`
114        ///
115        /// Example:
116        /// ```ignore
117        /// #[derive(Facet)]
118        /// struct Config {
119        ///     #[facet(args::env_subst)]
120        ///     data_dir: PathBuf,  // "${BASE_PATH}/data" -> "/var/myapp/data"
121        /// }
122        /// ```
123        EnvSubst,
124        /// Enables environment variable substitution for all direct fields in a struct.
125        ///
126        /// This is equivalent to adding `#[facet(args::env_subst)]` to each direct
127        /// field. Does not propagate to nested structs (mirrors `rename_all` behavior),
128        /// but does apply to flattened fields since they become direct children.
129        ///
130        /// Usage: `#[facet(args::env_subst_all)]`
131        ///
132        /// Example:
133        /// ```ignore
134        /// #[derive(Facet)]
135        /// #[facet(args::env_subst_all)]
136        /// struct Config {
137        ///     data_dir: PathBuf,   // gets env_subst
138        ///     cache_dir: PathBuf,  // gets env_subst
139        ///     nested: Other,       // nested.field does NOT get env_subst
140        /// }
141        /// ```
142        EnvSubstAll,
143        /// Marks a field as the help flag.
144        ///
145        /// When this flag is set, the driver shows help and exits with code 0.
146        /// The field should be a `bool`.
147        ///
148        /// Usage: `#[facet(figue::help)]`
149        Help,
150        /// Marks a field as the version flag.
151        ///
152        /// When this flag is set, the driver shows version and exits with code 0.
153        /// The field should be a `bool`.
154        ///
155        /// Usage: `#[facet(figue::version)]`
156        Version,
157        /// Marks a field as the completions flag.
158        ///
159        /// When this flag is set, the driver generates shell completions and exits with code 0.
160        /// The field should be `Option<Shell>`.
161        ///
162        /// Usage: `#[facet(figue::completions)]`
163        Completions,
164        /// Specifies the origin path for field extraction.
165        ///
166        /// Used in "requirements structs" to indicate which field from the
167        /// parsed config should be extracted into this field. The path uses
168        /// dot notation to navigate nested structures.
169        ///
170        /// Usage: `#[facet(args::origin = "config.database_url")]`
171        ///
172        /// Example:
173        /// ```ignore
174        /// use figue as args;
175        ///
176        /// #[derive(Facet)]
177        /// struct MigrateRequirements {
178        ///     #[facet(args::origin = "config.database_url")]
179        ///     database_url: String,  // Required for this context
180        ///
181        ///     #[facet(args::origin = "config.migrations_path")]
182        ///     migrations_path: PathBuf,
183        /// }
184        /// ```
185        Origin(&'static str),
186        /// Specifies a custom label for the help message.
187        ///
188        /// When provided, this string is used instead of the Rust type name
189        /// in the generated help (e.g., `<MY_TYPE>` instead of `<STRING>`).
190        ///
191        /// Usage: `#[facet(args::label = "MY_TYPE")]`
192        Label(&'static str),
193   }
194}