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}