test-with-derive 0.16.1

A library that helps you run tests with conditions
Documentation
use std::{fs::metadata, path::Path};

#[cfg(feature = "runtime")]
use proc_macro::TokenStream;
#[cfg(feature = "runtime")]
use syn::{parse_macro_input, ItemFn, ReturnType};

pub(crate) fn check_path_condition(attr_str: String) -> (bool, String) {
    let paths: Vec<&str> = attr_str.split(',').collect();
    let mut missing_paths = vec![];
    for path in paths.iter() {
        if metadata(path.trim_matches('"')).is_err() {
            missing_paths.push(path.to_string());
        }
    }
    let ignore_msg = if missing_paths.len() == 1 {
        format!("because path not found: {}", missing_paths[0])
    } else {
        format!(
            "because following paths not found: \n{}\n",
            missing_paths.join("\n")
        )
    };
    (missing_paths.is_empty(), ignore_msg)
}

pub(crate) fn check_file_condition(attr_str: String) -> (bool, String) {
    let files: Vec<&str> = attr_str.split(',').collect();
    let mut missing_files = vec![];
    for file in files.iter() {
        if !Path::new(file.trim_matches('"')).is_file() {
            missing_files.push(file.to_string());
        }
    }
    let ignore_msg = if missing_files.len() == 1 {
        format!("because file not found: {}", missing_files[0])
    } else {
        format!(
            "because following files not found: \n{}\n",
            missing_files.join("\n")
        )
    };
    (missing_files.is_empty(), ignore_msg)
}

#[cfg(feature = "runtime")]
pub(crate) fn runtime_file(attr: TokenStream, stream: TokenStream) -> TokenStream {
    let attr_str = attr.to_string().replace(' ', "");
    let files: Vec<&str> = attr_str.split(',').collect();
    let ItemFn {
        attrs,
        vis,
        sig,
        block,
    } = parse_macro_input!(stream as ItemFn);
    let syn::Signature { ident, .. } = sig.clone();
    let check_ident = syn::Ident::new(&format!("_check_{ident}"), proc_macro2::Span::call_site());
    let check_fn = match (&sig.asyncness, &sig.output) {
        (Some(_), ReturnType::Default) => quote::quote! {
            async fn #check_ident() -> Result<test_with::Completion, test_with::Failed> {
                let mut missing_files = vec![];
                #(
                    if !std::path::Path::new(#files.trim_matches('"')).is_file() {
                        missing_files.push(#files);
                    }
                )*

                match missing_files.len() {
                    0 => {
                        #ident().await;
                        Ok(test_with::Completion::Completed)
                    },
                    1 => Ok(test_with::Completion::ignored_with(format!("because file not found: {}", missing_files[0]))),
                    _ => Ok(test_with::Completion::ignored_with(format!("because following files not found: \n{}\n", missing_files.join(", ")))),
                }
            }
        },
        (Some(_), ReturnType::Type(_, _)) => quote::quote! {
            async fn #check_ident() -> Result<test_with::Completion, test_with::Failed> {
                let mut missing_files = vec![];
                #(
                    if !std::path::Path::new(#files.trim_matches('"')).is_file() {
                        missing_files.push(#files);
                    }
                )*

                match missing_files.len() {
                    0 => {
                        if let Err(e) = #ident().await {
                            Err(format!("{e:?}").into())
                        } else {
                            Ok(test_with::Completion::Completed)
                        }
                    },
                    1 => Ok(test_with::Completion::ignored_with(format!("because file not found: {}", missing_files[0]))),
                    _ => Ok(test_with::Completion::ignored_with(format!("because following files not found: \n{}\n", missing_files.join(", ")))),
                }
            }
        },
        (None, _) => quote::quote! {
            fn #check_ident() -> Result<test_with::Completion, test_with::Failed> {
                let mut missing_files = vec![];
                #(
                    if !std::path::Path::new(#files.trim_matches('"')).is_file() {
                        missing_files.push(#files);
                    }
                )*

                match missing_files.len() {
                    0 => {
                        #ident();
                        Ok(test_with::Completion::Completed)
                    },
                    1 => Ok(test_with::Completion::ignored_with(format!("because file not found: {}", missing_files[0]))),
                    _ => Ok(test_with::Completion::ignored_with(format!("because following files not found: \n{}\n", missing_files.join(", ")))),
                }
            }
        },
    };

    quote::quote! {
        #check_fn
        #(#attrs)*
        #vis #sig #block
    }
    .into()
}

#[cfg(feature = "runtime")]
pub(crate) fn runtime_path(attr: TokenStream, stream: TokenStream) -> TokenStream {
    let attr_str = attr.to_string().replace(' ', "");
    let paths: Vec<&str> = attr_str.split(',').collect();
    let ItemFn {
        attrs,
        vis,
        sig,
        block,
    } = parse_macro_input!(stream as ItemFn);
    let syn::Signature { ident, .. } = sig.clone();
    let check_ident = syn::Ident::new(&format!("_check_{ident}"), proc_macro2::Span::call_site());

    let check_fn = match (&sig.asyncness, &sig.output) {
        (Some(_), ReturnType::Default) => quote::quote! {
            async fn #check_ident() -> Result<test_with::Completion, test_with::Failed> {
                let mut missing_paths = vec![];
                #(
                    if std::fs::metadata(#paths.trim_matches('"')).is_err() {
                        missing_paths.push(#paths.to_string());
                    }
                )*

                match missing_paths.len() {
                    0 => {
                        #ident().await;
                        Ok(test_with::Completion::Completed)
                    },
                    1 => Ok(test_with::Completion::ignored_with(format!("because path not found: {}", missing_paths[0]))),
                    _ => Ok(test_with::Completion::ignored_with(format!("because following paths not found: \n{}\n", missing_paths.join(", ")))),
                }
            }
        },
        (Some(_), ReturnType::Type(_, _)) => quote::quote! {
            async fn #check_ident() -> Result<test_with::Completion, test_with::Failed> {
                let mut missing_paths = vec![];
                #(
                    if std::fs::metadata(#paths.trim_matches('"')).is_err() {
                        missing_paths.push(#paths.to_string());
                    }
                )*

                match missing_paths.len() {
                    0 => {
                        if let Err(e) = #ident().await {
                            Err(format!("{e:?}").into())
                        } else {
                            Ok(test_with::Completion::Completed)
                        }
                    },
                    1 => Ok(test_with::Completion::ignored_with(format!("because path not found: {}", missing_paths[0]))),
                    _ => Ok(test_with::Completion::ignored_with(format!("because following paths not found: \n{}\n", missing_paths.join(", ")))),
                }
            }
        },
        (None, _) => quote::quote! {
            fn #check_ident() -> Result<test_with::Completion, test_with::Failed> {
                let mut missing_paths = vec![];
                #(
                    if std::fs::metadata(#paths.trim_matches('"')).is_err() {
                        missing_paths.push(#paths.to_string());
                    }
                )*

                match missing_paths.len() {
                    0 => {
                        #ident();
                        Ok(test_with::Completion::Completed)
                    },
                    1 => Ok(test_with::Completion::ignored_with(format!("because path not found: {}", missing_paths[0]))),
                    _ => Ok(test_with::Completion::ignored_with(format!("because following paths not found: \n{}\n", missing_paths.join(", ")))),
                }
            }
        },
    };

    quote::quote! {
        #check_fn
        #(#attrs)*
        #vis #sig #block
    }
    .into()
}