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}