pacaptr-macros 0.23.2

Implementation of several macros used in pacaptr.
Documentation
use itertools::Itertools;
use proc_macro2::{Literal, Span, TokenStream};
use quote::quote;
use syn::{Error, Result};

enum TestDslItem {
    In(Vec<String>),
    InBang(Vec<String>),
    Ou(String),
    Rt(u8),
}

impl TestDslItem {
    fn try_from_line(ln: &str) -> Result<Self> {
        let in_bang = "in ! ";
        let in_ = "in ";
        let ou = "ou ";
        let rt = "rt ";
        let tokenize = |s: &str| s.split_whitespace().map_into().collect();
        #[allow(clippy::option_if_let_else)]
        if let Some(rest) = ln.strip_prefix(in_bang) {
            Ok(Self::InBang(tokenize(rest)))
        } else if let Some(rest) = ln.strip_prefix(in_) {
            Ok(Self::In(tokenize(rest)))
        } else if let Some(rest) = ln.strip_prefix(ou) {
            Ok(Self::Ou(rest.into()))
        } else if let Some(rest) = ln.strip_prefix(rt) {
            let code = rest
                .trim()
                .parse()
                .map_err(|_| Error::new(Span::call_site(), "return code must be a valid u8"))?;
            Ok(Self::Rt(code))
        } else {
            let msg = format!(
                "item must start with one of the following: {}, found `{}`",
                [in_bang, in_, ou,]
                    .iter()
                    .map(|s| format!("`{}`", s.trim_end()))
                    .join(", "),
                ln,
            );
            Err(Error::new(Span::call_site(), msg))
        }
    }

    fn build(&self) -> TokenStream {
        match self {
            Self::In(i) => {
                let i = i.iter().map(|s| Literal::string(s)).collect_vec();
                quote! { .pacaptr(&[ #(#i),* ], &[]) }
            }
            Self::InBang(i) => {
                let i = i.iter().map(|s| Literal::string(s)).collect_vec();
                quote! { .exec(&[ #(#i),* ], &[]) }
            }
            Self::Ou(o) => {
                let o = Literal::string(o);
                quote! { .output(&[ #o ]) }
            }
            Self::Rt(code) => {
                let code = Literal::u8_suffixed(*code);
                quote! { .code(#code) }
            }
        }
    }
}

#[allow(clippy::module_name_repetitions)]
pub fn test_dsl_impl(input: &str) -> Result<TokenStream> {
    let items: Vec<TokenStream> = input
        .lines()
        .map(|ln| ln.trim_start().trim_end())
        // Filter out comments and empty lines.
        .filter(|ln| !(ln.is_empty() || ln.starts_with('#')))
        .map(|ln| TestDslItem::try_from_line(ln).map(|item| item.build()))
        .try_collect()?;
    Ok(quote! { Test::new() #(#items)* .run()})
}