embedded-cli-macros 0.2.1

Macros for embedded-cli lib
Documentation
use darling::{Error, Result};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput};

use crate::{processor, utils::TargetType};

use self::command_group::CommandGroup;

mod command_group;

pub fn derive_command_group(input: DeriveInput) -> Result<TokenStream> {
    let DeriveInput {
        ident,
        data,
        generics,
        ..
    } = input;

    let data = if let Data::Enum(data) = data {
        data
    } else {
        return Err(Error::custom("Command can be derived only for an enum").with_span(&ident));
    };

    let target = TargetType::parse(ident, generics)?;

    let mut errors = Error::accumulator();
    let groups: Vec<CommandGroup> = data
        .variants
        .iter()
        .filter_map(|variant| errors.handle_in(|| CommandGroup::parse(variant)))
        .collect();
    errors.finish()?;

    let derive_autocomplete = derive_autocomplete(&target, &groups);
    let derive_help = derive_help(&target, &groups);
    let derive_from_raw = derive_from_raw(&target, &groups);
    let impl_processor = processor::impl_processor(&target)?;

    let output = quote! {
        #derive_autocomplete
        #derive_help
        #derive_from_raw
        #impl_processor
    };

    Ok(output)
}

#[cfg(feature = "autocomplete")]
fn derive_autocomplete(target: &TargetType, groups: &[CommandGroup]) -> TokenStream {
    let ident = target.ident();
    let named_lifetime = target.named_lifetime();

    let groups = groups
        .iter()
        .filter(|group| !group.hidden)
        .map(|group| {
            let ty = &group.field_type;
            quote! {
                <#ty as _cli::service::Autocomplete>::autocomplete(request.clone(), autocompletion);
            }
        })
        .collect::<Vec<_>>();

    quote! {
        impl #named_lifetime _cli::service::Autocomplete for #ident #named_lifetime {
            fn autocomplete(
                request: _cli::autocomplete::Request<'_>,
                autocompletion: &mut _cli::autocomplete::Autocompletion<'_>,
            ) {
                #(#groups)*
            }
        }
    }
}

#[allow(unused_variables)]
#[cfg(not(feature = "autocomplete"))]
fn derive_autocomplete(target: &TargetType, groups: &[CommandGroup]) -> TokenStream {
    let ident = target.ident();
    let named_lifetime = target.named_lifetime();

    quote! {
        impl #named_lifetime _cli::service::Autocomplete for #ident #named_lifetime { }
    }
}

#[cfg(feature = "help")]
fn derive_help(target: &TargetType, groups: &[CommandGroup]) -> TokenStream {
    let ident = target.ident();
    let named_lifetime = target.named_lifetime();

    let command_counts = groups
        .iter()
        .filter(|group| !group.hidden)
        .enumerate()
        .map(|(i, group)| {
            let ty = &group.field_type;
            if i > 0 {
                quote! {
                    + <#ty as _cli::service::Help>::command_count()
                }
            } else {
                quote! {
                    <#ty as _cli::service::Help>::command_count()
                }
            }
        })
        .collect::<Vec<_>>();

    let command_help = groups
        .iter()
        .filter(|group| !group.hidden)
        .enumerate()
        .map(|(i, group)| {
            let ty = &group.field_type;
            if i > 0 {
                quote! {
                    .or_else(|_| <#ty as _cli::service::Help>::command_help(parent, command.clone(), writer))
                }
            } else {
                quote! {
                    <#ty as _cli::service::Help>::command_help(parent, command.clone(), writer)
                }
            }
        })
        .collect::<Vec<_>>();

    let list_commands = groups
        .iter()
        .filter(|group| !group.hidden)
        .map(|group| {
            let ty = &group.field_type;
            quote! {
                if <#ty as _cli::service::Help>::command_count() > 0 {
                    if has_output {
                        writer.writeln_str("")?;
                    }
                    <#ty as _cli::service::Help>::list_commands(writer)?;
                    has_output = true;
                }
            }
        })
        .collect::<Vec<_>>();

    quote! {
        impl #named_lifetime _cli::service::Help for #ident #named_lifetime {

            fn command_count() -> usize {
                #(#command_counts)*
            }

            fn list_commands<W: _io::Write<Error = E>, E: _io::Error>(
                writer: &mut _cli::writer::Writer<'_, W, E>,
            ) -> Result<(), E> {
                let mut has_output = false;
                #(#list_commands)*
                Ok(())
            }

            fn command_help<
                W: _io::Write<Error = E>,
                E: _io::Error,
                F: FnMut(&mut _cli::writer::Writer<'_, W, E>) -> Result<(), E>,
            >(
                parent: &mut F,
                command: _cli::command::RawCommand<'_>,
                writer: &mut _cli::writer::Writer<'_, W, E>,
            ) -> Result<(), _cli::service::HelpError<E>> {
                #(#command_help)*?;
                Ok(())
            }
        }
    }
}

#[allow(unused_variables)]
#[cfg(not(feature = "help"))]
fn derive_help(target: &TargetType, groups: &[CommandGroup]) -> TokenStream {
    let ident = target.ident();
    let named_lifetime = target.named_lifetime();

    quote! {
        impl #named_lifetime _cli::service::Help for #ident #named_lifetime { }
    }
}

fn derive_from_raw(target: &TargetType, groups: &[CommandGroup]) -> TokenStream {
    let ident = target.ident();
    let named_lifetime = target.named_lifetime();

    let groups = groups
        .iter()
        .map(|group| {
            let ident = &group.ident;
            let ty = &group.field_type;
            quote! {
                match <#ty as _cli::service::FromRaw>::parse(raw.clone()) {
                    Ok(cmd) => {
                        return Ok(Self:: #ident (cmd));
                    }
                    Err(_cli::service::ParseError::UnknownCommand) => {}
                    Err(err) => return Err(err),
                }
            }
        })
        .collect::<Vec<_>>();

    quote! {
        impl<'a> _cli::service::FromRaw<'a> for #ident #named_lifetime {
            fn parse(raw: _cli::command::RawCommand<'a>) -> Result<Self, _cli::service::ParseError<'a>> {
                #(#groups)*

                Err(_cli::service::ParseError::UnknownCommand)
            }
        }
    }
}