1use crate::elm::COMMENT_SYMBOL;
2use crate::elm::PRELUDE;
3use std::fmt::Debug;
4use std::{collections::HashMap, path::PathBuf};
5
6use specta::{Types, datatype::NamedReference};
7
8use crate::{
9 Error, Project,
10 elm::{EXTENSION, ReferenceExports},
11 module::{Module, ModuleImports, ModulePath},
12};
13
14#[derive(Debug, Clone)]
17pub struct SingleFileExporter {
18 target: PathBuf,
19 content: Option<String>,
20 imports: ModuleImports,
21}
22
23impl Exporter for SingleFileExporter {
24 fn format_reference(&self, r: &NamedReference, types: &Types) -> String {
25 types
26 .get(r)
27 .expect("reference to a type that wasn't ever registered?")
28 .name
29 .to_string()
30 }
31}
32
33impl Registry for SingleFileExporter {
34 fn register_module(&mut self, _path: &ModulePath, module_str: String, imports: ModuleImports) {
35 self.imports.merge(imports);
36 self.content
37 .get_or_insert(String::new())
38 .push_str(&module_str);
39 }
40
41 fn registry(&self) -> FileRegistry {
42 let mut registry = FileRegistry::with_capacity(1);
45 if let Some(content) = &self.content {
46 let elm_module = &self.target.file_prefix().unwrap().to_string_lossy();
47 let imports = self.imports.import_core().render();
48 let prelude_space = match imports.is_empty() {
49 true => "\n",
50 false => "\n\n",
51 };
52 let content = format!(
53 "module {elm_module} exposing (..)\n{COMMENT_SYMBOL}{PRELUDE}{prelude_space}{imports}\n{content}"
54 );
55 let _ = registry.insert(self.target.clone(), content);
56 };
57 registry
58 }
59}
60
61pub trait IntoExporter {
65 type Output: Exporter;
66 fn into(self, project: &Project) -> Self::Output;
67}
68
69#[derive(Debug, Clone)]
70pub struct SingleFileOutput<'a>(pub &'a str);
71impl<'a> IntoExporter for SingleFileOutput<'a> {
72 type Output = SingleFileExporter;
73 fn into(self, project: &Project) -> Self::Output {
74 let target = project
75 .source_directories()
76 .first()
77 .expect("no src?")
78 .clone()
79 .join(self.0)
80 .with_extension(EXTENSION);
81
82 return SingleFileExporter {
83 target,
84 content: None,
85 imports: ModuleImports::new(),
86 };
87 }
88}
89
90#[derive(Debug, Clone)]
91pub struct TypesFileOutput;
92impl IntoExporter for TypesFileOutput {
93 type Output = SingleFileExporter;
94 fn into(self, project: &Project) -> Self::Output {
95 IntoExporter::into(SingleFileOutput("Types.elm"), project)
96 }
97}
98
99pub type FileRegistry = HashMap<PathBuf, String>;
104
105pub trait Registry: Debug {
106 fn register_module(
107 &mut self,
108 path: &ModulePath,
109 rendered_module: String,
110 imports: ModuleImports,
111 );
112 fn registry(&self) -> FileRegistry;
113}
114
115pub trait Exporter: Registry {
116 fn format_reference(&self, r: &NamedReference, types: &Types) -> String;
117 fn export(&mut self, types: &Types)
118 where
119 Self: Sized,
120 {
121 self.try_export(types).unwrap();
122 }
123 fn try_export(&mut self, types: &Types) -> Result<(), Error>
124 where
125 Self: Sized,
126 {
127 let mut exports = ReferenceExports::default();
128 let mut root_module = Module::from(types);
131 root_module.recursively_render(self, &types, &mut exports)?;
132
133 for (path, content) in self.registry() {
138 if let Some(parent) = path.parent() {
139 std::fs::create_dir_all(parent)
140 .map_err(|source| Error::create_dir(parent.to_path_buf(), source))?;
141 }
142 std::fs::write(&path, content)
143 .map_err(|source| Error::write_file(path.clone(), source))?;
144 }
145
146 Ok(())
147 }
148}
149
150