specta-elm 0.0.1

Yey! now your Rust types in Elm ;)
Documentation
use crate::elm::COMMENT_SYMBOL;
use crate::elm::PRELUDE;
use std::fmt::Debug;
use std::{collections::HashMap, path::PathBuf};

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

use crate::{
    Error, Project,
    elm::{EXTENSION, ReferenceExports},
    module::{Module, ModuleImports, ModulePath},
};

// SingleFileExporter {{{
//
#[derive(Debug, Clone)]
pub struct SingleFileExporter {
    target: PathBuf,
    content: Option<String>,
    imports: ModuleImports,
}

impl Exporter for SingleFileExporter {
    fn format_reference(&self, r: &NamedReference, types: &Types) -> String {
        types
            .get(r)
            .expect("reference to a type that wasn't ever registered?")
            .name
            .to_string()
    }
}

impl Registry for SingleFileExporter {
    fn register_module(&mut self, _path: &ModulePath, module_str: String, imports: ModuleImports) {
        self.imports.merge(imports);
        self.content
            .get_or_insert(String::new())
            .push_str(&module_str);
    }

    fn registry(&self) -> FileRegistry {
        // This one doesn't actually have a registry
        // We'll make a 1 file registry just at the end
        let mut registry = FileRegistry::with_capacity(1);
        if let Some(content) = &self.content {
            let elm_module = &self.target.file_prefix().unwrap().to_string_lossy();
            let imports = self.imports.import_core().render();
            let prelude_space = match imports.is_empty() {
                true => "\n",
                false => "\n\n",
            };
            let content = format!(
                "module {elm_module} exposing (..)\n{COMMENT_SYMBOL}{PRELUDE}{prelude_space}{imports}\n{content}"
            );
            let _ = registry.insert(self.target.clone(), content);
        };
        registry
    }
}

//
// }}}
// IntoExporter {{{
pub trait IntoExporter {
    type Output: Exporter;
    fn into(self, project: &Project) -> Self::Output;
}

#[derive(Debug, Clone)]
pub struct SingleFileOutput<'a>(pub &'a str);
impl<'a> IntoExporter for SingleFileOutput<'a> {
    type Output = SingleFileExporter;
    fn into(self, project: &Project) -> Self::Output {
        let target = project
            .source_directories()
            .first()
            .expect("no src?")
            .clone()
            .join(self.0)
            .with_extension(EXTENSION);

        return SingleFileExporter {
            target,
            content: None,
            imports: ModuleImports::new(),
        };
    }
}

#[derive(Debug, Clone)]
pub struct TypesFileOutput;
impl IntoExporter for TypesFileOutput {
    type Output = SingleFileExporter;
    fn into(self, project: &Project) -> Self::Output {
        IntoExporter::into(SingleFileOutput("Types.elm"), project)
    }
}

//
// }}}
// Exporter {{{

pub type FileRegistry = HashMap<PathBuf, String>;

pub trait Registry: Debug {
    fn register_module(
        &mut self,
        path: &ModulePath,
        rendered_module: String,
        imports: ModuleImports,
    );
    fn registry(&self) -> FileRegistry;
}

pub trait Exporter: Registry {
    fn format_reference(&self, r: &NamedReference, types: &Types) -> String;
    fn export(&mut self, types: &Types)
    where
        Self: Sized,
    {
        self.try_export(types).unwrap();
    }
    fn try_export(&mut self, types: &Types) -> Result<(), Error>
    where
        Self: Sized,
    {
        let mut exports = ReferenceExports::default();
        // let refs = References::collect(types);

        let mut root_module = Module::from(types);
        root_module.recursively_render(self, &types, &mut exports)?;

        // eprintln!("{:#?}", root_module);
        // eprintln!("{:#?}", refs);
        // eprintln!("{:#?}", exports);

        for (path, content) in self.registry() {
            if let Some(parent) = path.parent() {
                std::fs::create_dir_all(parent)
                    .map_err(|source| Error::create_dir(parent.to_path_buf(), source))?;
            }
            std::fs::write(&path, content)
                .map_err(|source| Error::write_file(path.clone(), source))?;
        }

        Ok(())
    }
}

// }}}