cxx-auto 0.0.2

Automatically generate cxx bindings
#[cfg(feature = "alloc")]
use crate::BoxResult;

use proc_macro2::Span;
use quote::ToTokens;
use rust_format::Formatter;
#[cfg(feature = "std")]
use std::collections::BTreeSet;

pub(crate) fn process_src_auto_module(
    project_dir: &std::path::Path,
    out_dir: &std::path::Path,
    cfg_dir: &std::path::Path,
) -> BoxResult<()> {
    let cfg_dir_walker = walkdir::WalkDir::new(cfg_dir).min_depth(1);
    let skip_paths = std::collections::BTreeSet::new();
    let mut walked_path_components = ::alloc::vec::Vec::new();

    process_src_auto_sub_module(
        project_dir,
        out_dir,
        cfg_dir_walker.into_iter(),
        skip_paths,
        &mut walked_path_components,
    )?;

    walked_path_components.sort();

    let contents = {
        let mut path_descendants = BTreeSet::new();
        let mut item_mods = ::alloc::vec![];
        emit_item_mods_for_path_descendants(
            project_dir,
            out_dir,
            cfg_dir,
            &mut std::collections::BTreeSet::new(),
            &mut path_descendants,
            &mut item_mods,
        )?;
        let item_write_module = emit_item_write_module_for_dir(&::alloc::vec![], &path_descendants);
        let item_fn_process_artifact_infos =
            emit_item_fn_process_artifact_infos(([::alloc::vec![]]).iter().chain(walked_path_components.iter()));
        let file: syn::File = syn::parse_quote! {
            #(#item_mods)*
            #item_write_module
            #item_fn_process_artifact_infos
        };
        let tokens = file.to_token_stream();
        rust_format::RustFmt::default().format_tokens(tokens)?
    };
    std::fs::write(out_dir.join("auto.rs"), contents)?;

    Ok(())
}

#[cfg(feature = "std")]
fn process_src_auto_sub_module(
    project_dir: &std::path::Path,
    out_dir: &std::path::Path,
    mut cfg_dir_walker: impl Iterator<Item = walkdir::Result<walkdir::DirEntry>>,
    mut skip_paths: std::collections::BTreeSet<std::path::PathBuf>,
    walked_path_file_components: &mut ::alloc::vec::Vec<::alloc::vec::Vec<::alloc::string::String>>,
) -> BoxResult<()> {
    if let Some(entry) = cfg_dir_walker.next().transpose()? {
        let path = entry.path();

        if !skip_paths.contains(path) {
            let path_dir = Some(path.to_path_buf())
                .filter(|p| p.is_dir())
                .or_else(|| Some(path.with_extension("")).filter(|p| p.is_dir()));
            let path_file = Some(path.to_path_buf())
                .filter(|p| p.is_file())
                .or_else(|| Some(path.with_extension("json")).filter(|p| p.is_file()));

            let path_components = relativized_components_from_path(path)?;
            let mut path_descendants = BTreeSet::new();
            let mut item_mods = ::alloc::vec![];

            if let Some(path) = &path_dir {
                emit_item_mods_for_path_descendants(
                    project_dir,
                    out_dir,
                    path,
                    &mut skip_paths,
                    &mut path_descendants,
                    &mut item_mods,
                )?;
            }

            let mut items_write_module: ::alloc::vec::Vec<syn::ItemFn> = ::alloc::vec![];
            let mut item_mod_cxx_bridge: ::alloc::vec::Vec<syn::Item> = ::alloc::vec![];

            if let Some(path) = &path_file {
                skip_paths.insert(path.clone());
                let text = std::fs::read_to_string(path)?;
                let data = serde_json::from_str::<crate::CxxAutoEntry>(&text)?;
                items_write_module =
                    data.emit_items_write_module_for_file(path_components.iter(), path_descendants.iter());
                item_mod_cxx_bridge.extend(data.emit_item_mod_cxx_bridge());
            } else {
                items_write_module.push(emit_item_write_module_for_dir(&path_components, &path_descendants));
            }

            let auto_sub_module_path = out_dir.join(
                [::alloc::string::String::from("auto")]
                    .iter()
                    .chain(&path_components)
                    .collect::<std::path::PathBuf>(),
            );

            walked_path_file_components.push(path_components);

            if let Some(parent) = auto_sub_module_path.parent() {
                std::fs::create_dir_all(parent)?;
            }

            write_auto_sub_module(
                &auto_sub_module_path.with_extension("rs"),
                &item_mods,
                &item_mod_cxx_bridge,
                &items_write_module,
            )?;
        }
        process_src_auto_sub_module(
            project_dir,
            out_dir,
            cfg_dir_walker,
            skip_paths,
            walked_path_file_components,
        )?;
    }
    Ok(())
}

