1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! Simple documented feature gates
//!
//! This crates provides the [`feature_gate`] and [`feature_gate_ex`]
//! macros for simple `#[cfg(feature = "...")]` macros that are properly
//! documented on docs.rs.
//!
//! ## Stable Rust
//!
//! Note that for it to work properly on stable Rust, the following needs to be
//! added to `Cargo.toml` for the time being (see [Metadata for custom builds](https://docs.rs/about/metadata)):
//!
//! ```toml
//! [package.metadata.docs.rs]
//! all-features = true
//! rustdoc-args = ["--cfg", "docsrs"]
//! ```
//!
//! ## Example
//!
//! The `feature_gate` macro allows the specification of a single feature:
//!
//! ```
//! use feature_gate::feature_gate;
//!
//! #[feature_gate("test")]
//! struct FeatureGated;
//!
//! #[test]
//! fn it_works() {
//!     let _ = FeatureGated {};
//! }
//! ```
//!
//! The `feature_gate_ex` macro allows the specification of a complex set of requirements:
//!
//! ```
//! use feature_gate::feature_gate_ex;
//!
//! #[feature_gate_ex(any(test, feature = "test"))]
//! struct FeatureGated;
//!
//! #[test]
//! fn it_works() {
//!     let _ = FeatureGated {};
//! }
//! ```

extern crate proc_macro;
use proc_macro::TokenStream;

use quote::quote;
use syn::{parse_macro_input, DeriveInput, Meta};

/// The `#[feature_gate(...)]` macro conditionally compiles the element it
/// is applied to if the provided feature is enabled.
///
/// It behaves as if `cfg!(feature = ...)` is applied to the element.
/// See [`feature_gate_ex`] if more complex configuration is required.
///
/// It addition, it conditionally assigns the `#[doc(cfg(...))]` attribute
/// to help docs.rs document the feature dependency.
/// See [doc(cfg)] for more information.
///
/// [doc(cfg)]: https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
///
/// ## Example
///
/// ```
/// use feature_gate::feature_gate;
///
/// #[feature_gate("test")]
/// struct Test;
///
/// #[test]
/// fn it_works() {
///     let _ = Test {};
/// }
/// ```
#[proc_macro_attribute]
pub fn feature_gate(args: TokenStream, input: TokenStream) -> TokenStream {
    if args.is_empty() {
        panic!("No feature requirements were specified")
    }

    // For a single-string input.
    let args: syn::Expr = match syn::parse(args) {
        Ok(args) => args,
        Err(_) => panic!("Expected a single feature name as input"),
    };

    let input = parse_macro_input!(input as DeriveInput);

    // See https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
    let tokens = quote! {
        #[cfg(feature = #args)]
        #[cfg_attr(any(docsrs /*, dox */), doc(cfg(feature = #args)))]
        #input
    };

    tokens.into()
}

/// The `#[feature_gate_ex(...)]` macro conditionally compiles the element it
/// is applied to if the provided configureation is enabled.
///
/// It behaves as if `cfg!(...)` is applied to the element. See
/// [`feature_gate`] for a simpler version of the macro.
///
/// It addition, it conditionally assigns the `#[doc(cfg(...))]` attribute
/// to help docs.rs document the feature dependency.
/// See [doc(cfg)] for more information.
///
/// [doc(cfg)]: https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
///
/// ## Example
///
/// The following example fails to compile because the `test` feature
/// is not declared:
///
/// ```
/// use feature_gate::feature_gate_ex;
///
/// #[feature_gate_ex(test)]
/// struct Test;
///
/// #[feature_gate_ex(feature = "test")]
/// struct Test2;
///
/// #[feature_gate_ex(any(test, feature = "test"))]
/// struct Test3;
///
/// #[test]
/// fn it_works() {
///     let _ = Test {};
///     let _ = Test2 {};
///     let _ = Test3 {};
/// }
/// ```
#[proc_macro_attribute]
pub fn feature_gate_ex(args: TokenStream, input: TokenStream) -> TokenStream {
    if args.is_empty() {
        panic!("No feature requirements were specified.")
    }

    let args: Meta = match syn::parse(args) {
        Ok(args) => args,
        Err(_) => panic!("Expected a configuration specification as input"),
    };

    let input = parse_macro_input!(input as DeriveInput);

    // See https://doc.rust-lang.org/unstable-book/language-features/doc-cfg.html
    let tokens = quote! {
        #[cfg(#args)]
        #[cfg_attr(any(docsrs /*, dox */), doc(cfg(#args)))]
        #input
    };

    tokens.into()
}