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