fn emit_item_mods_for_path_descendants(
    project_dir: &std::path::Path,
    out_dir: &std::path::Path,
    path: &std::path::Path,
    skip_paths: &mut BTreeSet<std::path::PathBuf>,
    path_descendants: &mut BTreeSet<::alloc::string::String>,
    items: &mut ::alloc::vec::Vec<syn::ItemMod>,
) -> BoxResult<()> {
    skip_paths.insert(path.to_path_buf());
    find_immediate_path_descendants(path, path_descendants)?;
    let span = Span::call_site();
    for descendant in &*path_descendants {
        let ident = syn::Ident::new(descendant, span);
        let descendant_path = path.join(descendant).with_extension("rs");
        let mod_suffix = descendant_path.strip_prefix(project_dir)?;
        let mod_path = out_dir.join(mod_suffix);
        let mod_path_string = mod_path.to_string_lossy();
        items.push(syn::parse_quote! {
            #[path = #mod_path_string]
            pub mod #ident;
        });
    }
    Ok(())
}

#[cfg(feature = "std")]
fn emit_item_write_module_for_dir(
    path_components: &::alloc::vec::Vec<::alloc::string::String>,
    path_descendants: &BTreeSet<::alloc::string::String>,
) -> syn::ItemFn {
    syn::parse_quote! {
        pub(crate) fn write_module(out_dir: &::std::path::Path) -> ::cxx_auto::BoxResult<()> {
            let path_components = &[#(#path_components),*];
            let path_descendants = &[#(#path_descendants),*];
            ::cxx_auto::CxxAutoArtifactInfo::write_module_for_dir(out_dir, path_components, path_descendants)
        }
    }
}

#[cfg(feature = "std")]
fn emit_item_fn_process_artifact_infos<'a>(
    walked_path_components: impl Iterator<Item = &'a ::alloc::vec::Vec<::alloc::string::String>>,
) -> syn::ItemFn {
    let span = Span::call_site();
    let items = walked_path_components.map(|path_components| -> syn::Stmt {
        let path = syn::Path {
            leading_colon: None,
            segments: path_components
                .iter()
                .map(|component| syn::PathSegment::from(syn::Ident::new(component, span)))
                .collect(),
        };
        if path_components.is_empty() {
            syn::parse_quote!(self::write_module(auto_out_dir_root)?;)
        } else {
            syn::parse_quote!(self::#path::write_module(auto_out_dir_root)?;)
        }
    });
    syn::parse_quote! {
        #[allow(clippy::missing_errors_doc)]
        pub fn process_artifacts(out_dir: &::std::path::Path) -> ::cxx_auto::BoxResult<()> {
            let auto_out_dir_root = &out_dir.join("src/auto");
            #(#items)*
            Ok(())
        }
    }
}

#[cfg(feature = "std")]
fn find_immediate_path_descendants(
    path: &std::path::Path,
    path_descendants: &mut BTreeSet<::alloc::string::String>,
) -> BoxResult<()> {
    for result in walkdir::WalkDir::new(path).min_depth(1).max_depth(1) {
        let entry = result?;
        if let Some(file_stem) = entry
            .path()
            .file_stem()
            .map(|s| {
                s.to_os_string()
                    .into_string()
                    .map_err(|err| ::alloc::format!("Failed to convert to String: {err:?}"))
            })
            .transpose()?
        {
            path_descendants.insert(file_stem);
        }
    }
    Ok(())
}

#[cfg(feature = "std")]
fn relativized_components_from_path(path: &std::path::Path) -> BoxResult<::alloc::vec::Vec<::alloc::string::String>> {
    let path_components = path
        .with_extension("")
        .components()
        .skip_while(|component| component.as_os_str() != "auto")
        .skip(1)
        .map(|component| {
            component
                .as_os_str()
                .to_os_string()
                .into_string()
                .map_err(|err| ::alloc::format!("Failed to convert to String: {err:?}"))
        })
        .collect::<Result<::alloc::vec::Vec<_>, _>>()?;
    Ok(path_components)
}

#[cfg(feature = "std")]
fn write_auto_sub_module(
    path: &std::path::Path,
    item_mods: &[syn::ItemMod],
    item_mod_cxx_bridge: &[syn::Item],
    items_write_module: &[syn::ItemFn],
) -> BoxResult<()> {
    let file: syn::File = syn::parse_quote! {
        #(#item_mods)*
        #(#item_mod_cxx_bridge)*
        #(#items_write_module)*
    };
    let tokens = file.to_token_stream();
    let contents = rust_format::RustFmt::default().format_tokens(tokens)?;
    std::fs::write(path.with_extension("rs"), contents)?;
    Ok(())
}