docbot-derive 0.3.0-alpha.2

Derive macros for docbot
Documentation
use proc_macro2::{Literal, TokenStream};
use quote::{format_ident, quote_spanned};

use super::id::IdParts;
use crate::inputs::prelude::*;

pub struct PathParts {
    pub is_id: bool,
    pub ty: Ident,
    pub items: TokenStream,
}

fn get_pats(
    span: Span,
    iter: &Ident,
    command: &Command,
) -> (
    Option<TokenStream>,
    Option<TokenStream>,
    Option<TokenStream>,
    Option<TokenStream>,
) {
    if command.opts.subcommand {
        let field = command.fields.iter().next().unwrap();
        let inner = field.ty;

        (
            Some(quote_spanned! { span => (Option<Box<<#inner as ::docbot::Command>::Path>>) }),
            Some(
                quote_spanned! { span => (::docbot::CommandPath::parse_opt(#iter)?.map(Box::new)) },
            ),
            Some(quote_spanned! { span => (..) }),
            Some(quote_spanned! { span => (None) }),
        )
    } else {
        (None, None, None, None)
    }
}

fn handle_variant(
    input_ty: &Ident,
    id_ty: &Ident,
    iter: &Ident,
    CommandVariant {
        ident,
        command,
        span,
        ..
    }: &CommandVariant,
) -> (TokenStream, (TokenStream, (TokenStream, TokenStream))) {
    let doc = Literal::string(&format!("Path for {}::{}", input_ty, ident));

    let (var_pat, parse_pat, head_pat, from_id_pat) = get_pats(*span, iter, command);

    (
        quote_spanned! { *span => #[doc = #doc] #ident #var_pat },
        (
            quote_spanned! { *span => #id_ty::#ident => Self::#ident #parse_pat },
            (
                quote_spanned! { *span => Self::#ident #head_pat => #id_ty::#ident },
                quote_spanned! { *span => #id_ty::#ident => Self::#ident #from_id_pat },
            ),
        ),
    )
}

pub fn emit(input: &InputData, id_parts: &IdParts) -> PathParts {
    let ty;
    let def;

    let is_id = !input.commands.iter().any(|c| c.opts.subcommand);

    if is_id {
        ty = id_parts.ty.clone();
        def = None;
    } else {
        let vis = input.vis;
        let id_ty = &id_parts.ty;
        let iter = Ident::new("__iter", input.span);
        let doc = Literal::string(&format!("Command path for commands of type {}", input.ty));

        ty = format_ident!("{}Path", input.ty, span = input.ty.span());

        let def_body;
        let parse;
        let head;
        let from_id;

        match input.commands {
            Commands::Struct { ref command, .. } => {
                let (body_pat, parse_pat, head_pat, from_id_pat) =
                    get_pats(input.span, &iter, command);

                def_body = quote_spanned! { input.span => struct #ty #body_pat; };
                parse = quote_spanned! { input.span => #id_ty => Self #parse_pat };
                head = quote_spanned! { input.span => Self #head_pat => #id_ty };
                from_id = quote_spanned! { input.span => #id_ty => Self #from_id_pat };
            },
            Commands::Enum { ref variants, .. } => {
                let (path_vars, (parse_vars, (head_vars, from_id_vars))): (
                    Vec<_>,
                    (Vec<_>, (Vec<_>, Vec<_>)),
                ) = variants
                    .iter()
                    .map(|v| handle_variant(input.ty, id_ty, &iter, v))
                    .unzip();

                def_body = quote_spanned! { input.span => enum #ty { #(#path_vars),* } };
                parse = quote_spanned! { input.span => #(#parse_vars),* };
                head = quote_spanned! { input.span => #(#head_vars),* };
                from_id = quote_spanned! { input.span => #(#from_id_vars),* };
            },
        }

        def = Some(quote_spanned! { input.span =>
            #[doc = #doc]
            #vis #def_body

            impl ::docbot::CommandPath for #ty {
                type Id = #id_ty;

                fn parse<I: IntoIterator<Item = S>, S: AsRef<str>>(
                    #iter: I
                ) -> ::std::result::Result<Self, ::docbot::PathParseError> {
                    let mut #iter = #iter.into_iter();

                    Ok(match #iter
                       .next()
                       .ok_or_else(|| {
                           ::docbot::PathParseError::Incomplete(#id_ty::names())
                       })?
                       .as_ref()
                       .parse()? {
                        #parse
                    })
                }

                fn head(&self) -> #id_ty { match self { #head } }
            }

            impl ::std::convert::From<#id_ty> for #ty {
                fn from(id: #id_ty) -> Self { match id { #from_id } }
            }
        });
    }

    let items = quote_spanned! { input.span =>
        #def
    };

    PathParts { is_id, ty, items }
}