specta-elm 0.0.1

Yey! now your Rust types in Elm ;)
Documentation
use std::{
    collections::{BTreeMap, HashSet},
    path::PathBuf,
};

use specta::{Types, datatype::NamedDataType};

use crate::{
    Error, Exporter,
    elm::{ElmCoreLibImport, ReferenceExports},
    types::{NDT, primitive::export_internal},
};

const ROOT_MARKER: &'static str = "root";
// Module {{{
#[derive(Debug)]
pub struct Module<'a> {
    pub(crate) ndts: Vec<&'a NamedDataType>,
    children: BTreeMap<&'a str, Module<'a>>,
    path: ModulePath<'a>,
}

// #[derive(Debug)]
// pub struct ModuleMetadata<'a> {
//     path: ModulePath<'a>,
// }

impl<'a> Module<'a> {
    // To export a module is to render it as a file.
    pub fn recursively_render<E: Exporter>(
        &mut self,
        exporter: &mut E,
        types: &'a Types,
        exports: &mut ReferenceExports,
    ) -> Result<(), Error> {
        let mut rendered = String::new();
        let mut imports = ModuleImports::new();

        for ndt in self.ndts.iter().filter(|ndt| ndt.ty.is_some()) {
            let ndt = Into::<NDT>::into(*ndt);
            export_internal(&mut rendered, &mut imports, exporter, types, &ndt)?;
        }

        if !rendered.is_empty() {
            exporter.register_module(&self.path, rendered, imports);
        }

        for (name, module) in &mut self.children {
            // OKAY! Here, we're looping the export!!!
            module.recursively_render(exporter, types, exports)?;
        }

        Ok(())
    }
}

impl<'a> From<&'a Types> for Module<'a> {
    fn from(types: &'a Types) -> Self {
        return types.into_sorted_iter().fold(
            Module {
                ndts: Default::default(),
                children: Default::default(),
                path: Default::default(),
            },
            |mut module, ndt| {
                let mod_path: &'a str = &ndt.module_path.as_ref();
                let mut current_module = &mut module;

                for segment in mod_path.split("::") {
                    current_module =
                        current_module
                            .children
                            .entry(segment)
                            .or_insert_with(|| Module {
                                ndts: Default::default(),
                                children: Default::default(),
                                path: ModulePath::from(mod_path),
                            });
                }

                module.ndts.push(ndt);

                module
            },
        );
    }
}

//
// }}}
// ModuleImports {{{
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub enum ModuleImport {
    Core(ElmCoreLibImport),
    Specta(String, Option<String>),
}
#[derive(Clone, Debug)]
pub struct ModuleImports {
    imports: HashSet<ModuleImport>,
}

impl FromIterator<ModuleImport> for ModuleImports {
    fn from_iter<T: IntoIterator<Item = ModuleImport>>(iter: T) -> Self {
        ModuleImports {
            imports: iter.into_iter().collect(),
        }
    }
}

impl ModuleImports {
    pub fn new() -> Self {
        ModuleImports {
            imports: HashSet::new(),
        }
    }
    pub fn merge(&mut self, other: ModuleImports) {
        self.imports.extend(other.imports);
    }

    pub fn import_core(&self) -> Self {
        self.imports
            .clone()
            .into_iter()
            .filter(|i| match i {
                ModuleImport::Core(_) => true,
                ModuleImport::Specta(_, _) => false,
            })
            .collect()
    }

    pub fn require_core(&mut self, import: ElmCoreLibImport) -> bool {
        self.imports.insert(ModuleImport::Core(import))
    }

    pub fn require_specta(&mut self, import: String) -> bool {
        self.imports.insert(ModuleImport::Specta(import, None))
    }

    pub fn render(&self) -> String {
        let mut s = String::new();
        for import in &self.imports {
            let mut is_core = false;
            let (name, exposed) = match import {
                ModuleImport::Core(elm_core_lib_import) => {
                    is_core = true;
                    (
                        elm_core_lib_import.to_string(),
                        elm_core_lib_import.to_string(),
                    )
                }
                ModuleImport::Specta(name, maybe_exposed) => {
                    let exposed = match maybe_exposed {
                        Some(s) => s.clone(),
                        None => "..".to_string(),
                    };
                    (name.clone(), exposed)
                }
            };
            let import = format!("import {name} exposing ({exposed})");
            if is_core {
                s = import + "\n" + &s
            } else {
                s = s + "\n" + &import
            }
        }
        s
    }
}
//
// }}}
// ModulePath {{{
#[derive(Debug, Clone)]
pub struct ModulePath<'a> {
    id: &'a str,
    segments: Vec<&'a str>,
}

impl<'a> ModulePath<'a> {
    pub fn is_root(&self) -> bool {
        self.id == ROOT_MARKER
    }

    pub fn segments<'l>(&'l self) -> std::slice::Iter<'l, &'a str> {
        self.segments.iter()
    }

    pub fn full(&self) -> PathBuf {
        PathBuf::from(self.id)
    }
}

impl<'a> From<&'a str> for ModulePath<'a> {
    fn from(path: &'a str) -> Self {
        let id = if path.is_empty() { ROOT_MARKER } else { path };
        let segments = id.split("::").collect();

        ModulePath { id, segments }
    }
}

impl<'a> Default for ModulePath<'a> {
    fn default() -> Self {
        let id = ROOT_MARKER;
        let segments = vec![ROOT_MARKER];

        ModulePath { id, segments }
    }
}
//
// }}}