claude_code_switcher/
utils.rs

1use anyhow::{Result, anyhow};
2use console::style;
3use std::path::{Path, PathBuf};
4
5use crate::settings::ClaudeSettings;
6
7/// Get the path to the settings file
8pub fn get_settings_path(settings_path: Option<PathBuf>) -> PathBuf {
9    settings_path.unwrap_or_else(|| {
10        // Use current directory by default for project-specific settings
11        PathBuf::from(".claude").join("settings.json")
12    })
13}
14
15/// Get the path to the environment-specific settings file
16pub fn get_env_var_path() -> PathBuf {
17    PathBuf::from(".claude").join("settings.json")
18}
19
20/// Get the snapshots directory
21pub fn get_snapshots_dir() -> PathBuf {
22    let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
23    home_dir.join(".claude").join("snapshots")
24}
25
26/// Get the credentials directory
27pub fn get_credentials_dir() -> PathBuf {
28    let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("."));
29    home_dir.join(".claude").join("credentials")
30}
31
32/// Confirm an action with the user using enhanced selector
33pub fn confirm_action(message: &str, default: bool) -> Result<bool> {
34    crate::confirm_selector::confirm_with_enhanced_selector(message, default)
35}
36
37/// Create a backup of current settings
38pub fn backup_settings(settings_path: &Path) -> Result<Option<PathBuf>> {
39    if !settings_path.exists() {
40        return Ok(None);
41    }
42
43    let backup_path = settings_path.with_extension("json.backup");
44    std::fs::copy(settings_path, &backup_path)
45        .map_err(|e| anyhow!("Failed to create backup: {}", e))?;
46    Ok(Some(backup_path))
47}
48
49/// Restore settings from backup
50pub fn restore_from_backup(settings_path: &Path) -> Result<()> {
51    let backup_path = settings_path.with_extension("json.backup");
52
53    if !backup_path.exists() {
54        return Err(anyhow!("Backup file not found: {}", backup_path.display()));
55    }
56
57    std::fs::copy(&backup_path, settings_path)
58        .map_err(|e| anyhow!("Failed to restore from backup: {}", e))?;
59
60    std::fs::remove_file(&backup_path)
61        .map_err(|e| anyhow!("Failed to remove backup file: {}", e))?;
62
63    Ok(())
64}
65
66/// Get the current working directory's claude settings path
67pub fn get_local_settings_path() -> PathBuf {
68    PathBuf::from(".claude").join("settings.json")
69}
70
71/// Check if we should use local or global settings
72pub fn should_use_local_settings() -> bool {
73    let local_path = get_local_settings_path();
74    if local_path.exists() {
75        return true;
76    }
77
78    // Check for .claude directory in current working directory
79    let local_claude_dir = PathBuf::from(".claude");
80    local_claude_dir.exists()
81}
82
83/// Format bytes to human readable format
84pub fn format_bytes(bytes: u64) -> String {
85    const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
86    if bytes == 0 {
87        return "0 B".to_string();
88    }
89
90    let mut size = bytes as f64;
91    let mut unit_index = 0;
92
93    while size >= 1024.0 && unit_index < UNITS.len() - 1 {
94        size /= 1024.0;
95        unit_index += 1;
96    }
97
98    if unit_index == 0 {
99        format!("{} {}", bytes, UNITS[unit_index])
100    } else {
101        format!("{:.1} {}", size, UNITS[unit_index])
102    }
103}
104
105/// Format duration to human readable format
106pub fn format_duration(seconds: i64) -> String {
107    if seconds < 60 {
108        format!("{}s", seconds)
109    } else if seconds < 3600 {
110        format!("{}m {}s", seconds / 60, seconds % 60)
111    } else {
112        format!(
113            "{}h {}m {}s",
114            seconds / 3600,
115            (seconds % 3600) / 60,
116            seconds % 60
117        )
118    }
119}
120
121/// Truncate text to a maximum length
122pub fn truncate_text(text: &str, max_length: usize) -> String {
123    if text.len() <= max_length {
124        text.to_string()
125    } else {
126        format!("{}...", &text[..max_length.saturating_sub(3)])
127    }
128}
129
130/// Get a colored status indicator
131pub fn status_indicator(success: bool, message: &str) -> String {
132    if success {
133        format!("{} {}", style("✓").green().bold(), message)
134    } else {
135        format!("{} {}", style("✗").red().bold(), message)
136    }
137}
138
139/// Format a list of items for display
140pub fn format_list(items: &[&str], separator: &str) -> String {
141    items.join(separator)
142}
143
144/// Get the file size of a path
145pub fn get_file_size(path: &Path) -> Result<u64> {
146    if path.exists() {
147        let metadata =
148            std::fs::metadata(path).map_err(|e| anyhow!("Failed to get file metadata: {}", e))?;
149        Ok(metadata.len())
150    } else {
151        Ok(0)
152    }
153}
154
155/// Ensure a directory exists
156pub fn ensure_dir_exists(dir: &Path) -> Result<()> {
157    if !dir.exists() {
158        std::fs::create_dir_all(dir)
159            .map_err(|e| anyhow!("Failed to create directory {}: {}", dir.display(), e))?;
160    }
161    Ok(())
162}
163
164/// Check if a string is a valid UUID
165pub fn is_valid_uuid(uuid_str: &str) -> bool {
166    uuid::Uuid::parse_str(uuid_str).is_ok()
167}
168
169/// Get timestamp for display
170pub fn get_timestamp() -> String {
171    let now = chrono::Utc::now();
172    now.format("%Y-%m-%d %H:%M:%S UTC").to_string()
173}
174
175/// Format settings summary for display
176pub fn format_settings_summary(settings: &ClaudeSettings) -> String {
177    let mut summary = String::new();
178
179    if let Some(ref model) = settings.model {
180        summary.push_str(&format!("Model: {}\n", model));
181    }
182
183    if let Some(ref permissions) = settings.permissions {
184        if let Some(ref allowed) = permissions.allow
185            && allowed.contains(&"network".to_string())
186        {
187            summary.push_str("Network Access: Allowed\n");
188        }
189        if let Some(ref denied) = permissions.deny
190            && denied.contains(&"network".to_string())
191        {
192            summary.push_str("Network Access: Denied\n");
193        }
194        if let Some(ref allowed) = permissions.allow
195            && allowed.contains(&"filesystem".to_string())
196        {
197            summary.push_str("Filesystem Access: Allowed\n");
198        }
199        if let Some(ref denied) = permissions.deny
200            && denied.contains(&"filesystem".to_string())
201        {
202            summary.push_str("Filesystem Access: Denied\n");
203        }
204        if let Some(ref denied) = permissions.deny
205            && denied.contains(&"command".to_string())
206        {
207            summary.push_str("Command Execution: Denied\n");
208        }
209    }
210
211    summary.trim_end().to_string()
212}