ralph_workflow/cli/handlers/
template_selection.rs1use std::fs;
15use std::io::{self, IsTerminal, Write};
16use std::path::Path;
17
18use crate::logger::Colors;
19use crate::templates::{get_template, list_templates};
20
21pub type TemplateSelectionResult = Option<String>;
26
27pub fn prompt_template_selection(colors: Colors) -> TemplateSelectionResult {
45 if !io::stdin().is_terminal() || !io::stdout().is_terminal() {
47 return None;
48 }
49
50 println!();
51 println!("{}PROMPT.md not found.{}", colors.yellow(), colors.reset());
52 println!();
53 println!("PROMPT.md contains your task specification for the AI agents.");
54 print!("Would you like to create one from a template? [Y/n]: ");
55 if io::stdout().flush().is_err() {
56 return None;
57 }
58
59 let mut input = String::new();
60 match io::stdin().read_line(&mut input) {
61 Ok(0) | Err(_) => return None, Ok(_) => {}
63 }
64
65 let response = input.trim().to_lowercase();
66
67 if response == "n" || response == "no" || response == "skip" {
69 return None;
70 }
71
72 println!();
74 println!("Available templates:");
75
76 let templates = list_templates();
77
78 for (name, description) in &templates {
79 println!(
80 " {}{}{} {}{}{}",
81 colors.cyan(),
82 name,
83 colors.reset(),
84 colors.dim(),
85 description,
86 colors.reset()
87 );
88 }
89 println!();
90
91 print!(
93 "Select template {}[default: feature-spec]{}: ",
94 colors.dim(),
95 colors.reset()
96 );
97 if io::stdout().flush().is_err() {
98 return None;
99 }
100
101 let mut template_input = String::new();
102 match io::stdin().read_line(&mut template_input) {
103 Ok(0) | Err(_) => return None, Ok(_) => {}
105 }
106
107 let template_name = template_input.trim();
108
109 let selected = if template_name.is_empty() {
111 "feature-spec"
112 } else {
113 template_name
114 };
115
116 if get_template(selected).is_none() {
118 println!(
119 "{}Unknown template: '{}'. Using feature-spec as default.{}",
120 colors.yellow(),
121 selected,
122 colors.reset()
123 );
124 return Some("feature-spec".to_string());
125 }
126
127 Some(selected.to_string())
128}
129
130pub fn create_prompt_from_template(template_name: &str, colors: Colors) -> anyhow::Result<()> {
142 let prompt_path = Path::new("PROMPT.md");
143
144 if prompt_path.exists() {
146 println!(
147 "{}PROMPT.md already exists. Skipping creation.{}",
148 colors.yellow(),
149 colors.reset()
150 );
151 return Ok(());
152 }
153
154 let Some(template) = get_template(template_name) else {
156 return Err(anyhow::anyhow!("Template '{template_name}' not found"));
157 };
158
159 let content = template.content();
161 fs::write(prompt_path, content)?;
162
163 println!();
164 println!(
165 "{}Created PROMPT.md from template: {}{}{}",
166 colors.green(),
167 colors.bold(),
168 template_name,
169 colors.reset()
170 );
171 println!();
172 println!(
173 "Template: {}{}{} {}",
174 colors.cyan(),
175 template.name(),
176 colors.reset(),
177 template.description()
178 );
179 println!();
180 println!("Next steps:");
181 println!(" 1. Edit PROMPT.md with your task details");
182 println!(" 2. Run ralph again with your commit message");
183
184 Ok(())
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_get_template_by_name() {
193 assert!(get_template("feature-spec").is_some());
195 assert!(get_template("bug-fix").is_some());
196 assert!(get_template("refactor").is_some());
197 assert!(get_template("test").is_some());
198 assert!(get_template("docs").is_some());
199 assert!(get_template("quick").is_some());
200 assert!(get_template("nonexistent").is_none());
201 }
202
203 #[test]
204 fn test_template_has_required_content() {
205 for (name, _) in list_templates() {
207 if let Some(template) = get_template(name) {
208 let content = template.content();
209 assert!(
210 content.contains("## Goal"),
211 "Template {name} missing Goal section"
212 );
213 assert!(
214 content.contains("Acceptance") || content.contains("## Acceptance Checks"),
215 "Template {name} missing Acceptance section"
216 );
217 }
218 }
219 }
220}