azalia_config_macros/lib.rs
1// 🐻❄️🪚 azalia: Noelware's Rust commons library.
2// Copyright (c) 2024-2025 Noelware, LLC. <team@noelware.org>
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy
5// of this software and associated documentation files (the "Software"), to deal
6// in the Software without restriction, including without limitation the rights
7// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8// copies of the Software, and to permit persons to whom the Software is
9// furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in all
12// copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20// SOFTWARE.
21
22#![doc(html_logo_url = "https://cdn.floofy.dev/images/trans.png")]
23#![doc(html_favicon_url = "https://cdn.floofy.dev/images/trans.png")]
24#![cfg_attr(any(noeldoc, docsrs), feature(doc_cfg))]
25
26mod merge;
27
28#[cfg(feature = "unstable")]
29mod tryfromenv;
30
31use proc_macro::TokenStream;
32use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput};
33
34/// Procedural macro to implement the [**`Merge`**] trait from `azalia::config`'s `merge` module.
35///
36/// [**`Merge`**]: trait.Merge.html
37///
38/// ## Example
39/// > **NOTE**: This will require the `macros` feature for `azalia_config` or `config+macros` for
40/// > the `azalia` crate.
41///
42/// ```ignore
43/// # mod azalia {
44/// # mod config {
45/// # mod merge {
46/// # pub use azalia_config_macros::Merge;
47/// # }
48/// # }
49/// # }
50/// #
51/// use azalia::config::merge::Merge;
52///
53/// #[derive(Debug, Merge, Default, PartialEq)]
54/// pub struct Config {
55/// pub a: String,
56///
57/// #[merge(skip)]
58/// pub b: bool,
59///
60/// #[merge(strategy = "i32::merge")]
61/// pub c: i32,
62/// }
63///
64/// let mut config = Config::default();
65/// assert_eq!(config, Config { a: String::default(), b: false, c: 0i32 });
66///
67/// config.merge(Config { a: "hello world".into(), b: true, c: 9 });
68/// assert_eq!(config, Config { a: "hello world".into(), b: false, c: 42 });
69///
70/// // strategies can be regular functions that will expand
71/// // to a fn that implements `(&mut self, Self)`
72/// mod i32 {
73/// pub fn merge(lhs: &mut i32, rhs: i32) {
74/// *lhs = 42;
75/// }
76/// }
77/// ```
78#[allow(non_snake_case)]
79#[proc_macro_derive(Merge, attributes(merge))]
80pub fn Merge(input: TokenStream) -> TokenStream {
81 let derive = parse_macro_input!(input as DeriveInput);
82 match &derive.data {
83 Data::Struct(s) => merge::expand_struct(&derive, &s.fields).into(),
84 Data::Enum(e) => syn::Error::new(e.enum_token.span(), "merge trait for enumerations are not supported")
85 .into_compile_error()
86 .into(),
87
88 Data::Union(u) => syn::Error::new(u.union_token.span(), "merge trait for unions will never be supported")
89 .into_compile_error()
90 .into(),
91 }
92}
93
94/// Procedural macro to implement [`TryFromEnv`] for `struct`s.
95///
96/// [`TryFromEnv`]: trait.TryFromEnv.html
97///
98/// ## Example
99/// ```ignore
100/// # mod azalia {
101/// # mod config {
102/// # mod env {
103/// # pub use azalia_config_macros::TryFromEnv;
104/// # }
105/// # }
106/// # }
107/// #
108/// use azalia::config::env::TryFromEnv;
109/// use std::error::Error;
110///
111/// #[derive(TryFromEnv)]
112/// #[env(error = Box<dyn Error>, prefix = "APP_")]
113/// pub struct Config {
114/// #[env(var = "A")]
115/// pub a: String,
116/// }
117///
118/// # fn main() -> Result<(), Box<dyn Error>> {
119/// # let _ = unsafe { std::env::set_var("APP_A", "apple") };
120/// # assert!(std::env::var("APP_A").is_ok());
121/// #
122/// // assume that APP_A=apple
123/// let config = Config::try_from_env()?;
124/// assert!(config.a, "apple");
125/// #
126/// # let _ = unsafe { std::env::remove_var("APP_A") };
127/// # assert!(std::env::var("APP_A").is_err());
128/// #
129/// # Ok(())
130/// # }
131/// ```
132#[allow(non_snake_case)]
133#[cfg(feature = "unstable")]
134#[cfg_attr(any(noeldoc, docsrs), doc(cfg(feature = "unstable")))]
135#[proc_macro_derive(TryFromEnv, attributes(env))]
136pub fn TryFromEnv(input: TokenStream) -> TokenStream {
137 // let derive = parse_macro_input!(input as DeriveInput);
138 // match &derive.data {
139 // Data::Struct(s) => tryfromenv::expand_struct(&derive, &s.fields)
140 // .unwrap_or_else(syn::Error::into_compile_error)
141 // .into(),
142
143 // Data::Enum(_) => syn::Error::new(derive.span(), "TryFromEnv trait for enumerations are not supported")
144 // .into_compile_error()
145 // .into(),
146
147 // Data::Union(_) => syn::Error::new(derive.span(), "TryFromEnv trait for unions will never be supported")
148 // .into_compile_error()
149 // .into(),
150 // }
151
152 syn::Error::new(
153 proc_macro2::Span::call_site(),
154 "`#[derive(TryFromEnv)]` is not implemented at this time",
155 )
156 .into_compile_error()
157 .into()
158}