1use std::io::{self, Write};
2
3use serde::Serialize;
4
5use crate::cli::{OutputFormat, ProfileCommand};
6use crate::error::{CliError, Result};
7use crate::models::{Column, DataType, Row, Value};
8use crate::output::formatter::{create_formatter, Formatter};
9
10use super::config::{Profile, ProfileManager};
11
12#[derive(Debug, Serialize)]
13pub struct ProfileListOutput {
14 pub profiles: Vec<ProfileListItem>,
15}
16
17#[derive(Debug, Serialize, Clone)]
18pub struct ProfileListItem {
19 pub name: String,
20 pub data_dir: String,
21 pub is_default: bool,
22}
23
24#[derive(Debug, Serialize)]
25pub struct ProfileShowOutput {
26 pub name: String,
27 pub data_dir: String,
28 pub is_default: bool,
29}
30
31pub fn execute_profile_command(cmd: ProfileCommand, output: OutputFormat) -> Result<()> {
32 match cmd {
33 ProfileCommand::Create { name, data_dir } => {
34 let mut manager = ProfileManager::load()?;
35 manager.create(&name, Profile { data_dir })?;
36 manager.save()
37 }
38 ProfileCommand::List => {
39 let manager = ProfileManager::load()?;
40 let items = build_list_items(&manager);
41 output_profile_list(&items, output)
42 }
43 ProfileCommand::Show { name } => {
44 let manager = ProfileManager::load()?;
45 let show = build_show_output(&manager, &name)?;
46 output_profile_show(&show, output)
47 }
48 ProfileCommand::Delete { name } => {
49 let mut manager = ProfileManager::load()?;
50 manager.delete(&name)?;
51 manager.save()
52 }
53 ProfileCommand::SetDefault { name } => {
54 let mut manager = ProfileManager::load()?;
55 manager.set_default(&name)?;
56 manager.save()
57 }
58 }
59}
60
61fn build_list_items(manager: &ProfileManager) -> Vec<ProfileListItem> {
62 let default_name = manager.default_profile();
63 manager
64 .list()
65 .into_iter()
66 .filter_map(|name| {
67 manager.get(name).map(|profile| ProfileListItem {
68 name: name.to_string(),
69 data_dir: profile.data_dir.clone(),
70 is_default: default_name == Some(name),
71 })
72 })
73 .collect()
74}
75
76fn build_show_output(manager: &ProfileManager, name: &str) -> Result<ProfileShowOutput> {
77 let profile = manager
78 .get(name)
79 .ok_or_else(|| CliError::ProfileNotFound(name.to_string()))?;
80 Ok(ProfileShowOutput {
81 name: name.to_string(),
82 data_dir: profile.data_dir.clone(),
83 is_default: manager.default_profile() == Some(name),
84 })
85}
86
87fn output_profile_list(items: &[ProfileListItem], output: OutputFormat) -> Result<()> {
88 match output {
89 OutputFormat::Table => write_list_table(items),
90 OutputFormat::Json => write_list_json(items),
91 _ => Err(CliError::InvalidArgument(format!(
92 "Unsupported output format for profile list: {:?}",
93 output
94 ))),
95 }
96}
97
98fn output_profile_show(show: &ProfileShowOutput, output: OutputFormat) -> Result<()> {
99 match output {
100 OutputFormat::Table => write_show_table(show),
101 OutputFormat::Json => write_show_json(show),
102 _ => Err(CliError::InvalidArgument(format!(
103 "Unsupported output format for profile show: {:?}",
104 output
105 ))),
106 }
107}
108
109fn write_list_table(items: &[ProfileListItem]) -> Result<()> {
110 let mut writer = io::stdout().lock();
111 write_list_table_to(&mut writer, items)
112}
113
114fn write_list_table_to(writer: &mut dyn Write, items: &[ProfileListItem]) -> Result<()> {
115 let columns = vec![
116 Column::new("Name", DataType::Text),
117 Column::new("Data Dir", DataType::Text),
118 Column::new("Default", DataType::Text),
119 ];
120 let rows: Vec<Row> = items
121 .iter()
122 .map(|item| {
123 Row::new(vec![
124 Value::Text(item.name.clone()),
125 Value::Text(item.data_dir.clone()),
126 Value::Text(if item.is_default { "*" } else { "" }.to_string()),
127 ])
128 })
129 .collect();
130 write_rows_with_formatter_to(writer, OutputFormat::Table, &columns, &rows)
131}
132
133fn write_list_json(items: &[ProfileListItem]) -> Result<()> {
134 let mut writer = io::stdout().lock();
135 write_list_json_to(&mut writer, items)
136}
137
138fn write_list_json_to(writer: &mut dyn Write, items: &[ProfileListItem]) -> Result<()> {
139 let output = ProfileListOutput {
140 profiles: items.to_vec(),
141 };
142 let value = serde_json::to_value(output)?;
143 write_json_value_to(writer, &value)
144}
145
146fn write_show_table(show: &ProfileShowOutput) -> Result<()> {
147 let mut writer = io::stdout().lock();
148 write_show_table_to(&mut writer, show)
149}
150
151fn write_show_table_to(writer: &mut dyn Write, show: &ProfileShowOutput) -> Result<()> {
152 let columns = vec![
153 Column::new("Name", DataType::Text),
154 Column::new("Data Dir", DataType::Text),
155 Column::new("Default", DataType::Text),
156 ];
157 let rows = vec![Row::new(vec![
158 Value::Text(show.name.clone()),
159 Value::Text(show.data_dir.clone()),
160 Value::Text(if show.is_default { "Yes" } else { "No" }.to_string()),
161 ])];
162 let mut formatter = KeyValueFormatter::new();
163 formatter.write_header(writer, &columns)?;
164 for row in &rows {
165 formatter.write_row(writer, row)?;
166 }
167 formatter.write_footer(writer)
168}
169
170fn write_show_json(show: &ProfileShowOutput) -> Result<()> {
171 let mut writer = io::stdout().lock();
172 write_show_json_to(&mut writer, show)
173}
174
175fn write_show_json_to(writer: &mut dyn Write, show: &ProfileShowOutput) -> Result<()> {
176 let columns = vec![
177 Column::new("name", DataType::Text),
178 Column::new("data_dir", DataType::Text),
179 Column::new("is_default", DataType::Bool),
180 ];
181 let rows = vec![Row::new(vec![
182 Value::Text(show.name.clone()),
183 Value::Text(show.data_dir.clone()),
184 Value::Bool(show.is_default),
185 ])];
186 let array = rows_to_json_array(&columns, &rows)?;
187 let obj = array
188 .as_array()
189 .and_then(|items| items.first())
190 .cloned()
191 .unwrap_or(serde_json::Value::Null);
192 write_json_value_to(writer, &obj)
193}
194
195fn write_rows_with_formatter_to(
196 writer: &mut dyn Write,
197 output: OutputFormat,
198 columns: &[Column],
199 rows: &[Row],
200) -> Result<()> {
201 let mut formatter = create_formatter(output);
202 formatter.write_header(writer, columns)?;
203 for row in rows {
204 formatter.write_row(writer, row)?;
205 }
206 formatter.write_footer(writer)
207}
208
209fn rows_to_json_array(columns: &[Column], rows: &[Row]) -> Result<serde_json::Value> {
210 let mut buffer = Vec::new();
211 let mut formatter = create_formatter(OutputFormat::Json);
212 formatter.write_header(&mut buffer, columns)?;
213 for row in rows {
214 formatter.write_row(&mut buffer, row)?;
215 }
216 formatter.write_footer(&mut buffer)?;
217 let value: serde_json::Value = serde_json::from_slice(&buffer)?;
218 Ok(value)
219}
220
221fn write_json_value_to(writer: &mut dyn Write, value: &serde_json::Value) -> Result<()> {
222 serde_json::to_writer_pretty(&mut *writer, value)?;
223 writeln!(writer)?;
224 Ok(())
225}
226
227struct KeyValueFormatter {
228 columns: Vec<String>,
229 rows: Vec<Row>,
230}
231
232impl KeyValueFormatter {
233 fn new() -> Self {
234 Self {
235 columns: Vec::new(),
236 rows: Vec::new(),
237 }
238 }
239}
240
241impl Formatter for KeyValueFormatter {
242 fn write_header(&mut self, _writer: &mut dyn Write, columns: &[Column]) -> Result<()> {
243 self.columns = columns.iter().map(|column| column.name.clone()).collect();
244 Ok(())
245 }
246
247 fn write_row(&mut self, _writer: &mut dyn Write, row: &Row) -> Result<()> {
248 self.rows.push(row.clone());
249 Ok(())
250 }
251
252 fn write_footer(&mut self, writer: &mut dyn Write) -> Result<()> {
253 const LABEL_WIDTH: usize = 9;
254 if let Some(row) = self.rows.first() {
255 for (column, value) in self.columns.iter().zip(row.columns.iter()) {
256 let label = format!("{}:", column);
257 let value_str = format_value(value);
258 writeln!(
259 writer,
260 "{:<width$} {}",
261 label,
262 value_str,
263 width = LABEL_WIDTH
264 )?;
265 }
266 }
267 Ok(())
268 }
269
270 fn supports_streaming(&self) -> bool {
271 false
272 }
273}
274
275fn format_value(value: &Value) -> String {
276 match value {
277 Value::Null => "NULL".to_string(),
278 Value::Bool(b) => {
279 if *b {
280 "Yes".to_string()
281 } else {
282 "No".to_string()
283 }
284 }
285 Value::Int(i) => i.to_string(),
286 Value::Float(f) => format!("{:.6}", f),
287 Value::Text(s) => s.clone(),
288 Value::Bytes(bytes) => format!("{:?}", bytes),
289 Value::Vector(values) => format!("{:?}", values),
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 fn sample_items() -> Vec<ProfileListItem> {
298 vec![
299 ProfileListItem {
300 name: "dev".to_string(),
301 data_dir: "/path/dev".to_string(),
302 is_default: true,
303 },
304 ProfileListItem {
305 name: "prod".to_string(),
306 data_dir: "/path/prod".to_string(),
307 is_default: false,
308 },
309 ]
310 }
311
312 fn sample_show() -> ProfileShowOutput {
313 ProfileShowOutput {
314 name: "dev".to_string(),
315 data_dir: "/path/dev".to_string(),
316 is_default: true,
317 }
318 }
319
320 #[test]
321 fn test_list_table_output() {
322 let items = sample_items();
323 let mut buffer = Vec::new();
324 write_list_table_to(&mut buffer, &items).unwrap();
325 let output = String::from_utf8(buffer).unwrap();
326 assert!(output.contains("Name"));
327 assert!(output.contains("Data Dir"));
328 assert!(output.contains("Default"));
329 assert!(output.contains("dev"));
330 assert!(output.contains("/path/dev"));
331 assert!(output.contains("*"));
332 }
333
334 #[test]
335 fn test_list_json_output() {
336 let items = sample_items();
337 let mut buffer = Vec::new();
338 write_list_json_to(&mut buffer, &items).unwrap();
339 let value: serde_json::Value = serde_json::from_slice(&buffer).unwrap();
340 let profiles = value["profiles"].as_array().unwrap();
341 assert_eq!(profiles.len(), 2);
342 assert!(profiles.iter().any(|profile| {
343 profile["name"] == "dev"
344 && profile["data_dir"] == "/path/dev"
345 && profile["is_default"] == true
346 }));
347 assert!(profiles.iter().any(|profile| {
348 profile["name"] == "prod"
349 && profile["data_dir"] == "/path/prod"
350 && profile["is_default"] == false
351 }));
352 }
353
354 #[test]
355 fn test_show_table_output() {
356 let show = sample_show();
357 let mut buffer = Vec::new();
358 write_show_table_to(&mut buffer, &show).unwrap();
359 let output = String::from_utf8(buffer).unwrap();
360 assert!(output.contains("Name:"));
361 assert!(output.contains("Data Dir:"));
362 assert!(output.contains("Default:"));
363 assert!(output.contains("dev"));
364 assert!(output.contains("/path/dev"));
365 assert!(output.contains("Yes"));
366 }
367
368 #[test]
369 fn test_show_json_output() {
370 let show = sample_show();
371 let mut buffer = Vec::new();
372 write_show_json_to(&mut buffer, &show).unwrap();
373 let value: serde_json::Value = serde_json::from_slice(&buffer).unwrap();
374 assert_eq!(value["name"], "dev");
375 assert_eq!(value["data_dir"], "/path/dev");
376 assert_eq!(value["is_default"], true);
377 }
378}