1use clap::Args;
2use ggen_core::graph::Graph;
3use ggen_core::snapshot::{Snapshot, SnapshotManager};
4use ggen_utils::error::Result;
5use std::path::{Path, PathBuf};
6
7#[derive(Args, Debug)]
8pub struct SnapshotArgs {
9 #[command(subcommand)]
10 pub command: SnapshotCommand,
11}
12
13#[derive(clap::Subcommand, Debug)]
14pub enum SnapshotCommand {
15 Create {
17 #[arg(short, long)]
19 name: String,
20
21 #[arg(short, long)]
23 graph: Option<String>,
24
25 #[arg(short, long)]
27 files: Vec<PathBuf>,
28
29 #[arg(short, long)]
31 templates: Vec<PathBuf>,
32
33 #[arg(short, long, default_value = ".ggen/snapshots")]
35 snapshot_dir: PathBuf,
36 },
37
38 List {
40 #[arg(short, long, default_value = ".ggen/snapshots")]
42 snapshot_dir: PathBuf,
43 },
44
45 Show {
47 #[arg(short, long)]
49 name: String,
50
51 #[arg(short, long, default_value = ".ggen/snapshots")]
53 snapshot_dir: PathBuf,
54 },
55
56 Delete {
58 #[arg(short, long)]
60 name: String,
61
62 #[arg(short, long, default_value = ".ggen/snapshots")]
64 snapshot_dir: PathBuf,
65 },
66
67 Verify {
69 #[arg(short, long)]
71 name: String,
72
73 #[arg(short, long, default_value = ".ggen/snapshots")]
75 snapshot_dir: PathBuf,
76
77 #[arg(short, long)]
79 exit_code: bool,
80 },
81}
82
83pub async fn run(args: &SnapshotArgs) -> Result<()> {
84 match &args.command {
85 SnapshotCommand::Create {
86 name,
87 graph,
88 files,
89 templates,
90 snapshot_dir,
91 } => create_snapshot(name, graph, files, templates, snapshot_dir).await,
92 SnapshotCommand::List { snapshot_dir } => list_snapshots(snapshot_dir).await,
93 SnapshotCommand::Show { name, snapshot_dir } => show_snapshot(name, snapshot_dir).await,
94 SnapshotCommand::Delete { name, snapshot_dir } => delete_snapshot(name, snapshot_dir).await,
95 SnapshotCommand::Verify {
96 name,
97 snapshot_dir,
98 exit_code,
99 } => verify_snapshot(name, snapshot_dir, *exit_code).await,
100 }
101}
102
103async fn create_snapshot(
104 name: &str, graph_name: &Option<String>, files: &[PathBuf], templates: &[PathBuf],
105 snapshot_dir: &Path,
106) -> Result<()> {
107 let graph = if let Some(_graph_name) = graph_name {
109 Graph::new()?
111 } else {
112 Graph::new()?
113 };
114
115 let mut file_contents = Vec::new();
117 for file_path in files {
118 if file_path.exists() {
119 let content = std::fs::read_to_string(file_path)?;
120 file_contents.push((file_path.clone(), content));
121 }
122 }
123
124 let mut template_contents = Vec::new();
126 for template_path in templates {
127 if template_path.exists() {
128 let content = std::fs::read_to_string(template_path)?;
129 template_contents.push((template_path.clone(), content));
130 }
131 }
132
133 let snapshot = Snapshot::new(name.to_string(), &graph, file_contents, template_contents)?;
135
136 let manager = SnapshotManager::new(snapshot_dir.to_path_buf())?;
138 manager.save(&snapshot)?;
139
140 println!("Snapshot '{}' created successfully", name);
141 Ok(())
142}
143
144async fn list_snapshots(snapshot_dir: &Path) -> Result<()> {
145 let manager = SnapshotManager::new(snapshot_dir.to_path_buf())?;
146 let snapshots = manager.list()?;
147
148 if snapshots.is_empty() {
149 println!("No snapshots found.");
150 } else {
151 println!("Available snapshots:");
152 for snapshot in snapshots {
153 println!(" {}", snapshot);
154 }
155 }
156
157 Ok(())
158}
159
160async fn show_snapshot(name: &str, snapshot_dir: &Path) -> Result<()> {
161 let manager = SnapshotManager::new(snapshot_dir.to_path_buf())?;
162 let snapshot = manager.load(name)?;
163
164 println!("Snapshot: {}", snapshot.name);
165 println!(
166 "Created: {}",
167 snapshot.created_at.format("%Y-%m-%d %H:%M:%S UTC")
168 );
169 println!("Graph hash: {}", snapshot.graph.hash);
170 println!("Triples: {}", snapshot.graph.triple_count);
171 println!("Files: {}", snapshot.files.len());
172 println!("Templates: {}", snapshot.templates.len());
173
174 if !snapshot.metadata.is_empty() {
175 println!("Metadata:");
176 for (key, value) in &snapshot.metadata {
177 println!(" {}: {}", key, value);
178 }
179 }
180
181 Ok(())
182}
183
184async fn delete_snapshot(name: &str, snapshot_dir: &Path) -> Result<()> {
185 let manager = SnapshotManager::new(snapshot_dir.to_path_buf())?;
186 manager.delete(name)?;
187 println!("Snapshot '{}' deleted", name);
188 Ok(())
189}
190
191async fn verify_snapshot(name: &str, snapshot_dir: &Path, exit_code: bool) -> Result<()> {
192 let manager = SnapshotManager::new(snapshot_dir.to_path_buf())?;
193 let snapshot = manager.load(name)?;
194
195 let current_graph = Graph::new()?;
197
198 let is_compatible = snapshot.is_compatible_with(¤t_graph)?;
199
200 if is_compatible {
201 println!("✓ Snapshot '{}' is compatible with current state", name);
202 if !exit_code {
203 std::process::exit(0);
204 }
205 } else {
206 println!("✗ Snapshot '{}' has drifted from current state", name);
207 println!(" Graph hash mismatch:");
208 println!(" Snapshot: {}", snapshot.graph.hash);
209 println!(" Current: {}", current_graph.compute_hash()?);
210
211 if exit_code {
212 std::process::exit(1);
213 }
214 }
215
216 Ok(())
217}