envx_cli/
snapshot.rs

1use clap::{Args, Subcommand};
2use color_eyre::Result;
3use comfy_table::Table;
4use envx_core::{EnvVarManager, SnapshotManager};
5
6#[derive(Args)]
7pub struct SnapshotArgs {
8    #[command(subcommand)]
9    pub command: SnapshotCommands,
10}
11
12#[derive(Subcommand)]
13pub enum SnapshotCommands {
14    /// Create a new snapshot
15    Create {
16        /// Snapshot name
17        name: String,
18        /// Description
19        #[arg(short, long)]
20        description: Option<String>,
21    },
22    /// List all snapshots
23    List,
24    /// Show details of a snapshot
25    Show {
26        /// Snapshot name or ID
27        snapshot: String,
28    },
29    /// Restore from a snapshot
30    Restore {
31        /// Snapshot name or ID
32        snapshot: String,
33        /// Force restore without confirmation
34        #[arg(short, long)]
35        force: bool,
36    },
37    /// Delete a snapshot
38    Delete {
39        /// Snapshot name or ID
40        snapshot: String,
41        /// Force deletion without confirmation
42        #[arg(short, long)]
43        force: bool,
44    },
45    /// Compare two snapshots
46    Diff {
47        /// First snapshot
48        snapshot1: String,
49        /// Second snapshot
50        snapshot2: String,
51    },
52}
53
54/// Handle snapshot-related commands.
55///
56/// # Errors
57///
58/// This function will return an error if:
59/// - The snapshot manager cannot be initialized
60/// - Environment variable loading fails
61/// - Snapshot operations fail (create, restore, delete, etc.)
62/// - File I/O operations fail during snapshot operations
63/// - User input cannot be read from stdin
64/// - Invalid snapshot names or IDs are provided
65pub fn handle_snapshot(args: SnapshotArgs) -> Result<()> {
66    let snapshot_manager = SnapshotManager::new()?;
67    let mut env_manager = EnvVarManager::new();
68    env_manager.load_all()?;
69
70    match args.command {
71        SnapshotCommands::Create { name, description } => {
72            let vars = env_manager.list().into_iter().cloned().collect();
73            let snapshot = snapshot_manager.create(name, description, vars)?;
74            println!("✅ Created snapshot: {} (ID: {})", snapshot.name, snapshot.id);
75        }
76        SnapshotCommands::List => {
77            let snapshots = snapshot_manager.list()?;
78            if snapshots.is_empty() {
79                println!("No snapshots found.");
80                return Ok(());
81            }
82
83            let mut table = Table::new();
84            table.set_header(vec!["Name", "ID", "Created", "Variables", "Description"]);
85
86            for snapshot in snapshots {
87                table.add_row(vec![
88                    snapshot.name,
89                    snapshot.id[..8].to_string(),
90                    snapshot.created_at.format("%Y-%m-%d %H:%M").to_string(),
91                    snapshot.variables.len().to_string(),
92                    snapshot.description.unwrap_or_default(),
93                ]);
94            }
95
96            println!("{table}");
97        }
98        SnapshotCommands::Show { snapshot } => {
99            let snap = snapshot_manager.get(&snapshot)?;
100            println!("Snapshot: {}", snap.name);
101            println!("ID: {}", snap.id);
102            println!("Created: {}", snap.created_at.format("%Y-%m-%d %H:%M:%S"));
103            println!("Description: {}", snap.description.unwrap_or_default());
104            println!("Variables: {}", snap.variables.len());
105
106            // Show first 10 variables
107            println!("\nFirst 10 variables:");
108            for (i, (name, var)) in snap.variables.iter().take(10).enumerate() {
109                println!("  {}. {} = {}", i + 1, name, var.value);
110            }
111
112            if snap.variables.len() > 10 {
113                println!("  ... and {} more", snap.variables.len() - 10);
114            }
115        }
116        SnapshotCommands::Restore { snapshot, force } => {
117            if !force {
118                print!("⚠️  This will replace all current environment variables. Continue? [y/N] ");
119                std::io::Write::flush(&mut std::io::stdout())?;
120
121                let mut input = String::new();
122                std::io::stdin().read_line(&mut input)?;
123                if !input.trim().eq_ignore_ascii_case("y") {
124                    println!("Cancelled.");
125                    return Ok(());
126                }
127            }
128
129            snapshot_manager.restore(&snapshot, &mut env_manager)?;
130            println!("✅ Restored from snapshot: {snapshot}");
131        }
132        SnapshotCommands::Delete { snapshot, force } => {
133            if !force {
134                print!("⚠️  Delete snapshot '{snapshot}'? [y/N] ");
135                std::io::Write::flush(&mut std::io::stdout())?;
136
137                let mut input = String::new();
138                std::io::stdin().read_line(&mut input)?;
139                if !input.trim().eq_ignore_ascii_case("y") {
140                    println!("Cancelled.");
141                    return Ok(());
142                }
143            }
144
145            snapshot_manager.delete(&snapshot)?;
146            println!("✅ Deleted snapshot: {snapshot}");
147        }
148        SnapshotCommands::Diff { snapshot1, snapshot2 } => {
149            let diff = snapshot_manager.diff(&snapshot1, &snapshot2)?;
150
151            if diff.added.is_empty() && diff.removed.is_empty() && diff.modified.is_empty() {
152                println!("No differences found between snapshots.");
153                return Ok(());
154            }
155
156            if !diff.added.is_empty() {
157                println!("➕ Added in {snapshot2}:");
158                for (name, var) in &diff.added {
159                    println!("   {} = {}", name, var.value);
160                }
161            }
162
163            if !diff.removed.is_empty() {
164                println!("\n➖ Removed in {snapshot2}:");
165                for (name, var) in &diff.removed {
166                    println!("   {} = {}", name, var.value);
167                }
168            }
169
170            if !diff.modified.is_empty() {
171                println!("\n🔄 Modified:");
172                for (name, (old, new)) in &diff.modified {
173                    println!("   {name}:");
174                    println!("     Old: {}", old.value);
175                    println!("     New: {}", new.value);
176                }
177            }
178        }
179    }
180
181    Ok(())
182}