envx_cli/
list.rs

1use color_eyre::Result;
2use color_eyre::eyre::eyre;
3use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table};
4use console::{Term, style};
5use envx_core::EnvVarManager;
6
7/// Handles the list command to display environment variables with various formatting options.
8///
9/// # Arguments
10/// * `source` - Optional filter by source (system, user, process, shell)
11/// * `query` - Optional search query to filter variables
12/// * `format` - Output format (table, json, simple, compact)
13/// * `sort` - Sort order (name, value, source)
14/// * `names_only` - Whether to display only variable names
15/// * `limit` - Optional limit on number of variables to display
16/// * `stats` - Whether to display statistics
17///
18/// # Errors
19/// Returns an error if:
20/// - Environment variables cannot be loaded
21/// - Invalid source type is provided
22/// - JSON serialization fails
23pub fn handle_list_command(
24    source: Option<&str>,
25    query: Option<&str>,
26    format: &str,
27    sort: &str,
28    names_only: bool,
29    limit: Option<usize>,
30    stats: bool,
31) -> Result<()> {
32    let mut manager = EnvVarManager::new();
33    manager.load_all()?;
34
35    // Get filtered variables
36    let mut vars = if let Some(q) = &query {
37        manager.search(q)
38    } else if let Some(src) = source {
39        let source_filter = match src {
40            "system" => envx_core::EnvVarSource::System,
41            "user" => envx_core::EnvVarSource::User,
42            "process" => envx_core::EnvVarSource::Process,
43            "shell" => envx_core::EnvVarSource::Shell,
44            _ => return Err(eyre!("Invalid source: {}", src)),
45        };
46        manager.filter_by_source(&source_filter)
47    } else {
48        manager.list()
49    };
50
51    // Sort variables
52    match sort {
53        "name" => vars.sort_by(|a, b| a.name.cmp(&b.name)),
54        "value" => vars.sort_by(|a, b| a.value.cmp(&b.value)),
55        "source" => vars.sort_by(|a, b| format!("{:?}", a.source).cmp(&format!("{:?}", b.source))),
56        _ => {}
57    }
58
59    // Apply limit if specified
60    let total_count = vars.len();
61    if let Some(lim) = limit {
62        vars.truncate(lim);
63    }
64
65    // Show statistics if requested
66    if stats || (format == "table" && !names_only) {
67        print_statistics(&manager, &vars, total_count, query, source);
68    }
69
70    // Handle names_only flag
71    if names_only {
72        for var in vars {
73            println!("{}", var.name);
74        }
75        return Ok(());
76    }
77
78    // Format output
79    match format {
80        "json" => {
81            println!("{}", serde_json::to_string_pretty(&vars)?);
82        }
83        "simple" => {
84            for var in vars {
85                println!("{} = {}", style(&var.name).cyan(), var.value);
86            }
87        }
88        "compact" => {
89            for var in vars {
90                let source_str = format_source_compact(&var.source);
91                println!(
92                    "{} {} = {}",
93                    source_str,
94                    style(&var.name).bright(),
95                    style(truncate_value(&var.value, 60)).dim()
96                );
97            }
98        }
99        _ => {
100            print_table(vars, limit.is_some());
101        }
102    }
103
104    // Show limit notice
105    if let Some(lim) = limit {
106        if total_count > lim {
107            println!(
108                "\n{}",
109                style(format!(
110                    "Showing {lim} of {total_count} total variables. Use --limit to see more."
111                ))
112                .yellow()
113            );
114        }
115    }
116
117    Ok(())
118}
119
120fn print_statistics(
121    manager: &EnvVarManager,
122    filtered_vars: &[&envx_core::EnvVar],
123    total_count: usize,
124    query: Option<&str>,
125    source: Option<&str>,
126) {
127    let _term = Term::stdout();
128
129    // Count by source
130    let system_count = manager.filter_by_source(&envx_core::EnvVarSource::System).len();
131    let user_count = manager.filter_by_source(&envx_core::EnvVarSource::User).len();
132    let process_count = manager.filter_by_source(&envx_core::EnvVarSource::Process).len();
133    let shell_count = manager.filter_by_source(&envx_core::EnvVarSource::Shell).len();
134
135    // Header
136    println!("{}", style("═".repeat(60)).blue().bold());
137    println!("{}", style("Environment Variables Summary").cyan().bold());
138    println!("{}", style("═".repeat(60)).blue().bold());
139
140    // Filter info
141    if query.is_some() || source.is_some() {
142        print!("  {} ", style("Filter:").yellow());
143        if let Some(q) = query {
144            print!("query='{}' ", style(q).green());
145        }
146        if let Some(s) = source {
147            print!("source={} ", style(s).green());
148        }
149        println!();
150        println!(
151            "  {} {}/{} variables",
152            style("Showing:").yellow(),
153            style(filtered_vars.len()).green().bold(),
154            total_count
155        );
156    } else {
157        println!(
158            "  {} {} variables",
159            style("Total:").yellow(),
160            style(total_count).green().bold()
161        );
162    }
163
164    println!();
165    println!("  {} By Source:", style("►").cyan());
166
167    // Source breakdown with visual bars
168    let max_count = system_count.max(user_count).max(process_count).max(shell_count);
169    let bar_width = 30;
170
171    print_source_bar("System", system_count, max_count, bar_width, "red");
172    print_source_bar("User", user_count, max_count, bar_width, "yellow");
173    print_source_bar("Process", process_count, max_count, bar_width, "green");
174    print_source_bar("Shell", shell_count, max_count, bar_width, "cyan");
175
176    println!("{}", style("─".repeat(60)).blue());
177    println!();
178}
179
180fn print_source_bar(label: &str, count: usize, max: usize, width: usize, color: &str) {
181    let filled = if max > 0 { (count * width / max).max(1) } else { 0 };
182
183    let bar = "█".repeat(filled);
184    let empty = "░".repeat(width - filled);
185
186    let colored_bar = match color {
187        "red" => style(bar).red(),
188        "yellow" => style(bar).yellow(),
189        "green" => style(bar).green(),
190        "cyan" => style(bar).cyan(),
191        _ => style(bar).white(),
192    };
193
194    println!(
195        "    {:10} {} {}{} {}",
196        style(label).bold(),
197        colored_bar,
198        style(empty).dim(),
199        style(format!(" {count:4}")).bold(),
200        style("vars").dim()
201    );
202}
203
204fn print_table(vars: Vec<&envx_core::EnvVar>, _is_limited: bool) {
205    if vars.is_empty() {
206        println!("{}", style("No environment variables found.").yellow());
207    }
208
209    let mut table = Table::new();
210
211    // Configure table style
212    table
213        .set_content_arrangement(ContentArrangement::Dynamic)
214        .set_width(120)
215        .set_header(vec![
216            Cell::new("Source").add_attribute(Attribute::Bold).fg(Color::Cyan),
217            Cell::new("Name").add_attribute(Attribute::Bold).fg(Color::Cyan),
218            Cell::new("Value").add_attribute(Attribute::Bold).fg(Color::Cyan),
219        ]);
220
221    // Add rows with colored source indicators
222    for var in vars {
223        let (source_str, source_color) = format_source(&var.source);
224        let truncated_value = truncate_value(&var.value, 50);
225
226        table.add_row(vec![
227            Cell::new(source_str).fg(source_color),
228            Cell::new(&var.name).fg(Color::White),
229            Cell::new(truncated_value).fg(Color::Grey),
230        ]);
231    }
232
233    println!("{table}");
234}
235
236fn format_source(source: &envx_core::EnvVarSource) -> (String, Color) {
237    match source {
238        envx_core::EnvVarSource::System => ("System".to_string(), Color::Red),
239        envx_core::EnvVarSource::User => ("User".to_string(), Color::Yellow),
240        envx_core::EnvVarSource::Process => ("Process".to_string(), Color::Green),
241        envx_core::EnvVarSource::Shell => ("Shell".to_string(), Color::Cyan),
242        envx_core::EnvVarSource::Application(app) => (format!("App:{app}"), Color::Magenta),
243    }
244}
245
246fn format_source_compact(source: &envx_core::EnvVarSource) -> console::StyledObject<String> {
247    match source {
248        envx_core::EnvVarSource::System => style("[SYS]".to_string()).red().bold(),
249        envx_core::EnvVarSource::User => style("[USR]".to_string()).yellow().bold(),
250        envx_core::EnvVarSource::Process => style("[PRC]".to_string()).green().bold(),
251        envx_core::EnvVarSource::Shell => style("[SHL]".to_string()).cyan().bold(),
252        envx_core::EnvVarSource::Application(app) => style(format!("[{}]", &app[..3.min(app.len())].to_uppercase()))
253            .magenta()
254            .bold(),
255    }
256}
257
258fn truncate_value(value: &str, max_len: usize) -> String {
259    if value.len() <= max_len {
260        value.to_string()
261    } else {
262        format!("{}...", &value[..max_len - 3])
263    }
264}