macrofied-toolbox 0.4.2

This library provides an ergonomic experience of adding debugging messages to rust's Result<T,E> and Option<T> patterns
Documentation
use proc_macro2::TokenStream;
use quote::ToTokens;

use crate::common::{Capture, OnFail, OnSuccess, WhenExpr};
#[cfg(all(debug_assertions, feature = "result-debug"))]
use crate::common::Message;
use crate::common::tokenize::build_captured;
use crate::common::tracing::trace_expansion;
use crate::result_macro::parts::Parts;
use crate::result_macro::ResultMacro;

impl ToTokens for ResultMacro {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.extend(trace_expansion({
            let when = &self.when;

            match self.definition() {
                #[cfg(not(all(debug_assertions, feature = "result-debug")))]
                Parts::OK |
                Parts::OK_DEBUG =>
                    branch_only_ok(when, self.ok.as_ref().unwrap()),
                #[cfg(all(debug_assertions, feature = "result-debug"))]
                Parts::OK =>
                    branch_only_ok(when, self.ok.as_ref().unwrap()),
                #[cfg(not(all(debug_assertions, feature = "result-debug")))]
                Parts::DEBUG =>
                    branch_only_error(when, || (&None, TokenStream::new())),
                #[cfg(all(debug_assertions, feature = "result-debug"))]
                Parts::DEBUG =>
                    branch_only_error(when, || build_message_stdout(self.debug.as_ref().unwrap())),
                Parts::ERROR =>
                    branch_only_error(when, || build_on_error(self.error.as_ref().unwrap())),
                #[cfg(all(debug_assertions, feature = "result-debug"))]
                Parts::OK_DEBUG =>
                    branch_ok_or_error(
                        when, self.ok.as_ref().unwrap(),
                        || build_message_stdout(self.debug.as_ref().unwrap()),
                    ),
                Parts::OK_ERROR =>
                    branch_ok_or_error(
                        when, self.ok.as_ref().unwrap(),
                        || build_on_error(self.error.as_ref().unwrap()),
                    ),
                Parts::OK_DEBUG_ERROR =>
                    branch_ok_or_error(
                        when, self.ok.as_ref().unwrap(), || build_debugged_error(self),
                    ),
                Parts::DEBUG_ERROR =>
                    branch_only_error(when, || build_debugged_error(self)),
                _ => unimplemented!("{:?} is not supported", self.definition())
            }
        }));
    }
}

fn branch_ok_or_error<'a>(
    when: &'a WhenExpr, ok: &OnSuccess,
    build_error: impl Fn() -> (&'a Option<Capture>, TokenStream),
) -> TokenStream {
    let when_expr = &when.expr;
    let (ok_branch, on_ok) = build_on_ok(ok);
    let (captured, on_error) = build_error();
    let error_branch = if captured.is_some() || when.tried {
        quote! { Err(err) }
    } else {
        quote! { Err(_) }
    };
    let tried = if when.tried { quote! { ; return Err(err); } } else { TokenStream::new() };

    quote! {
        match #when_expr {
            #ok_branch => { #on_ok }
            #error_branch => { #on_error #tried }
        }
    }
}

fn branch_only_error<'a>(
    when: &'a WhenExpr, build_error: impl Fn() -> (&'a Option<Capture>, TokenStream),
) -> TokenStream {
    let when_expr = &when.expr;
    let (captured, on_error) = build_error();
    let when_tried = if when.tried { quote! { ; return Err(err); } } else { TokenStream::new() };

    if captured.is_some() || when.tried {
        quote! { if let Err(err) = #when_expr { #on_error #when_tried } }
    } else {
        quote! { if #when_expr.is_err() { #on_error } }
    }
}

fn branch_only_ok(when: &WhenExpr, ok: &OnSuccess) -> TokenStream {
    let when_expr = &when.expr;
    let (captured, on_ok) = match ok {
        OnSuccess::Expr(expr) =>
            (&expr.captured, expr.expr.to_token_stream()),
        OnSuccess::Message(message) => {
            let message_fmt = message.build_message();
            let ok_message = quote! { println!(#message_fmt); };

            (&message.captured, ok_message)
        }
    };

    if when.tried {
        let ok_branch = build_branch_ok(captured);

        quote! {
            match #when_expr {
                #ok_branch => { #on_ok }
                Err(err) => { return Err(err); }
            }
        }
    } else if captured.is_some() {
        let captured = build_captured(captured);

        quote! { if let Ok(#captured) = #when_expr { #on_ok; } }
    } else {
        quote! { if #when_expr.is_ok() { #on_ok; } }
    }
}

fn build_branch_ok(captured: &Option<Capture>) -> TokenStream {
    let capture = build_captured(captured);

    quote! { Ok(#capture) }
}

fn build_debugged_error(result_macro: &ResultMacro) -> (&Option<Capture>, TokenStream) {
    cfg_if! {
        if #[cfg(all(debug_assertions, feature = "result-debug"))] {
            let (captured_err, on_error) = build_on_error(result_macro.error.as_ref().unwrap());
            let (captured_dbg, on_debug) = build_message_stdout(result_macro.debug.as_ref().unwrap());
            let captured = if captured_dbg.is_some() { captured_dbg } else { captured_err };

            (captured, quote! { #on_debug  #on_error })
        } else {
            build_on_error(result_macro.error.as_ref().unwrap())
        }
    }
}

#[cfg(all(debug_assertions, feature = "result-debug"))]
fn build_message_stdout(message: &Message) -> (&Option<Capture>, TokenStream) {
    let message_fmt = message.build_message();

    (&message.captured, quote! { println!(#message_fmt); })
}

fn build_on_error(error: &OnFail) -> (&Option<Capture>, TokenStream) {
    let mut on_error = TokenStream::new();
    let mut captured = &None;

    if error.message.is_some() {
        let (captured_error, message_error) = {
            let message_fmt = error.message.as_ref().unwrap().build_message();
            let error_message = quote! { eprintln!(#message_fmt); };

            (&error.message.as_ref().unwrap().captured, error_message)
        };

        captured = captured_error;

        on_error.extend(message_error);
    }

    if let Some(expr) = &error.expr {
        let error_expr = &expr.expr;

        if captured.is_none() && expr.captured.is_some() {
            captured = &expr.captured;
        }

        on_error.extend(quote! { #error_expr });
    }

    (captured, on_error)
}

fn build_on_ok(ok: &OnSuccess) -> (TokenStream, TokenStream) {
    let (ok_captured, on_ok) = match ok {
        OnSuccess::Message(message) => {
            let ok_message = message.build_message();
            (message.captured.clone(), quote! { println!(#ok_message); })
        }
        OnSuccess::Expr(expr) => (expr.captured.clone(), expr.expr.to_token_stream())
    };

    let ok_branch = build_branch_ok(&ok_captured);

    (ok_branch, on_ok)
}