cfg_boost 1.0.0

Revamped syntax and macros to easily manage all #[cfg] parameters in one package. Compatible with stable toolchain and no dependencies. See README / Homepage for more details.
Documentation
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/67743099?v=4")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/67743099?v=4")]
//! <div style="float:right;width:200px;height:80px;"><iframe src="https://github.com/sponsors/NickelAngeStudio/button" title="Sponsor NickelAngeStudio" height="32" width="200" style=" border: 0; border-radius: 6px;"></iframe><a href="https://github.com/NickelAngeStudio/cfg_boost/wiki"><button style="width:200px;height:32px;background-color: #1f883d;border: none;color: white;padding: 0px;text-align: center;border-radius: 6px;text-decoration: none;display: inline-block;font-size: 16px;margin: 0px;">Wiki</button></a></div>
//! 
//! cfg_boost provides a [revamped syntax and macros](https://github.com/NickelAngeStudio/cfg_boost/wiki/Syntax) 
//! to easily manage all `#[cfg]` [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
//! predicates and parameters in one package.
//! 
//! See [features](https://github.com/NickelAngeStudio/cfg_boost/wiki/Features) to get the full list of features like aliases, attributes, automatic dependency tag documentation and more.
//!
//! ## Example
//! **Transform this :**
//! ```ignore
//! #[cfg(any(doc, any(target_os = "linux", target_os = "macos", target_os = "windows")))]
//! #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))))]
//! pub mod desktop_mod;
//! 
//! #[cfg(any(doc, any(target_os = "linux", target_os = "macos", target_os = "windows")))]
//! #[cfg_attr(docsrs, doc(cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))))]
//! pub use desktop_mod::Struct as Struct;
//! 
//! #[cfg(any(doc, any(target_os = "ios", target_os = "android")))]
//! #[cfg_attr(docsrs, doc(cfg(any(target_os = "ios", target_os = "android"))))]
//! pub mod mobile_mod;
//! 
//! #[cfg(any(doc, any(target_os = "ios", target_os = "android")))]
//! #[cfg_attr(docsrs, doc(cfg(any(target_os = "ios", target_os = "android"))))]
//! pub use mobile_mod::Struct1 as Struct1;
//! 
//! #[cfg(any(doc, any(target_os = "ios", target_os = "android")))]
//! #[cfg_attr(docsrs, doc(cfg(any(target_os = "ios", target_os = "android"))))]
//! pub use mobile_mod::Struct2 as Struct2;
//! 
//! #[cfg(any(doc, any(target_os = "ios", target_os = "android")))]
//! #[cfg_attr(docsrs, doc(cfg(any(target_os = "ios", target_os = "android"))))]
//! pub fn mobile_only_fn() {}
//! ```
//! 
//! **Into this :**
//! ```ignore
//! target_cfg!{
//!     desktop => {
//!         pub mod desktop_mod;
//!         pub use desktop_mod::Struct as Struct;
//!     },
//!     mobile => {
//!         pub mod mobile_mod;
//!         pub use mobile_mod::Struct1 as Struct1;
//!         pub use mobile_mod::Struct2 as Struct2;
//!         pub fn mobile_only_fn() {}
//!     }
//! }
//! ```
//! 
//! [Get more examples on the wiki.](https://github.com/NickelAngeStudio/cfg_boost/wiki/Examples)
use arm::TargetArm;
use proc_macro::{TokenStream, TokenTree, Group, Delimiter};

/// Errors enumeration
mod errors;

/// config.toml fetch functions
mod config;

/// Arms structure and functions
mod arm;

/// Syntax tree
mod syntax;

/// Proc macro source enumeration to determinate matching macro source.
#[derive(Clone, Copy)]
pub(crate) enum CfgBoostMacroSource {
    /// Call come from target_cfg! macro.
    TargetMacro,

    /// Call come from match_cfg! macro.
    MatchMacro,
}

