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},
};
#[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 {
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
}
}
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)
}
}
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 mut root_module = Module::from(types);
root_module.recursively_render(self, &types, &mut 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(())
}
}