envx_cli/
profile.rs

1use clap::{Args, Subcommand};
2use color_eyre::Result;
3use comfy_table::Table;
4use envx_core::{EnvVarManager, ProfileManager};
5
6#[derive(Args)]
7pub struct ProfileArgs {
8    #[command(subcommand)]
9    pub command: ProfileCommands,
10}
11
12#[derive(Subcommand)]
13pub enum ProfileCommands {
14    /// Create a new profile
15    Create {
16        /// Profile name
17        name: String,
18        /// Description
19        #[arg(short, long)]
20        description: Option<String>,
21    },
22    /// List all profiles
23    List,
24    /// Show current or specific profile
25    Show {
26        /// Profile name (shows active if not specified)
27        name: Option<String>,
28    },
29    /// Switch to a profile
30    Switch {
31        /// Profile name
32        name: String,
33        /// Apply immediately
34        #[arg(short, long)]
35        apply: bool,
36    },
37    /// Add a variable to a profile
38    Add {
39        /// Profile name
40        profile: String,
41        /// Variable name
42        name: String,
43        /// Variable value
44        value: String,
45        /// Override system variable
46        #[arg(short, long)]
47        override_system: bool,
48    },
49    /// Remove a variable from a profile
50    Remove {
51        /// Profile name
52        profile: String,
53        /// Variable name
54        name: String,
55    },
56    /// Delete a profile
57    Delete {
58        /// Profile name
59        name: String,
60        /// Force deletion
61        #[arg(short, long)]
62        force: bool,
63    },
64    /// Export a profile
65    Export {
66        /// Profile name
67        name: String,
68        /// Output file (stdout if not specified)
69        #[arg(short, long)]
70        output: Option<String>,
71    },
72    /// Import a profile
73    Import {
74        /// Import file
75        file: String,
76        /// Profile name
77        #[arg(short, long)]
78        name: Option<String>,
79        /// Overwrite if exists
80        #[arg(short, long)]
81        overwrite: bool,
82    },
83    /// Apply a profile to current environment
84    Apply {
85        /// Profile name
86        name: String,
87    },
88}
89
90/// Handle profile-related commands.
91///
92/// # Errors
93///
94/// This function will return an error if:
95/// - The profile manager cannot be initialized
96/// - Environment variable loading fails  
97/// - Profile operations fail (create, switch, delete, etc.)
98/// - File I/O operations fail during profile import/export
99/// - User input cannot be read from stdin
100/// - Invalid profile names are provided
101/// - Profile data cannot be serialized/deserialized
102pub fn handle_profile(args: ProfileArgs) -> Result<()> {
103    let mut profile_manager = ProfileManager::new()?;
104    let mut env_manager = EnvVarManager::new();
105    env_manager.load_all()?;
106
107    match args.command {
108        ProfileCommands::Create { name, description } => {
109            handle_profile_create(&mut profile_manager, &name, description)?;
110        }
111        ProfileCommands::List => {
112            handle_profile_list(&profile_manager);
113        }
114        ProfileCommands::Show { name } => {
115            handle_profile_show(&profile_manager, name)?;
116        }
117        ProfileCommands::Switch { name, apply } => {
118            handle_profile_switch(&mut profile_manager, &mut env_manager, &name, apply)?;
119        }
120        ProfileCommands::Add {
121            profile,
122            name,
123            value,
124            override_system,
125        } => {
126            handle_profile_add(&mut profile_manager, &profile, &name, &value, override_system)?;
127        }
128        ProfileCommands::Remove { profile, name } => {
129            handle_profile_remove(&mut profile_manager, &profile, &name)?;
130        }
131        ProfileCommands::Delete { name, force } => {
132            handle_profile_delete(&mut profile_manager, &name, force)?;
133        }
134        ProfileCommands::Export { name, output } => {
135            handle_profile_export(&profile_manager, &name, output)?;
136        }
137        ProfileCommands::Import { file, name, overwrite } => {
138            handle_profile_import(&mut profile_manager, &file, name, overwrite)?;
139        }
140        ProfileCommands::Apply { name } => {
141            handle_profile_apply(&mut profile_manager, &mut env_manager, &name)?;
142        }
143    }
144
145    Ok(())
146}
147
148fn handle_profile_create(profile_manager: &mut ProfileManager, name: &str, description: Option<String>) -> Result<()> {
149    profile_manager.create(name.to_string(), description)?;
150    println!("✅ Created profile: {name}");
151    Ok(())
152}
153
154fn handle_profile_list(profile_manager: &ProfileManager) {
155    let profiles = profile_manager.list();
156    if profiles.is_empty() {
157        println!("No profiles found.");
158    }
159
160    let active = profile_manager.active().map(|p| &p.name);
161    let mut table = Table::new();
162    table.set_header(vec!["Name", "Variables", "Created", "Description", "Status"]);
163
164    for profile in profiles {
165        let status = if active == Some(&profile.name) {
166            "● Active"
167        } else {
168            ""
169        };
170
171        table.add_row(vec![
172            profile.name.clone(),
173            profile.variables.len().to_string(),
174            profile.created_at.format("%Y-%m-%d").to_string(),
175            profile.description.clone().unwrap_or_default(),
176            status.to_string(),
177        ]);
178    }
179
180    println!("{table}");
181}
182
183fn handle_profile_show(profile_manager: &ProfileManager, name: Option<String>) -> Result<()> {
184    let profile = if let Some(name) = name {
185        profile_manager
186            .get(&name)
187            .ok_or_else(|| color_eyre::eyre::eyre!("Profile '{}' not found", name))?
188    } else {
189        profile_manager
190            .active()
191            .ok_or_else(|| color_eyre::eyre::eyre!("No active profile"))?
192    };
193
194    println!("Profile: {}", profile.name);
195    println!("Description: {}", profile.description.as_deref().unwrap_or(""));
196    println!("Created: {}", profile.created_at.format("%Y-%m-%d %H:%M:%S"));
197    println!("Updated: {}", profile.updated_at.format("%Y-%m-%d %H:%M:%S"));
198    if let Some(parent) = &profile.parent {
199        println!("Inherits from: {parent}");
200    }
201    println!("\nVariables:");
202
203    for (name, var) in &profile.variables {
204        let status = if var.enabled { "✓" } else { "✗" };
205        let override_flag = if var.override_system { " [override]" } else { "" };
206        println!("  {} {} = {}{}", status, name, var.value, override_flag);
207    }
208    Ok(())
209}
210
211fn handle_profile_switch(
212    profile_manager: &mut ProfileManager,
213    env_manager: &mut EnvVarManager,
214    name: &str,
215    apply: bool,
216) -> Result<()> {
217    profile_manager.switch(name)?;
218    println!("✅ Switched to profile: {name}");
219
220    if apply {
221        profile_manager.apply(name, env_manager)?;
222        println!("✅ Applied profile variables");
223    }
224    Ok(())
225}
226
227fn handle_profile_add(
228    profile_manager: &mut ProfileManager,
229    profile: &str,
230    name: &str,
231    value: &str,
232    override_system: bool,
233) -> Result<()> {
234    let prof = profile_manager
235        .get_mut(profile)
236        .ok_or_else(|| color_eyre::eyre::eyre!("Profile '{}' not found", profile))?;
237
238    prof.add_var(name.to_string(), value.to_string(), override_system);
239    profile_manager.save()?;
240
241    println!("✅ Added {name} to profile {profile}");
242    Ok(())
243}
244
245fn handle_profile_remove(profile_manager: &mut ProfileManager, profile: &str, name: &str) -> Result<()> {
246    let prof = profile_manager
247        .get_mut(profile)
248        .ok_or_else(|| color_eyre::eyre::eyre!("Profile '{}' not found", profile))?;
249
250    prof.remove_var(name)
251        .ok_or_else(|| color_eyre::eyre::eyre!("Variable '{}' not found in profile", name))?;
252
253    profile_manager.save()?;
254    println!("✅ Removed {name} from profile {profile}");
255    Ok(())
256}
257
258fn handle_profile_delete(profile_manager: &mut ProfileManager, name: &str, force: bool) -> Result<()> {
259    if !force {
260        print!("⚠️  Delete profile '{name}'? [y/N] ");
261        std::io::Write::flush(&mut std::io::stdout())?;
262
263        let mut input = String::new();
264        std::io::stdin().read_line(&mut input)?;
265        if !input.trim().eq_ignore_ascii_case("y") {
266            println!("Cancelled.");
267            return Ok(());
268        }
269    }
270
271    profile_manager.delete(name)?;
272    println!("✅ Deleted profile: {name}");
273    Ok(())
274}
275
276fn handle_profile_export(profile_manager: &ProfileManager, name: &str, output: Option<String>) -> Result<()> {
277    let json = profile_manager.export(name)?;
278
279    if let Some(path) = output {
280        std::fs::write(path, json)?;
281        println!("✅ Exported profile to file");
282    } else {
283        println!("{json}");
284    }
285    Ok(())
286}
287
288fn handle_profile_import(
289    profile_manager: &mut ProfileManager,
290    file: &str,
291    name: Option<String>,
292    overwrite: bool,
293) -> Result<()> {
294    let content = std::fs::read_to_string(file)?;
295    let import_name = name.unwrap_or_else(|| "imported".to_string());
296
297    profile_manager.import(import_name.clone(), &content, overwrite)?;
298    println!("✅ Imported profile: {import_name}");
299    Ok(())
300}
301
302fn handle_profile_apply(
303    profile_manager: &mut ProfileManager,
304    env_manager: &mut EnvVarManager,
305    name: &str,
306) -> Result<()> {
307    profile_manager.apply(name, env_manager)?;
308    println!("✅ Applied profile: {name}");
309    Ok(())
310}