embd-macros 0.1.5

Read files or directories from the filesystem at runtime on debug, embed on release.
#![allow(unexpected_cfgs)]
#[cfg(procmacro2_semver_exempt)]
use std::{
    fs,
    path::Path,
};

#[cfg(not(procmacro2_semver_exempt))]
compile_error!(
    r#"pass `--cfg procmacro2_semver_exempt` to rustc to compile embd-macros or add this to your `.cargo/config.toml`:

[build]
rustflags = [ "--cfg", "procmacro2_semver_exempt" ]

"#
);

#[cfg(procmacro2_semver_exempt)]
use proc_macro as pm1;
use proc_macro2::TokenStream;
use quote::{
    quote,
    ToTokens,
};
#[cfg(procmacro2_semver_exempt)]
use syn::{
    parse_macro_input,
    spanned::Spanned,
    LitStr,
};

struct TokenVec(Vec<TokenStream>);

impl ToTokens for TokenVec {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let inner = &self.0;

        tokens.extend(quote! {
            ::std::borrow::Cow::Borrowed(&[#(#inner),*])
        });
    }
}

#[proc_macro]
#[cfg(procmacro2_semver_exempt)]
pub fn __dir(input: pm1::TokenStream) -> pm1::TokenStream {
    let input2 = input.clone();
    let path = parse_macro_input!(input2 as LitStr).value();

    let debug = dir_debug(&path);
    let release = dir_release(input.into(), &path);

    quote! {
        {
            #[cfg(debug_assertions)]
            {
                #debug
            }
            #[cfg(not(debug_assertions))]
            {
                #release
            }
        }
    }
    .into()
}

#[cfg(procmacro2_semver_exempt)]
fn dir_debug(path: &str) -> TokenStream {
    quote! {
        ::embd::__dir_runtime(file!(), #path)
    }
}

#[cfg(procmacro2_semver_exempt)]
fn dir_release(input: TokenStream, path: &str) -> TokenStream {
    let neighbor = input.span().source_file().path();

    let base = neighbor.parent().expect("Failed to get the parent of file");

    let directory = base
        .join(path)
        .canonicalize()
        .expect("Failed to canonicalize path");

    let directory_str = directory.to_str().expect("Failed to convert OsStr to str");

    let children = read_dir(&directory);

    quote! {
        ::embd::Dir {
            __children: #children,
            __path: ::std::borrow::Cow::Borrowed(#directory_str),
        }
    }
}

#[cfg(procmacro2_semver_exempt)]
fn read_dir(directory: &Path) -> TokenVec {
    let mut entries = Vec::new();

    for entry in fs::read_dir(directory).expect("Failed to list directory contents") {
        let entry = entry.expect("Failed to read entry");

        let path = entry.path();

        let path_str = path
            .to_str()
            .expect("Failed to get the string representation of PathBuf");

        let filetype = fs::metadata(&path)
            .expect("Failed to get file metadata")
            .file_type();

        if filetype.is_dir() {
            let children = read_dir(&path);

            entries.push(quote! {
                ::embd::DirEntry::Dir(::embd::Dir {
                    __children: #children,
                    __path: ::std::borrow::Cow::Borrowed(#path_str),
                })
            });
        } else if filetype.is_file() {
            entries.push(quote! {
                ::embd::DirEntry::File(::embd::File {
                    __content: ::std::borrow::Cow::Borrowed(include_bytes!(#path_str)),
                    __path: ::std::borrow::Cow::Borrowed(#path_str),
                })
            });
        }
    }

    TokenVec(entries)
}