1use standout_render::{render_with_output, OutputMode, Renderer, Theme};
8
9use crate::Result;
10
11const THEME_YAML: &str = r#"
16pack-name:
17 bold: true
18 fg: blue
19
20filename:
21 fg: white
22
23handler-symbol:
24 bold: true
25 fg: yellow
26
27description:
28 dim: true
29
30deployed:
31 fg: green
32
33pending:
34 fg: magenta
35
36error:
37 fg: red
38 bold: true
39
40broken:
41 fg: red
42
43stale:
44 fg: yellow
45
46warning:
47 fg: yellow
48
49message:
50 fg: cyan
51
52dim:
53 dim: true
54
55header:
56 bold: true
57
58dry-run:
59 fg: yellow
60 italic: true
61
62conflict-banner:
63 fg: white
64 bg: red
65 bold: true
66
67conflict-header:
68 fg: white
69 bg: red
70 bold: true
71
72conflict-target:
73 fg: red
74 bold: true
75
76conflict-pack:
77 fg: red
78
79conflict-hint:
80 dim: true
81
82ignored-pack:
83 dim: true
84 italic: true
85
86group-banner-deployed:
87 fg: green
88 bold: true
89
90group-banner-pending:
91 fg: yellow
92 bold: true
93
94group-banner-error:
95 fg: red
96 bold: true
97
98group-banner-ignored:
99 dim: true
100 bold: true
101"#;
102
103pub const TEMPLATE_PACK_STATUS: &str = include_str!("../templates/pack-status.jinja");
112
113pub const TEMPLATE_LIST: &str = include_str!("../templates/list.jinja");
115
116pub const TEMPLATE_MESSAGE: &str = include_str!("../templates/message.jinja");
118
119pub const TEMPLATE_PROBE: &str = include_str!("../templates/probe.jinja");
122
123pub fn create_theme() -> Theme {
127 Theme::from_yaml(THEME_YAML).expect("built-in theme YAML must be valid")
128}
129
130pub fn create_renderer() -> Renderer {
132 let theme = create_theme();
133 let mut renderer = Renderer::new(theme).expect("renderer creation must succeed");
134 renderer
135 .add_template("pack-status", TEMPLATE_PACK_STATUS)
136 .unwrap();
137 renderer.add_template("list", TEMPLATE_LIST).unwrap();
138 renderer.add_template("message", TEMPLATE_MESSAGE).unwrap();
139 renderer.add_template("probe", TEMPLATE_PROBE).unwrap();
140 renderer
141}
142
143pub fn render<T: serde::Serialize>(
148 template_name: &str,
149 data: &T,
150 mode: OutputMode,
151) -> Result<String> {
152 if matches!(mode, OutputMode::Json) {
153 return serde_json::to_string_pretty(data)
154 .map_err(|e| crate::DodotError::Other(format!("JSON serialization failed: {e}")));
155 }
156
157 let theme = create_theme();
158 let template = match template_name {
159 "pack-status" => TEMPLATE_PACK_STATUS,
160 "list" => TEMPLATE_LIST,
161 "message" => TEMPLATE_MESSAGE,
162 "probe" => TEMPLATE_PROBE,
163 other => {
164 return Err(crate::DodotError::Other(format!(
165 "unknown template: {other}"
166 )))
167 }
168 };
169
170 render_with_output(template, data, &theme, mode)
171 .map_err(|e| crate::DodotError::Other(format!("render failed: {e}")))
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn theme_parses_without_error() {
180 let _theme = create_theme();
181 }
182
183 #[test]
184 fn renderer_creates_with_all_templates() {
185 let _renderer = create_renderer();
186 }
187
188 #[test]
189 fn render_pack_status_text_mode() {
190 use serde::Serialize;
191
192 #[derive(Serialize)]
193 struct Data {
194 message: Option<String>,
195 dry_run: bool,
196 packs: Vec<Pack>,
197 }
198 #[derive(Serialize)]
199 struct Pack {
200 name: String,
201 files: Vec<File>,
202 }
203 #[derive(Serialize)]
204 struct File {
205 name: String,
206 symbol: String,
207 description: String,
208 status: String,
209 status_label: String,
210 }
211
212 let data = Data {
213 message: None,
214 dry_run: false,
215 packs: vec![Pack {
216 name: "vim".into(),
217 files: vec![File {
218 name: "vimrc".into(),
219 symbol: "➞".into(),
220 description: "~/.vimrc".into(),
221 status: "deployed".into(),
222 status_label: "deployed".into(),
223 }],
224 }],
225 };
226
227 let output = render("pack-status", &data, OutputMode::Text).unwrap();
228 assert!(output.contains("vim"));
229 assert!(output.contains("vimrc"));
230 assert!(output.contains("deployed"));
231 }
232
233 #[test]
234 fn json_mode_produces_json() {
235 use serde::Serialize;
236
237 #[derive(Serialize)]
238 struct Data {
239 name: String,
240 }
241
242 let data = Data {
243 name: "test".into(),
244 };
245
246 let output = render("list", &data, OutputMode::Json).unwrap();
247 assert!(output.contains("\"name\""));
248 assert!(output.contains("\"test\""));
249 }
250}