paperclip_ng/v3/templates/
mod.rs

1use std::ops::Deref;
2
3use crate::v3::{OperationsApiTpl, Property};
4use heck::{ToPascalCase, ToSnakeCase};
5
6/// Support cases for the class names.
7#[derive(Debug, Clone)]
8pub(super) enum ClassCase {
9    PascalCase,
10    SnakeCase,
11}
12impl ClassCase {
13    fn format(&self, name: &str) -> String {
14        match self {
15            ClassCase::PascalCase => name.to_pascal_case(),
16            ClassCase::SnakeCase => name.to_snake_case(),
17        }
18    }
19}
20
21#[derive(Debug, Clone)]
22pub(super) enum ApiTargetFileCfg {
23    /// $prefix/$class_fname/$postfix
24    ClassFileName,
25    /// $prefix/$class/$postfix
26    ClassName,
27}
28
29/// Template file specification for support files.
30#[derive(Debug, Clone)]
31pub(super) struct SuppTemplateFile {
32    gen: GenTemplateFile,
33}
34impl SuppTemplateFile {
35    /// Create a new `Self` by specifying the template path, the target prefix and file extension.
36    pub(super) fn new(template: TemplateFile, target_prefix: &str, target_postfix: &str) -> Self {
37        let gen = GenTemplateFile {
38            template,
39            target_prefix: std::path::PathBuf::from(target_prefix),
40            target_postfix: std::path::PathBuf::from(target_postfix),
41            casing: ClassCase::PascalCase,
42        };
43        Self { gen }
44    }
45}
46impl Deref for SuppTemplateFile {
47    type Target = GenTemplateFile;
48    fn deref(&self) -> &Self::Target {
49        &self.gen
50    }
51}
52
53/// Template file specification for model files.
54#[derive(Debug, Clone)]
55pub(super) struct ModelTemplateFile {
56    gen: GenTemplateFile,
57    extension: String,
58}
59impl ModelTemplateFile {
60    /// Create a new `Self` by specifying the template path, the target prefix and file extension.
61    pub(super) fn new(template: TemplateFile, target_prefix: &str, ext: &str) -> Self {
62        let gen = GenTemplateFile {
63            template,
64            target_prefix: std::path::PathBuf::from(target_prefix),
65            target_postfix: std::path::PathBuf::from("."),
66            casing: ClassCase::SnakeCase,
67        };
68        Self {
69            gen,
70            extension: ext.trim_start_matches('.').into(),
71        }
72    }
73    /// Override the class file case.
74    pub(super) fn with_file_case(mut self, casing: ClassCase) -> Self {
75        self.gen = self.gen.with_class_case(casing);
76        self
77    }
78    /// Generate the path for the model file.
79    pub(super) fn model_path(&self, property: &Property) -> std::path::PathBuf {
80        let model_fname = self.casing.format(property.filename());
81        self.gen
82            .target_prefix
83            .join(model_fname)
84            .with_extension(&self.extension)
85    }
86}
87impl Deref for ModelTemplateFile {
88    type Target = GenTemplateFile;
89    fn deref(&self) -> &Self::Target {
90        &self.gen
91    }
92}
93
94/// Template file specification for api files.
95#[derive(Debug, Clone)]
96pub(super) struct ApiTemplateFile {
97    gen: GenTemplateFile,
98    kind: ApiTargetFileCfg,
99}
100impl ApiTemplateFile {
101    /// Create a new `Self` by specifying the template path, the target prefix and postfix.
102    pub(super) fn new(template: TemplateFile, target_prefix: &str, target_postfix: &str) -> Self {
103        let gen = GenTemplateFile {
104            template,
105            target_prefix: std::path::PathBuf::from(target_prefix),
106            target_postfix: std::path::PathBuf::from(target_postfix),
107            casing: ClassCase::SnakeCase,
108        };
109        Self {
110            gen,
111            kind: ApiTargetFileCfg::ClassFileName,
112        }
113    }
114    /// Override the class file case.
115    pub(super) fn with_class_case(mut self, casing: ClassCase) -> Self {
116        self.gen = self.gen.with_class_case(casing);
117        self
118    }
119    /// Override the class type.
120    pub(super) fn with_class(mut self, kind: ApiTargetFileCfg) -> Self {
121        self.kind = kind;
122        self
123    }
124    /// Generate the path for the api file.
125    pub(super) fn api_path(&self, api: &OperationsApiTpl) -> std::path::PathBuf {
126        let prefix = self.target_prefix();
127        let postfix = self.gen.target_postfix().display().to_string();
128        let class = self.casing.format(match &self.kind {
129            ApiTargetFileCfg::ClassFileName => api.class_filename(),
130            ApiTargetFileCfg::ClassName => api.classname(),
131        });
132        match postfix.starts_with('.') {
133            true => prefix
134                .join(class)
135                .with_extension(postfix.trim_start_matches('.')),
136            false => prefix.join(class).join(postfix),
137        }
138    }
139}
140impl Deref for ApiTemplateFile {
141    type Target = GenTemplateFile;
142    fn deref(&self) -> &Self::Target {
143        &self.gen
144    }
145}
146
147#[derive(Debug, Clone)]
148pub(crate) enum TemplateFile {
149    #[allow(unused)]
150    Path(std::path::PathBuf),
151    Buffer(&'static str),
152    BufferOwned(String),
153}
154impl TemplateFile {
155    /// Get the template raw buffer.
156    pub(super) fn buffer(&self) -> Option<&str> {
157        match self {
158            TemplateFile::Path(_) => None,
159            TemplateFile::Buffer(buffer) => Some(buffer),
160            TemplateFile::BufferOwned(buffer) => Some(buffer.as_str()),
161        }
162    }
163}
164
165/// A generic template file specification.
166#[derive(Debug, Clone)]
167pub(super) struct GenTemplateFile {
168    template: TemplateFile,
169    target_prefix: std::path::PathBuf,
170    target_postfix: std::path::PathBuf,
171    casing: ClassCase,
172}
173impl GenTemplateFile {
174    /// Override the class file case.
175    pub(super) fn with_class_case(mut self, casing: ClassCase) -> Self {
176        self.casing = casing;
177        self
178    }
179    /// Get the template input file.
180    pub(super) fn input(&self) -> &TemplateFile {
181        &self.template
182    }
183    /// Get the target path prefix.
184    pub(super) fn target_prefix(&self) -> &std::path::PathBuf {
185        &self.target_prefix
186    }
187    /// Get the target path postfix.
188    pub(super) fn target_postfix(&self) -> &std::path::PathBuf {
189        &self.target_postfix
190    }
191}
192
193macro_rules! path_or_builtin {
194    ($tpl:ident, $offset:literal) => {
195        match $tpl {
196            None => TemplateFile::Buffer(include_str!($offset)),
197            Some(tpl) => {
198                let path = format!("{}/{}", tpl.display(), $offset);
199                let buffer = std::fs::read_to_string(path)?;
200                TemplateFile::BufferOwned(buffer)
201            }
202        }
203    };
204}
205
206/// The template files for thes default template.
207#[allow(clippy::type_complexity)]
208pub(super) fn default_templates(
209    tpl_path: &Option<std::path::PathBuf>,
210) -> Result<
211    (
212        Vec<ApiTemplateFile>,
213        Vec<ModelTemplateFile>,
214        Vec<SuppTemplateFile>,
215    ),
216    std::io::Error,
217> {
218    let api_templates = vec![
219        // Actix
220        ApiTemplateFile::new(
221            path_or_builtin!(tpl_path, "default/actix/client/api_clients.mustache"),
222            "src/apis",
223            "actix/client/mod.rs",
224        ),
225        ApiTemplateFile::new(
226            path_or_builtin!(tpl_path, "default/actix/server/handlers.mustache"),
227            "src/apis",
228            "actix/server/handlers.rs",
229        ),
230        ApiTemplateFile::new(
231            path_or_builtin!(tpl_path, "default/actix/mod.mustache"),
232            "src/apis",
233            "actix/mod.rs",
234        ),
235        ApiTemplateFile::new(
236            path_or_builtin!(tpl_path, "default/actix/server/api.mustache"),
237            "src/apis",
238            "actix/server/mod.rs",
239        ),
240        // Tower-hyper
241        ApiTemplateFile::new(
242            path_or_builtin!(tpl_path, "default/tower-hyper/mod.mustache"),
243            "src/apis",
244            "tower/mod.rs",
245        ),
246        ApiTemplateFile::new(
247            path_or_builtin!(tpl_path, "default/tower-hyper/client/api_clients.mustache"),
248            "src/apis",
249            "tower/client/mod.rs",
250        ),
251        // Common
252        ApiTemplateFile::new(
253            path_or_builtin!(tpl_path, "default/mod.mustache"),
254            "src/apis",
255            "mod.rs",
256        ),
257        ApiTemplateFile::new(
258            path_or_builtin!(tpl_path, "default/api_doc.mustache"),
259            "docs/apis",
260            ".md",
261        )
262        .with_class_case(ClassCase::PascalCase)
263        .with_class(ApiTargetFileCfg::ClassName),
264    ];
265    let model_templates = vec![
266        ModelTemplateFile::new(
267            path_or_builtin!(tpl_path, "default/model.mustache"),
268            "src/models",
269            ".rs",
270        ),
271        ModelTemplateFile::new(
272            path_or_builtin!(tpl_path, "default/model_doc.mustache"),
273            "docs/models",
274            ".md",
275        )
276        .with_file_case(ClassCase::PascalCase),
277    ];
278    let supporting_templates = vec![
279        SuppTemplateFile::new(
280            path_or_builtin!(tpl_path, "default/model_mod.mustache"),
281            "src/models",
282            "mod.rs",
283        ),
284        SuppTemplateFile::new(
285            path_or_builtin!(tpl_path, "default/actix/client/configuration.mustache"),
286            "src/clients/actix",
287            "configuration.rs",
288        ),
289        SuppTemplateFile::new(
290            path_or_builtin!(
291                tpl_path,
292                "default/tower-hyper/client/configuration.mustache"
293            ),
294            "src/clients/tower",
295            "configuration.rs",
296        ),
297        SuppTemplateFile::new(
298            path_or_builtin!(tpl_path, "default/actix/client/client.mustache"),
299            "src/clients/actix",
300            "mod.rs",
301        ),
302        SuppTemplateFile::new(
303            path_or_builtin!(tpl_path, "default/tower-hyper/client/client.mustache"),
304            "src/clients/tower",
305            "mod.rs",
306        ),
307        SuppTemplateFile::new(
308            path_or_builtin!(tpl_path, "default/tower-hyper/client/body.mustache"),
309            "src/clients/tower",
310            "body.rs",
311        ),
312        SuppTemplateFile::new(
313            path_or_builtin!(tpl_path, "default/api_mod.mustache"),
314            "src/apis",
315            "mod.rs",
316        ),
317        SuppTemplateFile::new(
318            path_or_builtin!(tpl_path, "default/mod_clients.mustache"),
319            "src/clients",
320            "mod.rs",
321        ),
322        SuppTemplateFile::new(
323            path_or_builtin!(tpl_path, "default/lib.mustache"),
324            "src/",
325            "lib.rs",
326        ),
327        SuppTemplateFile::new(
328            path_or_builtin!(tpl_path, "default/actix/server/api_mod.mustache"),
329            "src/apis",
330            "actix_server.rs",
331        ),
332        SuppTemplateFile::new(
333            path_or_builtin!(tpl_path, "default/Cargo.mustache"),
334            "",
335            "Cargo.toml",
336        ),
337        SuppTemplateFile::new(
338            path_or_builtin!(tpl_path, "default/gitignore.mustache"),
339            "",
340            ".gitignore",
341        ),
342        SuppTemplateFile::new(
343            path_or_builtin!(tpl_path, "default/openapi.mustache"),
344            "src/apis",
345            "openapi.yaml",
346        ),
347        SuppTemplateFile::new(
348            path_or_builtin!(tpl_path, "default/README.mustache"),
349            "",
350            "README.md",
351        ),
352    ];
353    Ok((api_templates, model_templates, supporting_templates))
354}