Skip to main content

lean_ctx_patterns/
env_filter.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use std::collections::BTreeMap;
4
5pub fn compress(output: &str) -> Option<String> {
6    let trimmed = output.trim();
7    if trimmed.is_empty() {
8        return Some("(empty)".to_string());
9    }
10
11    let mut groups: BTreeMap<String, Vec<String>> = BTreeMap::new();
12    let mut ungrouped = Vec::new();
13
14    let sensitive_patterns = [
15        "KEY",
16        "SECRET",
17        "TOKEN",
18        "PASSWORD",
19        "PASSWD",
20        "CREDENTIALS",
21        "AUTH",
22        "API_KEY",
23        "PRIVATE",
24        "CERT",
25    ];
26
27    for line in trimmed.lines() {
28        let l = line.trim();
29        if l.is_empty() {
30            continue;
31        }
32
33        if let Some((key, value)) = l.split_once('=') {
34            let is_sensitive = sensitive_patterns
35                .iter()
36                .any(|p| key.to_uppercase().contains(p));
37            let display_value = if is_sensitive {
38                "***".to_string()
39            } else if value.len() > 80 {
40                let truncated: String = value.chars().take(40).collect();
41                format!("{truncated}...")
42            } else {
43                value.to_string()
44            };
45
46            let prefix = key.split('_').next().unwrap_or("OTHER").to_string();
47
48            groups
49                .entry(prefix)
50                .or_default()
51                .push(format!("{key}={display_value}"));
52        } else {
53            ungrouped.push(l.to_string());
54        }
55    }
56
57    let total: usize = groups.values().map(std::vec::Vec::len).sum();
58
59    let mut parts = Vec::new();
60    parts.push(format!("{total} variables:"));
61
62    for (prefix, vars) in &groups {
63        if vars.len() >= 3 {
64            parts.push(format!("[{prefix}_*] ({} vars)", vars.len()));
65            for v in vars.iter().take(3) {
66                parts.push(format!("  {v}"));
67            }
68            if vars.len() > 3 {
69                parts.push(format!("  ... +{} more", vars.len() - 3));
70            }
71        }
72    }
73
74    let small_groups: Vec<String> = groups
75        .iter()
76        .filter(|(_, v)| v.len() < 3)
77        .flat_map(|(_, v)| v.iter().cloned())
78        .collect();
79
80    if !small_groups.is_empty() {
81        parts.push(format!("[other] ({} vars)", small_groups.len()));
82        for v in small_groups.iter().take(5) {
83            parts.push(format!("  {v}"));
84        }
85        if small_groups.len() > 5 {
86            parts.push(format!("  ... +{} more", small_groups.len() - 5));
87        }
88    }
89
90    Some(parts.join("\n"))
91}