rama-macros 0.4.0

procedural macross for rama
Documentation
//! Macros for [`rama`].
//!
//! There are no more macros for Rama. We used to have an `AsRef` one,
//! but it is recommended to either not use a macro for that anymore,
//! write one yourself or use a thirdparty crate such as `derive_more`.
//!
//! [`rama`]: https://crates.io/crates/rama
//!
//! ## Paste
//!
//! The nightly-only [`concat_idents!`] macro in the Rust standard library is
//! notoriously underpowered in that its concatenated identifiers can only refer to
//! existing items, they can never be used to define something new.
//!
//! [`concat_idents!`]: https://doc.rust-lang.org/std/macro.concat_idents.html
//!
//! This crate provides a flexible way to paste together identifiers in a macro,
//! including using pasted identifiers to define new items.
//!
//! This approach works with any Rust compiler 1.31+.
//!
//! <br>
//!
//! # Pasting identifiers
//!
//! Within the `paste!` macro, identifiers inside `[<`...`>]` are pasted
//! together to form a single identifier.
//!
//! ```
//! use rama_macros::paste;
//!
//! paste! {
//!     // Defines a const called `QRST`.
//!     const [<Q R S T>]: &str = "success!";
//! }
//!
//! assert_eq!(
//!     paste! { [<Q R S T>].len() },
//!     8,
//! );
//! ```
//!
//! <br><br>
//!
//! # More elaborate example
//!
//! The next example shows a macro that generates accessor methods for some
//! struct fields. It demonstrates how you might find it useful to bundle a
//! paste invocation inside of a macro\_rules macro.
//!
//! ```
//! use rama_macros::paste;
//!
//! macro_rules! make_a_struct_and_getters {
//!     ($name:ident { $($field:ident),* }) => {
//!         // Define a struct. This expands to:
//!         //
//!         //     pub struct S {
//!         //         a: String,
//!         //         b: String,
//!         //         c: String,
//!         //     }
//!         pub struct $name {
//!             $(
//!                 $field: String,
//!             )*
//!         }
//!
//!         // Build an impl block with getters. This expands to:
//!         //
//!         //     impl S {
//!         //         pub fn get_a(&self) -> &str { &self.a }
//!         //         pub fn get_b(&self) -> &str { &self.b }
//!         //         pub fn get_c(&self) -> &str { &self.c }
//!         //     }
//!         paste! {
//!             impl $name {
//!                 $(
//!                     pub fn [<get_ $field>](&self) -> &str {
//!                         &self.$field
//!                     }
//!                 )*
//!             }
//!         }
//!     }
//! }
//!
//! make_a_struct_and_getters!(S { a, b, c });
//!
//! fn call_some_getters(s: &S) -> bool {
//!     s.get_a() == s.get_b() && s.get_c().is_empty()
//! }
//! #
//! # fn main() {}
//! ```
//!
//! <br><br>
//!
//! # Case conversion
//!
//! Use `$var:lower` or `$var:upper` in the segment list to convert an
//! interpolated segment to lower- or uppercase as part of the paste. For
//! example, `[<ld_ $reg:lower _expr>]` would paste to `ld_bc_expr` if invoked
//! with $reg=`Bc`.
//!
//! Use `$var:snake` to convert CamelCase input to snake\_case.
//! Use `$var:camel` to convert snake\_case to CamelCase.
//! These compose, so for example `$var:snake:upper` would give you SCREAMING\_CASE.
//!
//! The precise Unicode conversions are as defined by [`str::to_lowercase`] and
//! [`str::to_uppercase`].
//!
//! [`str::to_lowercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_lowercase
//! [`str::to_uppercase`]: https://doc.rust-lang.org/std/primitive.str.html#method.to_uppercase
//!
//! <br>
//!
//! # Pasting documentation strings
//!
//! Within the `paste!` macro, arguments to a #\[doc ...\] attribute are
//! implicitly concatenated together to form a coherent documentation string.
//!
//! ```
//! use rama_macros::paste;
//!
//! macro_rules! method_new {
//!     ($ret:ident) => {
//!         paste! {
//!             #[doc = "Create a new `" $ret "` object."]
//!             pub fn new() -> $ret { todo!() }
//!         }
//!     };
//! }
//!
//! pub struct Paste {}
//!
//! method_new!(Paste);  // expands to #[doc = "Create a new `Paste` object"]
//! ```

