Skip to main content

teamy_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        /// Specifies an additional long-form alias for a subcommand variant.
47        ///
48        /// The alias is accepted in addition to the canonical subcommand name.
49        ///
50        /// Multiple aliases can be specified by repeating the attribute.
51        ///
52        /// Usage: `#[facet(args::alias = "profiles")]`
53        Alias(&'static str),
54        /// Marks a field as a subcommand.
55        ///
56        /// The field type must be an enum where each variant represents a subcommand.
57        /// Variant names are converted to kebab-case for matching.
58        ///
59        /// Usage: `#[facet(args::subcommand)]`
60        Subcommand,
61        /// Marks a field as a counted flag.
62        ///
63        /// Each occurrence of the flag increments the count. Works with both short
64        /// flags (`-vvv` or `-v -v -v`) and long flags (`--verbose --verbose`).
65        /// The field type must be an integer type (u8, u16, u32, u64, usize, i8, i16, i32, i64, isize).
66        /// Uses saturating arithmetic to avoid overflow.
67        ///
68        /// Usage: `#[facet(args::named, args::short = 'v', args::counted)]`
69        Counted,
70        /// Marks a field as a layered configuration field.
71        ///
72        /// The field will be populated from merged configuration sources (CLI overrides,
73        /// environment variables, config files) in priority order: CLI > env > file > default.
74        ///
75        /// This automatically generates:
76        /// - `--{field_name} <PATH>` flag to specify config file path
77        /// - `--{field_name}.foo.bar <VALUE>` style CLI overrides
78        /// - Environment variable parsing
79        /// - Config file loading with multiple format support
80        ///
81        /// Usage: `#[facet(args::config)]`
82        Config,
83        /// Specifies the environment variable prefix for a config field.
84        ///
85        /// Must be used together with `#[facet(args::config)]`.
86        ///
87        /// Usage: `#[facet(args::env_prefix = "MYAPP")]`
88        ///
89        /// Example: `env_prefix = "MYAPP"` results in `MYAPP__FIELD__NAME` env vars.
90        EnvPrefix(Option<&'static str>),
91        /// Specifies an additional environment variable name for a config field.
92        ///
93        /// This allows a field to be read from standard environment variables
94        /// like `DATABASE_URL` or `PORT` in addition to the prefixed form.
95        ///
96        /// The prefixed env var takes priority over aliases when both are set.
97        /// Multiple aliases can be specified by using the attribute multiple times.
98        ///
99        /// Usage: `#[facet(args::env_alias = "DATABASE_URL")]`
100        ///
101        /// Example:
102        /// ```ignore
103        /// #[derive(Facet)]
104        /// struct Config {
105        ///     /// Also reads from $DATABASE_URL
106        ///     #[facet(args::env_alias = "DATABASE_URL")]
107        ///     database_url: String,
108        ///
109        ///     /// Reads from $PORT or $HTTP_PORT
110        ///     #[facet(args::env_alias = "PORT", args::env_alias = "HTTP_PORT")]
111        ///     port: u16,
112        /// }
113        /// ```
114        EnvAlias(&'static str),
115        /// Specifies an additional long-form CLI flag name for a named argument.
116        ///
117        /// This allows a field to keep one canonical long flag while accepting
118        /// compatibility aliases on the CLI. Multiple aliases can be specified
119        /// by using the attribute multiple times.
120        ///
121        /// Usage: `#[facet(args::long_alias = "drive-letter-pattern")]`
122        ///
123        /// Example:
124        /// ```ignore
125        /// #[derive(Facet)]
126        /// struct Args {
127        ///     #[facet(args::named, rename = "drive", args::long_alias = "drive-letter-pattern")]
128        ///     drive: Option<String>,
129        /// }
130        /// ```
131        LongAlias(&'static str),
132        /// Enables environment variable substitution for this field.
133        ///
134        /// When enabled, `${VAR}` patterns in the field's value will be replaced
135        /// with the corresponding environment variable. Supports default values
136        /// with `${VAR:-default}` syntax. Use `$$` to escape a literal `$`.
137        ///
138        /// Usage: `#[facet(args::env_subst)]`
139        ///
140        /// Example:
141        /// ```ignore
142        /// #[derive(Facet)]
143        /// struct Config {
144        ///     #[facet(args::env_subst)]
145        ///     data_dir: PathBuf,  // "${BASE_PATH}/data" -> "/var/myapp/data"
146        /// }
147        /// ```
148        EnvSubst,
149        /// Enables environment variable substitution for all direct fields in a struct.
150        ///
151        /// This is equivalent to adding `#[facet(args::env_subst)]` to each direct
152        /// field. Does not propagate to nested structs (mirrors `rename_all` behavior),
153        /// but does apply to flattened fields since they become direct children.
154        ///
155        /// Usage: `#[facet(args::env_subst_all)]`
156        ///
157        /// Example:
158        /// ```ignore
159        /// #[derive(Facet)]
160        /// #[facet(args::env_subst_all)]
161        /// struct Config {
162        ///     data_dir: PathBuf,   // gets env_subst
163        ///     cache_dir: PathBuf,  // gets env_subst
164        ///     nested: Other,       // nested.field does NOT get env_subst
165        /// }
166        /// ```
167        EnvSubstAll,
168        /// Marks a field as the help flag.
169        ///
170        /// When this flag is set, the driver shows help and exits with code 0.
171        /// The field should be a `bool`.
172        ///
173        /// Usage: `#[facet(figue::help)]`
174        Help,
175        /// Marks a field as the version flag.
176        ///
177        /// When this flag is set, the driver shows version and exits with code 0.
178        /// The field should be a `bool`.
179        ///
180        /// Usage: `#[facet(figue::version)]`
181        Version,
182        /// Marks a field as the completions flag.
183        ///
184        /// When this flag is set, the driver generates shell completions and exits with code 0.
185        /// The field should be `Option<Shell>`.
186        ///
187        /// Usage: `#[facet(figue::completions)]`
188        Completions,
189        /// Marks a field as the JSON Schema export flag.
190        ///
191        /// When this flag is set, the driver writes one JSON Schema file per config root
192        /// to the requested output directory and exits with code 0.
193        /// The field should be `Option<String>`.
194        ///
195        /// Usage: `#[facet(figue::export_jsonschemas)]`
196        ExportJsonschemas,
197        /// Specifies the origin path for field extraction.
198        ///
199        /// Used in "requirements structs" to indicate which field from the
200        /// parsed config should be extracted into this field. The path uses
201        /// dot notation to navigate nested structures.
202        ///
203        /// Usage: `#[facet(args::origin = "config.database_url")]`
204        ///
205        /// Example:
206        /// ```ignore
207        /// use figue as args;
208        ///
209        /// #[derive(Facet)]
210        /// struct MigrateRequirements {
211        ///     #[facet(args::origin = "config.database_url")]
212        ///     database_url: String,  // Required for this context
213        ///
214        ///     #[facet(args::origin = "config.migrations_path")]
215        ///     migrations_path: PathBuf,
216        /// }
217        /// ```
218        Origin(&'static str),
219        /// Specifies a custom label for the help message.
220        ///
221        /// When provided, this string is used instead of the Rust type name
222        /// in the generated help (e.g., `<MY_TYPE>` instead of `<STRING>`).
223        ///
224        /// Usage: `#[facet(args::label = "MY_TYPE")]`
225        Label(&'static str),
226   }
227}