weld_codegen/
docgen.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    path::Path,
4};
5
6use atelier_core::model::Model;
7
8use crate::format::NullFormatter;
9use crate::{
10    config::{LanguageConfig, OutputFile, OutputLanguage},
11    error::Result,
12    format::SourceFormatter,
13    gen::{to_json, CodeGen},
14    render::Renderer,
15    writer::Writer,
16    Bytes, Error, JsonValue, ParamMap,
17};
18
19// default page name for doc template
20const DOC_TEMPLATE: &str = "namespace_doc";
21
22/// Default templates
23pub const HTML_TEMPLATES: &[(&str, &str)] = &[
24    ("page_base", include_str!("../templates/html/page_base.hbs")),
25    (
26        "namespace_doc",
27        include_str!("../templates/html/namespace_doc.hbs"),
28    ),
29];
30
31#[derive(Debug, Default)]
32pub(crate) struct DocGen {}
33
34impl CodeGen for DocGen {
35    fn output_language(&self) -> OutputLanguage {
36        OutputLanguage::Html
37    }
38
39    /// Initialize code generator and renderer for language output.j
40    /// This hook is called before any code is generated and can be used to initialize code generator
41    /// and/or perform additional processing before output files are created.
42    fn init(
43        &mut self,
44        model: Option<&Model>,
45        lc: &LanguageConfig,
46        output_dir: Option<&Path>,
47        renderer: &mut Renderer,
48    ) -> std::result::Result<(), Error> {
49        let output_dir = match output_dir {
50            None => return Ok(()),
51            Some(d) => d,
52        };
53        let model = match model {
54            None => return Ok(()),
55            Some(model) => model,
56        };
57        for t in HTML_TEMPLATES.iter() {
58            renderer.add_template(*t)?;
59        }
60        let mut params: BTreeMap<String, JsonValue> = to_json(&lc.parameters)?;
61        let json_model = atelier_json::model_to_json(model);
62        params.insert("model".to_string(), json_model);
63
64        let minified = match params.get("minified") {
65            Some(JsonValue::Bool(b)) => *b,
66            _ => false,
67        };
68        params.insert("minified".to_string(), JsonValue::Bool(minified));
69        let doc_template = match params.get("doc_template") {
70            Some(JsonValue::String(s)) => s.clone(),
71            _ => DOC_TEMPLATE.to_string(),
72        };
73
74        // renderer is already initialized with "model" as the json-ast model,
75        // but Model has a more convenient way to get namespaces.
76        // Get list of namespaces from top level shapes, using BTreeSet to remove duplicates
77        let namespaces = model
78            .namespaces()
79            .iter()
80            .map(|id| id.to_string())
81            .collect::<BTreeSet<String>>();
82
83        for ns in namespaces.iter() {
84            let output_file =
85                output_dir.join(format!("{}.html", crate::strings::to_snake_case(ns)));
86            let mut out = std::fs::File::create(&output_file).map_err(|e| {
87                Error::Io(format!(
88                    "writing output file {}: {}",
89                    output_file.display(),
90                    e
91                ))
92            })?;
93            params.insert("namespace".to_string(), JsonValue::String(ns.clone()));
94            params.insert("title".to_string(), JsonValue::String(ns.clone()));
95            renderer.render(&doc_template, &params, &mut out)?;
96        }
97
98        Ok(())
99    }
100
101    /// DocGen doesn't do per-file generation so this is a no-op
102    fn generate_file(
103        &mut self,
104        _w: &mut Writer,
105        _model: &Model,
106        _file_config: &OutputFile,
107        _params: &ParamMap,
108    ) -> Result<Bytes> {
109        Ok(Bytes::new())
110    }
111
112    // never called
113    fn get_file_extension(&self) -> &'static str {
114        "html"
115    }
116
117    fn to_method_name_case(&self, name: &str) -> String {
118        name.into()
119    }
120    fn to_field_name_case(&self, name: &str) -> String {
121        name.into()
122    }
123    fn to_type_name_case(&self, name: &str) -> String {
124        name.into()
125    }
126    fn source_formatter(&self, _: Vec<String>) -> Result<Box<dyn SourceFormatter>> {
127        Ok(Box::<NullFormatter>::default())
128    }
129}