constructivist 0.3.0

Simplify the construction of structured data.
Documentation
use std::{cell::RefCell, collections::HashMap};

use proc_macro2::TokenStream;
use quote::{format_ident, quote};

pub struct Context {
    pub prefix: &'static str,
    cache: RefCell<HashMap<&'static str, TokenStream>>,
}

impl Context {
    pub fn new(prefix: &'static str) -> Self {
        Self {
            prefix,
            cache: RefCell::new(HashMap::new()),
        }
    }

    fn cache(&self, key: &'static str, value: TokenStream) -> TokenStream {
        self.cache.borrow_mut().insert(key, value.clone());
        value
    }

    pub fn constructivism(&self) -> TokenStream {
        self.path("constructivism")
    }

    pub fn path(&self, name: &'static str) -> TokenStream {
        if let Some(cached) = self.cache.borrow().get(name).cloned() {
            return cached;
        }
        let prefix = self.prefix;
        let prefix_ident = format_ident!("{prefix}");
        let global = format_ident!("{name}");
        let local = format_ident!("{prefix}_{name}");
        let lib = if name == prefix {
            quote! { ::#prefix_ident }
        } else {
            quote! { ::#prefix_ident::#global }
        };
        let Some(manifest_path) = std::env::var_os("CARGO_MANIFEST_DIR")
            .map(std::path::PathBuf::from)
            .map(|mut path| {
                path.push("Cargo.toml");
                path
            })
        else {
            return self.cache(name, lib);
        };
        let Ok(manifest) = std::fs::read_to_string(&manifest_path) else {
            return self.cache(name, lib);
        };
        let Ok(manifest) = toml::from_str::<toml::map::Map<String, toml::Value>>(&manifest) else {
            return self.cache(name, lib);
        };

        let Some(pkg) = manifest.get("package") else {
            return self.cache(name, lib);
        };
        let Some(pkg) = pkg.as_table() else {
            return self.cache(name, lib);
        };
        let Some(pkg) = pkg.get("name") else {
            return self.cache(name, lib);
        };
        let Some(pkg) = pkg.as_str() else {
            return self.cache(name, lib);
        };
        if pkg == &format!("{prefix}_{name}") {
            self.cache(name, quote! { crate })
        } else if pkg.starts_with(&format!("{prefix}_mod_")) {
            self.cache(name, lib)
        } else if pkg.starts_with(&format!("{prefix}_")) {
            self.cache(name, quote! { ::#local })
        } else {
            self.cache(name, lib)
        }
    }
}