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