Skip to main content

cliclack_yaml/steps/
multi_select.rs

1use std::collections::HashMap;
2use anyhow::Result;
3use serde::{Deserialize, Serialize};
4use crate::{PromptStepType, replace_variables};
5
6#[derive(Debug, Deserialize, Serialize, Clone)]
7pub struct MultiSelectItem {
8    pub value: String,
9    pub label: String,
10    pub hint: Option<String>,
11}
12
13#[derive(Debug, Deserialize, Serialize, Clone)]
14#[serde(untagged)]
15pub enum MultiSelectItems {
16    Static(Vec<MultiSelectItem>),
17    Variable(String),
18}
19
20/// Handle MultiSelect prompt step
21pub fn handle_multi_select(
22    step_type: &PromptStepType, 
23    context: &mut HashMap<String, String>,
24    last_input: &mut Option<String>
25) -> Result<()> {
26    if let PromptStepType::MultiSelect { prompt, items, output, required } = step_type {
27        let mut multiselect_builder = cliclack::multiselect(prompt);
28        
29        // Determine which items to use
30        let resolved_items = match items {
31            MultiSelectItems::Static(static_items) => static_items.clone(),
32            MultiSelectItems::Variable(variable_ref) => {
33                // Replace variables in the variable reference string
34                let resolved_var = replace_variables(variable_ref, context, last_input);
35                
36                // The resolved_var should now contain the actual JSON data
37                // First try to parse as an array of MultiSelectItem objects
38                if let Ok(items) = serde_json::from_str::<Vec<MultiSelectItem>>(&resolved_var) {
39                    items
40                } else if let Ok(config_map) = serde_json::from_str::<HashMap<String, String>>(&resolved_var) {
41                    // Fallback to HashMap for backward compatibility
42                    config_map.into_iter().map(|(key, value)| {
43                        let hint = if value.len() > 50 { 
44                            format!("{}...", &value[..47]) 
45                        } else { 
46                            value 
47                        };
48                        MultiSelectItem {
49                            value: key.clone(),
50                            label: key,
51                            hint: Some(hint),
52                        }
53                    }).collect()
54                } else {
55                    Vec::new()
56                }
57            }
58        };
59        
60        // Add items to multiselect builder
61        if resolved_items.is_empty() {
62            return Err(anyhow::anyhow!("No items available for multiselect"));
63        }
64        
65        for item in &resolved_items {
66            let hint = item.hint.as_deref().unwrap_or("");
67            multiselect_builder = multiselect_builder.item(&item.value, &item.label, hint);
68        }
69        
70        // Apply required validation if specified
71        if let Some(is_required) = required {
72            if *is_required {
73                multiselect_builder = multiselect_builder.required(true);
74            }
75        }
76        
77        let selected_values: Vec<String> = multiselect_builder.interact()?.iter().map(|s| s.to_string()).collect();
78        
79        // Store output in context if specified
80        if let Some(output_key) = output {
81            // Join selected values with commas for storage
82            let combined_values = selected_values.join(",");
83            context.insert(output_key.clone(), combined_values);
84            
85            // Also store individual selections with indexed keys for potential use
86            for (index, value) in selected_values.iter().enumerate() {
87                context.insert(format!("{}_{}", output_key, index), value.clone());
88            }
89            
90            // Store count of selected items
91            context.insert(format!("{}_count", output_key), selected_values.len().to_string());
92        }
93    }
94    Ok(())
95}