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}