feature_gate/lib.rs
1//! Simple documented feature gates
2//!
3//! This crates provides the [`feature_gate`] and [`feature_gate_ex`]
4//! macros for simple `#[cfg(feature = "...")]` macros that are properly
5//! documented on docs.rs.
6//!
7//! ## Stable Rust
8//!
9//! Note that for it to work properly on stable Rust, the following needs to be
10//! added to `Cargo.toml` for the time being (see [Metadata for custom builds](https://docs.rs/about/metadata)):
11//!
12//! ```toml
13//! [package.metadata.docs.rs]
14//! all-features = true
15//! rustdoc-args = ["--cfg", "docsrs"]
16//! ```
17//!
18//! ## Example
19//!
20//! The `feature_gate` macro allows the specification of a single feature:
21//!
22//! ```
23//! use feature_gate::feature_gate;
24//!
25//! #[feature_gate("test")]
26//! struct FeatureGated;
27//!
28//! #[test]
29//! fn it_works() {
30//! let _ = FeatureGated {};
31//! }
32//! ```
33//!
34//! The `feature_gate_ex` macro allows the specification of a complex set of requirements:
35//!
36//! ```
37//! use feature_gate::feature_gate_ex;
38//!
39//! #[feature_gate_ex(any(test, feature = "test"))]
40//! struct FeatureGated;
41//!
42//! #[test]
43//! fn it_works() {
44//! let _ = FeatureGated {};
45//! }
46//! ```
47
48extern crate proc_macro;
49use proc_macro::TokenStream;
50
51use quote::quote;
52use syn::{parse_macro_input, DeriveInput, Meta};
53
54/// The `#[feature_gate(...)]` macro conditionally compiles the element it
55/// is applied to if the provided feature is enabled.
56///
57/// It behaves as if `cfg!(feature = ...)` is applied to the element.
58/// See [`feature_gate_ex`] if more complex configuration is required.
59///
60/// It addition, it conditionally assigns the `#[doc(cfg(...))]` attribute
61/// to help docs.rs document the feature dependency.
62/// See [doc(cfg)] for more information.
63///
64/// [doc(cfg)]: https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
65///
66/// ## Example
67///
68/// ```
69/// use feature_gate::feature_gate;
70///
71/// #[feature_gate("test")]
72/// struct Test;
73///
74/// #[test]
75/// fn it_works() {
76/// let _ = Test {};
77/// }
78/// ```
79#[proc_macro_attribute]
80pub fn feature_gate(args: TokenStream, input: TokenStream) -> TokenStream {
81 if args.is_empty() {
82 panic!("No feature requirements were specified")
83 }
84
85 // For a single-string input.
86 let args: syn::Expr = match syn::parse(args) {
87 Ok(args) => args,
88 Err(_) => panic!("Expected a single feature name as input"),
89 };
90
91 let input = parse_macro_input!(input as DeriveInput);
92
93 // See https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
94 let tokens = quote! {
95 #[cfg(feature = #args)]
96 #[cfg_attr(any(docsrs /*, dox */), doc(cfg(feature = #args)))]
97 #input
98 };
99
100 tokens.into()
101}
102
103/// The `#[feature_gate_ex(...)]` macro conditionally compiles the element it
104/// is applied to if the provided configureation is enabled.
105///
106/// It behaves as if `cfg!(...)` is applied to the element. See
107/// [`feature_gate`] for a simpler version of the macro.
108///
109/// It addition, it conditionally assigns the `#[doc(cfg(...))]` attribute
110/// to help docs.rs document the feature dependency.
111/// See [doc(cfg)] for more information.
112///
113/// [doc(cfg)]: https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
114///
115/// ## Example
116///
117/// The following example fails to compile because the `test` feature
118/// is not declared:
119///
120/// ```
121/// use feature_gate::feature_gate_ex;
122///
123/// #[feature_gate_ex(test)]
124/// struct Test;
125///
126/// #[feature_gate_ex(feature = "test")]
127/// struct Test2;
128///
129/// #[feature_gate_ex(any(test, feature = "test"))]
130/// struct Test3;
131///
132/// #[test]
133/// fn it_works() {
134/// let _ = Test {};
135/// let _ = Test2 {};
136/// let _ = Test3 {};
137/// }
138/// ```
139#[proc_macro_attribute]
140pub fn feature_gate_ex(args: TokenStream, input: TokenStream) -> TokenStream {
141 if args.is_empty() {
142 panic!("No feature requirements were specified.")
143 }
144
145 let args: Meta = match syn::parse(args) {
146 Ok(args) => args,
147 Err(_) => panic!("Expected a configuration specification as input"),
148 };
149
150 let input = parse_macro_input!(input as DeriveInput);
151
152 // See https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
153 let tokens = quote! {
154 #[cfg(#args)]
155 #[cfg_attr(any(docsrs /*, dox */), doc(cfg(#args)))]
156 #input
157 };
158
159 tokens.into()
160}