xdoc-macros 0.1.0

Procedural macros for xdoc
Documentation
//! Procedural macros for `xdoc`.
//!
//! This crate intentionally does not depend on `xdoc`; generated code resolves
//! the caller's `xdoc-rs` dependency name so the runtime crate remains the
//! single XML engine.

#[allow(dead_code)]
mod ast;
#[allow(dead_code)]
mod codegen;
#[allow(dead_code)]
mod parser;

use proc_macro::TokenStream;
use proc_macro2::Ident;
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;

/// Declarative XML macro entry point.
///
/// Parses the XML-like template and generates builder calls.
#[proc_macro]
pub fn xml(input: TokenStream) -> TokenStream {
    let input = proc_macro2::TokenStream::from(input);

    match parser::parse_template(input).and_then(|template| {
        let runtime_path = xdoc_runtime_path().map_err(parser::ParseError::from_message)?;
        codegen::generate_template(&template, runtime_path).map_err(|error| {
            parser::ParseError::from_message(format!("code generation failed: {}", error.message()))
        })
    }) {
        Ok(tokens) => tokens.into(),
        Err(error) => compile_error(error.message()).into(),
    }
}

fn xdoc_runtime_path() -> Result<proc_macro2::TokenStream, String> {
    match crate_name("xdoc-rs").map_err(|error| error.to_string())? {
        FoundCrate::Itself => Ok(quote! { ::xdoc }),
        FoundCrate::Name(name) => {
            if name == "xdoc_rs" {
                return Ok(quote! { ::xdoc });
            }
            let ident = Ident::new(&name, proc_macro2::Span::call_site());
            Ok(quote! { ::#ident })
        }
    }
}

fn compile_error(message: &str) -> proc_macro2::TokenStream {
    let message = proc_macro2::Literal::string(message);
    quote! {
        compile_error!(#message)
    }
}