/// Procedural macro used to declare resource and item outside function.
/// 
/// ## Description
/// target_cfg! use a pattern syntax like [match](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) 
/// to define conditional compilation outside a function. One-to-many arms can be defined and contrary to [match_cfg!], **any matching arm WILL be included**
/// and not all cases are covered with a [wildcard](https://doc.rust-lang.org/reference/patterns.html#wildcard-pattern).
/// 
/// Because this behaviour is different from [match](https://doc.rust-lang.org/rust-by-example/flow_control/match.html), 
/// target_cfg! **WILL PANIC** if used in function (use [match_cfg!] inside function instead).
/// 
/// **target_cfg! has no runtime cost.**
/// 
/// ## Syntax
/// ```ignore
/// target_cfg!{
///     !? alias* (| &)? !? value:pred* => {},+
///     #[cfg(legacy_syntax)] => {},+    // target_cfg! also support legacy syntax
/// }
/// ```
/// [More details on syntax here.](https://github.com/NickelAngeStudio/cfg_boost/wiki/Syntax)
/// 
/// ## Documentation
/// target_cfg! always wrap arm with `doc | (arm)` if `doc` is not defined in the arm (even for legacy syntax). This allow `cargo doc` to always generate documentation of each arm. 
/// This feature can be deactivated. [More details here](https://github.com/NickelAngeStudio/cfg_boost/wiki/Documentation)
/// 
/// **BONUS :** target_cfg! can also generate those dependency tags. 
/// <img src="https://github.com/NickelAngeStudio/cfg_boost/raw/main/img/tag.png?raw=true" width="600" height="160"><br>
/// [More details here](https://github.com/NickelAngeStudio/cfg_boost/wiki/Documentation)
/// 
/// ## Example
/// **This**
/// ```ignore
/// /// This function is not for windows
/// #[cfg(any(doc, not(windows)))]
/// pub fn not_for_windows() {
/// }
/// 
/// /// This function is not for windows again
/// #[cfg(any(doc, not(windows)))]
/// pub fn not_for_windows_again() {
/// }
/// 
/// #[cfg(any(doc, all(target_arch="x86", target_feature="sse4.1")))]
/// pub fn thirty_two_bits() {
/// }
/// 
/// #[cfg(all(not(doc), any(feature="myfeature1", feature="myfeature2")))]
/// pub struct undocumented_featured {
/// }
/// 
/// #[cfg(any(target_os="ios", target_os="android"))]
/// compile_error!("Mobile not supported");
/// ```
/// **becomes**
/// ```ignore
/// target_cfg!{
///     #[cfg(not(windows))] => {     // Legacy syntax example.
///         /// This function is not for windows
///         pub fn not_for_windows() {
///         }
/// 
///         /// This function is not for windows again
///         pub fn not_for_windows_again() {
///         }
///     },
///     x86:ar & sse4.1:tf => {
///         pub fn thirty_two_bits() {
///         }
///     },
///     !doc & (myfeature1:ft | myfeature2:ft) => {
///         pub struct undocumented_featured {
///         }
///     },
///     mobile => compile_error!("Mobile not supported"),
/// }
/// ```
/// [More examples here.](https://github.com/NickelAngeStudio/cfg_boost/wiki/Examples)
#[proc_macro]
pub fn target_cfg(item: TokenStream) -> TokenStream {

    // TokenStream that accumulate content
    let mut content = TokenStream::new();

    // 1. Extract target arms
    let arms = TargetArm::extract(item.clone(), CfgBoostMacroSource::TargetMacro);

    // 2. For each arm
    for arm in arms {

        // 2.1. Split item into vector of items
        let items = syntax::split_items(arm.content.clone());

        // 2.2. For each item in vector of items
        for item in items {
            // 2.2.1. Add cfg header.
            content.extend(arm.cfg_ts.clone()); 

            // 2.2.2. Add cfg_attr
            content.extend(arm.attr_ts.clone());

            // 2.2.3. Add item to content
            content.extend(item);
        }
    }

    // 3. Return content.
    content

}


