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";
#[derive(Debug)]
pub struct Module<'a> {
pub(crate) ndts: Vec<&'a NamedDataType>,
children: BTreeMap<&'a str, Module<'a>>,
path: ModulePath<'a>,
}
impl<'a> Module<'a> {
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 {
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
},
);
}
}
#[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
}
}
#[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 }
}
}