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"#;
62
63pub const TEMPLATE_PACK_STATUS: &str = r#"{% if message %}[message]{{ message }}[/message]
67{% endif %}{% if dry_run %}[dry-run] (dry run — no changes made)[/dry-run]
68{% endif %}{% for pack in packs %}[pack-name]{{ pack.name }}[/pack-name]
69{% for file in pack.files %} {{ file.name | col(24) }} [handler-symbol]{{ file.symbol }}[/handler-symbol] [description]{{ file.description | col(30) }}[/description] [{{ file.status }}]{{ file.status_label }}[/{{ file.status }}]
70{% endfor %}
71{% endfor %}"#;
72
73pub const TEMPLATE_LIST: &str = r#"{% for pack in packs %}{{ pack.name }}{% if pack.ignored %} [dim](ignored)[/dim]{% endif %}
75{% endfor %}"#;
76
77pub const TEMPLATE_MESSAGE: &str = r#"{% if message %}[message]{{ message }}[/message]
79{% endif %}{% for line in details %} {{ line }}
80{% endfor %}"#;
81
82pub fn create_theme() -> Theme {
86 Theme::from_yaml(THEME_YAML).expect("built-in theme YAML must be valid")
87}
88
89pub fn create_renderer() -> Renderer {
91 let theme = create_theme();
92 let mut renderer = Renderer::new(theme).expect("renderer creation must succeed");
93 renderer
94 .add_template("pack-status", TEMPLATE_PACK_STATUS)
95 .unwrap();
96 renderer.add_template("list", TEMPLATE_LIST).unwrap();
97 renderer.add_template("message", TEMPLATE_MESSAGE).unwrap();
98 renderer
99}
100
101pub fn render<T: serde::Serialize>(
106 template_name: &str,
107 data: &T,
108 mode: OutputMode,
109) -> Result<String> {
110 if matches!(mode, OutputMode::Json) {
111 return serde_json::to_string_pretty(data)
112 .map_err(|e| crate::DodotError::Other(format!("JSON serialization failed: {e}")));
113 }
114
115 let theme = create_theme();
116 let template = match template_name {
117 "pack-status" => TEMPLATE_PACK_STATUS,
118 "list" => TEMPLATE_LIST,
119 "message" => TEMPLATE_MESSAGE,
120 other => {
121 return Err(crate::DodotError::Other(format!(
122 "unknown template: {other}"
123 )))
124 }
125 };
126
127 render_with_output(template, data, &theme, mode)
128 .map_err(|e| crate::DodotError::Other(format!("render failed: {e}")))
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn theme_parses_without_error() {
137 let _theme = create_theme();
138 }
139
140 #[test]
141 fn renderer_creates_with_all_templates() {
142 let _renderer = create_renderer();
143 }
144
145 #[test]
146 fn render_pack_status_text_mode() {
147 use serde::Serialize;
148
149 #[derive(Serialize)]
150 struct Data {
151 message: Option<String>,
152 dry_run: bool,
153 packs: Vec<Pack>,
154 }
155 #[derive(Serialize)]
156 struct Pack {
157 name: String,
158 files: Vec<File>,
159 }
160 #[derive(Serialize)]
161 struct File {
162 name: String,
163 symbol: String,
164 description: String,
165 status: String,
166 status_label: String,
167 }
168
169 let data = Data {
170 message: None,
171 dry_run: false,
172 packs: vec![Pack {
173 name: "vim".into(),
174 files: vec![File {
175 name: "vimrc".into(),
176 symbol: "➞".into(),
177 description: "~/.vimrc".into(),
178 status: "deployed".into(),
179 status_label: "deployed".into(),
180 }],
181 }],
182 };
183
184 let output = render("pack-status", &data, OutputMode::Text).unwrap();
185 assert!(output.contains("vim"));
186 assert!(output.contains("vimrc"));
187 assert!(output.contains("deployed"));
188 }
189
190 #[test]
191 fn json_mode_produces_json() {
192 use serde::Serialize;
193
194 #[derive(Serialize)]
195 struct Data {
196 name: String,
197 }
198
199 let data = Data {
200 name: "test".into(),
201 };
202
203 let output = render("list", &data, OutputMode::Json).unwrap();
204 assert!(output.contains("\"name\""));
205 assert!(output.contains("\"test\""));
206 }
207}