/// Procedural macro used exclusively inside a function.
/// 
/// ## Description
/// match_cfg! use a pattern syntax like [match](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) 
/// to define conditional compilation in a function.  The first matching arm is evaluated and all possible values must be covered with a [wildcard](https://doc.rust-lang.org/reference/patterns.html#wildcard-pattern).
/// 
/// This behaviour is the same as [match](https://doc.rust-lang.org/rust-by-example/flow_control/match.html), 
/// thus match_cfg! can be used inside a function (while [target_cfg!] will [panic!]).
/// 
/// **match_cfg! has no runtime cost.**
/// 
/// ## Syntax
/// ```ignore
/// match_cfg!{
///     !? alias* (| &)? !? value:pred* => {},+
///     #[cfg(legacy_syntax)] => {},+    // match_cfg! also support legacy syntax
///     _ => {}+?     // Mandatory wildcard arm
/// };?
/// ```
/// [More details on syntax here.](https://github.com/NickelAngeStudio/cfg_boost/wiki/Syntax)
/// 
/// ## Example
/// **This**
/// ```ignore
/// pub fn foo(){
///     let a = {
///         #[cfg(linux)]
///         {
///             10
///         }
///         #[cfg(windows)]
///         {
///             20
///         }
///         #[cfg(all(not(linux), not(windows)))]   // This would be a wildcard arm.
///         {
///             30
///         }
///     };
/// 
///     #[cfg(linux)]
///     {
///         println!("linux={}", a);
///     }
///     #[cfg(windows)]
///     {
///         println!("windows={}", a);
///     }
///     #[cfg(all(not(linux), not(windows)))]   // This would be a wildcard arm.
///     {
///         println!("not linux and not windows={}", a);
///     }
/// }
/// ```
/// **becomes**
/// ```ignore
/// pub fn foo(){
///     let a = match_cfg!{
///         linux => 10,
///         windows => 20,
///         _ => 30     // Last `,` is optional like match
///     };
/// 
///     match_cfg!{
///         linux => println!("linux={}", a),
///         #[cfg(windows)] => println!("windows={}", a),   // Legacy syntax example
///         _ => println!("not linux and not windows={}", a),
///     };
/// }
/// ```
/// [More examples here.](https://github.com/NickelAngeStudio/cfg_boost/wiki/Examples)
#[proc_macro]
pub fn match_cfg(item: TokenStream) -> TokenStream {

     // TokenStream that accumulate content
     let mut content = TokenStream::new();

     // 1. Extract target arms
     let arms = TargetArm::extract(item.clone(), CfgBoostMacroSource::MatchMacro);
 
     // 2. For each arm
     for arm in arms {
         // 2.1. Add cfg header.
         content.extend(arm.cfg_ts.clone()); 
 
         // 2.2. Add braced content
         content.extend(TokenStream::from(TokenTree::from(Group::new(Delimiter::Brace, arm.content.clone()))));
     }
 
     // 3. Add braces around content then return it.
     TokenStream::from(TokenTree::from(Group::new(Delimiter::Brace,content)))

}


/// Attribute macro like [cfg](https://doc.rust-lang.org/rust-by-example/attribute/cfg.html) with [simplified syntax](https://github.com/NickelAngeStudio/cfg_boost/wiki/Syntax) used for one item.
/// 
/// ## Description
/// meta_cfg work exactly like [cfg](https://doc.rust-lang.org/rust-by-example/attribute/cfg.html) but with [simplified syntax](https://github.com/NickelAngeStudio/cfg_boost/wiki/Syntax).
/// 
/// **meta_cfg has no runtime cost.**
/// 
/// ## Syntax
/// ```ignore
/// #[meta_cfg(!? alias* (| &)? !? value:pred*)]
/// item
/// 
/// #[meta_cfg(#[cfg(legacy_syntax)])]  // meta_cfg also support legacy syntax.
/// item
/// ```
/// [More details on syntax here.](https://github.com/NickelAngeStudio/cfg_boost/wiki/Syntax)
/// 
/// ## Documentation
/// meta_cfg always wrap predicate with `doc | (predicates)` if `doc` is not defined. This allow `cargo doc` to always generate documentation. 
/// This feature can be deactivated. [More details here](https://github.com/NickelAngeStudio/cfg_boost/wiki/Documentation)
/// 
/// **BONUS :** meta_cfg can also generate those dependency tags. 
/// <img src="https://github.com/NickelAngeStudio/cfg_boost/raw/main/img/tag.png?raw=true" width="600" height="160"><br>
/// [More details here](https://github.com/NickelAngeStudio/cfg_boost/wiki/Documentation)
/// 
/// ## Example
/// **This**
/// ```ignore
/// #[cfg(any(doc, any(windows, unix, target_os="macos")))]
/// pub fn foo() {}
/// ```
/// **becomes**
/// ```ignore
/// #[meta_cfg(windows | unix | macos)]
/// pub fn foo() {}
/// ```
/// [More examples here.](https://github.com/NickelAngeStudio/cfg_boost/wiki/Examples)
#[proc_macro_attribute]
pub fn meta_cfg(attr: TokenStream, item: TokenStream) -> TokenStream {

    // 1. Generate target_cfg! syntax
    let mut stream = attr;
    stream.extend(" => ".parse::<TokenStream>().unwrap());  // Add separator
    stream.extend(TokenStream::from(TokenTree::from(Group::new(Delimiter::Brace,item))));   // Add braced content

    // 2. Generate tokenstream with target_cfg! macro
    target_cfg(stream)

}