doido_generators/generators/
controller.rs1use crate::generator::{GeneratedFile, Generator};
2use crate::generators::helper::{
3 helper_names, helpers_mod_path, read_helpers_mod, register_helper_in_mod, render_helper_content,
4};
5use crate::generators::{to_pascal, to_snake};
6use doido_core::Result;
7
8const CONTROLLERS_MOD_BASE: &str = include_str!("../../templates/new/app/controllers/mod.rs");
10pub(crate) const CONTROLLERS_MOD_PATH: &str = "app/controllers/mod.rs";
11
12const ROUTES_BASE: &str = include_str!("../../templates/new/config/routes.rs");
14const ROUTES_PATH: &str = "config/routes.rs";
15
16pub struct ControllerGenerator;
17
18pub(crate) fn read_routes() -> String {
19 std::fs::read_to_string(ROUTES_PATH).unwrap_or_else(|_| ROUTES_BASE.to_string())
20}
21
22pub(crate) fn inject_controller_route(routes: &str, snake: &str, controller: &str) -> String {
25 let route = format!("get!(\"/{snake}\", {controller}::index);");
26 if routes.contains(&route) {
27 return routes.to_string();
28 }
29
30 let use_line = format!("use crate::controllers::{controller};");
31 let mut lines: Vec<String> = routes.lines().map(String::from).collect();
32
33 if !routes.contains(&use_line) {
34 let pos = lines
35 .iter()
36 .rposition(|l| l.contains("use crate::controllers"))
37 .map(|i| i + 1)
38 .unwrap_or(0);
39 lines.insert(pos, use_line);
40 }
41
42 if let Some(open) = lines.iter().position(|l| l.contains("routes!")) {
43 if let Some(close_rel) = lines[open..].iter().position(|l| l.trim() == "}") {
44 let close = open + close_rel;
45 lines.insert(close, format!(" {route}"));
46 }
47 }
48
49 let mut out = lines.join("\n");
50 out.push('\n');
51 out
52}
53
54pub(crate) fn read_controllers_mod() -> String {
55 std::fs::read_to_string(CONTROLLERS_MOD_PATH)
56 .unwrap_or_else(|_| CONTROLLERS_MOD_BASE.to_string())
57}
58
59pub(crate) fn register_controller_in_mod(
62 controllers_mod: &str,
63 module_stem: &str,
64 controller: &str,
65) -> String {
66 let module = format!("{module_stem}_controller");
67 let decl = format!("mod {module};");
68 if controllers_mod.lines().any(|l| l.trim() == decl) {
69 return controllers_mod.to_string();
70 }
71 let mut out = controllers_mod.trim_end().to_string();
72 out.push('\n');
73 out.push_str(&format!("mod {module};\n"));
74 out.push_str(&format!("pub use {module}::{controller};\n"));
75 out
76}
77
78impl Generator for ControllerGenerator {
79 fn name(&self) -> &str {
80 "controller"
81 }
82
83 fn generate(&self, args: &[&str]) -> Result<Vec<GeneratedFile>> {
84 let name = args.first().copied().ok_or_else(|| {
85 doido_core::anyhow::anyhow!("controller generator requires a name argument")
86 })?;
87 let snake = to_snake(name);
88 let pascal = to_pascal(name);
89 let (_, helper_type) = helper_names(name);
90 let content = crate::templates::get("controller/controller.rs.template")
91 .replace("{pascal}", &pascal)
92 .replace("{snake}", &snake)
93 .replace("{Helper}", &helper_type);
94 let test = crate::templates::get("controller/controller_test.rs.template")
95 .replace("{pascal}", &pascal)
96 .replace("{snake}", &snake);
97 let (helper_snake, _) = helper_names(name);
98 let helpers_mod = register_helper_in_mod(&read_helpers_mod(), name);
99 let controller_type = format!("{pascal}Controller");
100 let controllers_mod =
101 register_controller_in_mod(&read_controllers_mod(), &snake, &controller_type);
102 let routes = inject_controller_route(&read_routes(), &snake, &controller_type);
103 Ok(vec![
104 GeneratedFile {
105 path: format!("app/helpers/{helper_snake}.rs"),
106 content: render_helper_content(name, &snake),
107 },
108 GeneratedFile {
109 path: helpers_mod_path().to_string(),
110 content: helpers_mod,
111 },
112 GeneratedFile {
113 path: format!("app/controllers/{snake}_controller.rs"),
114 content,
115 },
116 GeneratedFile {
117 path: CONTROLLERS_MOD_PATH.to_string(),
118 content: controllers_mod,
119 },
120 GeneratedFile {
121 path: ROUTES_PATH.to_string(),
122 content: routes,
123 },
124 GeneratedFile {
125 path: format!("tests/{snake}_controller_test.rs"),
126 content: test,
127 },
128 ])
129 }
130}