doido_generators/generators/
helper.rs1use crate::generator::{GeneratedFile, Generator};
2use crate::generators::{to_pascal, to_snake};
3use doido_core::Result;
4
5const HELPERS_MOD_BASE: &str = include_str!("../../templates/new/app/helpers/mod.rs");
7const HELPERS_MOD_PATH: &str = "app/helpers/mod.rs";
8
9pub struct HelperGenerator;
10
11fn helper_type_name(name: &str) -> String {
13 let pascal = to_pascal(name);
14 if pascal.ends_with("Helper") {
15 pascal
16 } else {
17 format!("{pascal}Helper")
18 }
19}
20
21fn helper_module_snake(name: &str) -> String {
23 let snake = to_snake(name);
24 if snake.ends_with("_helper") {
25 snake
26 } else {
27 format!("{snake}_helper")
28 }
29}
30
31pub(crate) fn helper_names(name: &str) -> (String, String) {
33 (helper_module_snake(name), helper_type_name(name))
34}
35
36pub(crate) fn render_helper_content(name: &str, helper_label: &str) -> String {
37 let (snake, pascal) = helper_names(name);
38 crate::templates::get("helper/helper.rs.template")
39 .replace("{pascal}", &pascal)
40 .replace("{snake}", &snake)
41 .replace("{helper_label}", helper_label)
42}
43
44pub(crate) fn register_helper_in_mod(existing: &str, name: &str) -> String {
45 let (snake, pascal) = helper_names(name);
46 register_helper(existing, &snake, &pascal)
47}
48
49pub(crate) fn helpers_mod_path() -> &'static str {
50 HELPERS_MOD_PATH
51}
52
53pub(crate) fn read_helpers_mod() -> String {
54 std::fs::read_to_string(HELPERS_MOD_PATH).unwrap_or_else(|_| HELPERS_MOD_BASE.to_string())
55}
56
57impl Generator for HelperGenerator {
58 fn name(&self) -> &str {
59 "helper"
60 }
61
62 fn generate(&self, args: &[&str]) -> Result<Vec<GeneratedFile>> {
63 let name = args.first().copied().ok_or_else(|| {
64 doido_core::anyhow::anyhow!("helper generator requires a name argument")
65 })?;
66 let snake = helper_module_snake(name);
67 let pascal = helper_type_name(name);
68 let content = render_helper_content(name, &to_snake(name));
69 let test = crate::templates::get("helper/helper_test.rs.template")
70 .replace("{pascal}", &pascal)
71 .replace("{snake}", &snake);
72
73 let helpers_mod = register_helper_in_mod(&read_helpers_mod(), name);
74
75 Ok(vec![
76 GeneratedFile {
77 path: format!("app/helpers/{snake}.rs"),
78 content,
79 },
80 GeneratedFile {
81 path: HELPERS_MOD_PATH.to_string(),
82 content: helpers_mod,
83 },
84 GeneratedFile {
85 path: format!("tests/{snake}_test.rs"),
86 content: test,
87 },
88 ])
89 }
90}
91
92fn register_helper(helpers_mod: &str, snake: &str, pascal: &str) -> String {
95 let decl = format!("pub mod {snake};");
96 if helpers_mod.lines().any(|l| l.trim() == decl) {
97 return helpers_mod.to_string();
98 }
99 let use_line = format!("pub use {snake}::{pascal};");
100 let marker = "@generated-helpers";
101 let mut lines: Vec<String> = helpers_mod.lines().map(String::from).collect();
102 match lines.iter().position(|l| l.contains(marker)) {
103 Some(i) => {
104 lines.insert(i, use_line);
105 lines.insert(i, decl);
106 }
107 None => {
108 lines.push(decl);
109 lines.push(use_line);
110 }
111 }
112 let mut out = lines.join("\n");
113 out.push('\n');
114 out
115}