#![doc(
    html_favicon_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
)]
#![doc(
    html_logo_url = "https://raw.githubusercontent.com/plabayo/rama/main/docs/img/rama_logo.svg"
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(test, allow(clippy::float_cmp))]
#![cfg_attr(not(test), warn(clippy::print_stdout, clippy::dbg_macro))]
#![expect(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::unwrap_in_result,
    clippy::panic_in_result_fn,
    reason = "proc-macro crate: panics are the historical compile-error mechanism for vendored macros (paste, etc.)"
)]
// `unreachable!()` is used in test fixtures (e.g. `|_| unreachable!()` callbacks for
// resolve_path tests in include_dir_macro). Scope the expect to cfg(test) so it only
// applies where it actually fires.
#![cfg_attr(
    test,
    expect(
        clippy::unreachable,
        reason = "test fixtures use unreachable!() in callbacks that aren't invoked"
    )
)]

use proc_macro::TokenStream;

mod extension_macro;
mod from_extensions_macro;
mod from_ref_macro;
mod include_dir_macro;
mod paste_macro;
mod utils;

#[proc_macro]
pub fn paste(input: TokenStream) -> TokenStream {
    let mut contains_paste = false;
    let flatten_single_interpolation = true;
    match paste_macro::expand(
        input.clone(),
        &mut contains_paste,
        flatten_single_interpolation,
    ) {
        Ok(expanded) => {
            if contains_paste {
                expanded
            } else {
                input
            }
        }
        Err(err) => err.to_compile_error(),
    }
}

/// Embed the contents of a directory in your crate.
#[proc_macro]
pub fn include_dir(input: TokenStream) -> TokenStream {
    include_dir_macro::execute(input)
}

/// Derive an implementation of [`FromRef`] for each field in a struct.
///
/// `#[from_ref(skip)]` can be used to skip specific fields
#[proc_macro_derive(FromRef, attributes(from_ref))]
pub fn derive_from_ref(item: TokenStream) -> TokenStream {
    from_ref_macro::expand_with(item, from_ref_macro::from_ref::expand)
}

/// Derive an implementation of `rama_core::extensions::Extension` for a type.
///
/// Note that all type parameters of the derived type must satisfy:
/// `Any + Send + Sync + Debug + 'static`.
///
/// Optional derive attributes:
/// - `#[extension(tags(...))]`: implement one or more marker traits used to group
///   extensions in rustdoc (`tls`, `http`, `net`, `ua`, `proxy`, `ws`, `dns`, `grpc`).
#[proc_macro_derive(Extension, attributes(extension))]
pub fn derive_extension(item: TokenStream) -> TokenStream {
    from_ref_macro::expand_with(item, extension_macro::extension::expand)
}

/// Derive a `from_extensions` constructor that gathers extension pieces from a
/// `rama_core::extensions::Extensions` store in a single pass.
///
/// On a struct, each named field must be `Option<&'a T>` (borrowed) or
/// `Option<Arc<T>>` (owned Arc clone), and the two may be mixed. A field may
/// also be `Option<(&'a T, usize)>` / `Option<(Arc<T>, usize)>` to additionally
/// capture the entry's traversal rank (`0` is the newest value seen, growing for
/// older ones, so ranks order fields by recency). A borrowed field requires the
/// struct to carry the matching lifetime (`struct View<'a>`); an all-`Arc`
/// struct needs no lifetime. Generates `fn from_extensions(ext: &Extensions) ->
/// Self`, where each field uses the same lookup as `Extensions::get_ref` but the
/// store is traversed only once.
///
/// A rank is a completely opaque type and should only be used to compare positions,
/// it does not tell anything about the absolute position.
///
/// On an enum, each variant is a one-field tuple variant naming a candidate
/// type (`Variant(&'a T)` or `Variant(Arc<T>)`, optionally `Variant((&'a T,
/// usize))`). Generates `fn from_extensions(ext: &Extensions) -> Option<Self>`
/// which returns the candidate inserted most recently (newest-wins by traversal
/// rank), or `None` if none are present. If two variants name the same type the
/// tie is broken deterministically in favour of the earlier declared variant.
#[proc_macro_derive(FromExtensions)]
pub fn derive_from_extensions(item: TokenStream) -> TokenStream {
    from_ref_macro::expand_with(item, from_extensions_macro::expand)
}