Skip to main content

specta_elm/
export.rs

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// SingleFileExporter {{{
15//
16#[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        // This one doesn't actually have a registry
43        // We'll make a 1 file registry just at the end
44        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
61//
62// }}}
63// IntoExporter {{{
64pub 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
99//
100// }}}
101// Exporter {{{
102
103pub 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 refs = References::collect(types);
129
130        let mut root_module = Module::from(types);
131        root_module.recursively_render(self, &types, &mut exports)?;
132
133        // eprintln!("{:#?}", root_module);
134        // eprintln!("{:#?}", refs);
135        // eprintln!("{:#?}", exports);
136
137        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// }}}