ggen_cli_lib/cmds/market/
info.rs1use clap::Args;
21use ggen_utils::error::Result;
22
23#[derive(Args, Debug)]
24pub struct InfoArgs {
25 pub gpack_id: String,
27
28 #[arg(long)]
30 pub examples: bool,
31
32 #[arg(long)]
34 pub dependencies: bool,
35
36 #[arg(long)]
38 pub health: bool,
39
40 #[arg(long)]
42 pub interactive: bool,
43}
44
45#[cfg_attr(test, mockall::automock)]
46pub trait GpackMetadataFetcher {
47 fn fetch_metadata(&self, gpack_id: &str) -> Result<GpackMetadata>;
48}
49
50#[derive(Debug, Clone)]
51pub struct GpackMetadata {
52 pub id: String,
53 pub name: String,
54 pub description: String,
55 pub version: String,
56 pub author: Option<String>,
57 pub license: Option<String>,
58 pub homepage: Option<String>,
59 pub stars: u32,
60 pub downloads: u32,
61 pub updated_at: String,
62 pub tags: Vec<String>,
63 pub health_score: Option<f32>,
64 pub dependencies: Vec<PackageDependency>,
65 pub examples: Vec<UsageExample>,
66}
67
68#[derive(Debug, Clone)]
69pub struct PackageDependency {
70 pub name: String,
71 pub version: String,
72 pub required: bool,
73}
74
75#[derive(Debug, Clone)]
76pub struct UsageExample {
77 pub title: String,
78 pub code: String,
79 pub description: String,
80}
81
82fn validate_gpack_id(gpack_id: &str) -> Result<()> {
84 if gpack_id.trim().is_empty() {
86 return Err(ggen_utils::error::Error::new("Gpack ID cannot be empty"));
87 }
88
89 if gpack_id.len() > 200 {
91 return Err(ggen_utils::error::Error::new(
92 "Gpack ID too long (max 200 characters)",
93 ));
94 }
95
96 if !gpack_id
98 .chars()
99 .all(|c| c.is_alphanumeric() || c == '.' || c == '-' || c == '_')
100 {
101 return Err(ggen_utils::error::Error::new(
102 "Invalid gpack ID format: only alphanumeric characters, dots, dashes, and underscores allowed",
103 ));
104 }
105
106 Ok(())
107}
108
109pub async fn run(args: &InfoArgs) -> Result<()> {
110 validate_gpack_id(&args.gpack_id)?;
112
113 let metadata = create_sample_metadata(&args.gpack_id);
116
117 if args.interactive {
118 display_interactive_info(&metadata, args);
119 } else {
120 display_formatted_info(&metadata, args);
121 }
122
123 Ok(())
124}
125
126fn display_interactive_info(metadata: &GpackMetadata, args: &InfoArgs) {
128 println!("╔══════════════════════════════════════════════════════════════╗");
129 println!("║ 📦 Gpack Information ║");
130 println!("╚══════════════════════════════════════════════════════════════╝");
131 println!();
132
133 println!("┌──────────────────────────────────────────────────────────────┐");
135 println!("│ Package Details │");
136 println!("├──────────────────────────────────────────────────────────────┤");
137 println!("│ ID: {:<50} │", metadata.id);
138 println!("│ Name: {:<50} │", metadata.name);
139 println!("│ Version: {:<50} │", metadata.version);
140 println!(
141 "│ Author: {:<50} │",
142 metadata.author.as_deref().unwrap_or("Unknown")
143 );
144 println!(
145 "│ License: {:<50} │",
146 metadata.license.as_deref().unwrap_or("Unknown")
147 );
148 println!("│ Stars: {:<50} │", format!("⭐ {}", metadata.stars));
149 println!("│ Downloads: {:<50} │", format!("⬇ {}", metadata.downloads));
150 println!("│ Updated: {:<50} │", metadata.updated_at);
151 println!("└──────────────────────────────────────────────────────────────┘");
152 println!();
153
154 if args.health || args.interactive {
155 display_health_metrics(metadata);
156 }
157
158 if args.dependencies || args.interactive {
159 display_dependencies(metadata);
160 }
161
162 if args.examples || args.interactive {
163 display_examples(metadata);
164 }
165}
166
167fn display_formatted_info(metadata: &GpackMetadata, args: &InfoArgs) {
169 println!("📦 Gpack Information");
170 println!("==================");
171 println!("ID: {}", metadata.id);
172 println!("Name: {}", metadata.name);
173 println!("Version: {}", metadata.version);
174
175 println!("\n📋 Description:");
176 println!(" {}", metadata.description);
177
178 println!("\n🏷️ Metadata:");
179 println!(
180 " Author: {} | License: {}",
181 metadata.author.as_deref().unwrap_or("Unknown"),
182 metadata.license.as_deref().unwrap_or("Unknown")
183 );
184 println!(
185 " Stars: ⭐ {} | Downloads: ⬇ {}",
186 metadata.stars, metadata.downloads
187 );
188 println!(" Updated: {}", metadata.updated_at);
189
190 if !metadata.tags.is_empty() {
191 println!(" Tags: {}", metadata.tags.join(", "));
192 }
193
194 if args.health {
195 display_health_metrics(metadata);
196 }
197
198 if args.dependencies {
199 display_dependencies(metadata);
200 }
201
202 if args.examples {
203 display_examples(metadata);
204 }
205}
206
207fn display_health_metrics(metadata: &GpackMetadata) {
208 println!("\n🏥 Health Metrics:");
209 if let Some(score) = metadata.health_score {
210 let health_emoji = match score {
211 90.0..=100.0 => "🟢",
212 70.0..=89.9 => "🟡",
213 50.0..=69.9 => "🟠",
214 _ => "🔴",
215 };
216 println!(" {} Overall Health Score: {:.1}%", health_emoji, score);
217 println!(" 📊 Security: 95% | Maintenance: 87% | Popularity: 92%");
218 }
219}
220
221fn display_dependencies(metadata: &GpackMetadata) {
222 println!("\n🔗 Dependencies:");
223 if metadata.dependencies.is_empty() {
224 println!(" None");
225 } else {
226 for dep in &metadata.dependencies {
227 let required_icon = if dep.required { "🔴" } else { "🟡" };
228 println!(
229 " {} {}@{} ({})",
230 required_icon,
231 dep.name,
232 dep.version,
233 if dep.required { "required" } else { "optional" }
234 );
235 }
236 }
237}
238
239fn display_examples(metadata: &GpackMetadata) {
240 println!("\n💡 Usage Examples:");
241 if metadata.examples.is_empty() {
242 println!(" ggen market install {}", metadata.id);
243 println!(" ggen project generate --template {}", metadata.id);
244 } else {
245 for example in &metadata.examples {
246 println!(" 📝 {}:", example.title);
247 println!(" {}", example.description);
248 println!(" ```bash");
249 for line in example.code.lines() {
250 println!(" {}", line);
251 }
252 println!(" ```");
253 println!();
254 }
255 }
256}
257
258fn create_sample_metadata(gpack_id: &str) -> GpackMetadata {
259 GpackMetadata {
260 id: gpack_id.to_string(),
261 name: "User Authentication System".to_string(),
262 description: "Complete user authentication system with email/password, JWT tokens, and role-based access control.".to_string(),
263 version: "1.2.3".to_string(),
264 author: Some("@ggen-official".to_string()),
265 license: Some("MIT".to_string()),
266 homepage: Some("https://ggen.dev".to_string()),
267 stars: 1245,
268 downloads: 45234,
269 updated_at: "2 days ago".to_string(),
270 tags: vec!["auth".to_string(), "user".to_string(), "jwt".to_string()],
271 health_score: Some(95.0),
272 dependencies: vec![
273 PackageDependency {
274 name: "@ggen/base-entity".to_string(),
275 version: "^2.0.0".to_string(),
276 required: true,
277 },
278 PackageDependency {
279 name: "@ggen/validation-helpers".to_string(),
280 version: "^1.1.0".to_string(),
281 required: true,
282 },
283 ],
284 examples: vec![
285 UsageExample {
286 title: "Basic Installation".to_string(),
287 code: "ggen market install @ggen/auth-user".to_string(),
288 description: "Install the authentication package".to_string(),
289 },
290 UsageExample {
291 title: "Generate Controllers".to_string(),
292 code: "ggen project generate --template @ggen/auth-user/controller".to_string(),
293 description: "Generate authentication controllers".to_string(),
294 },
295 ],
296 }
297}
298
299pub async fn run_with_deps(args: &InfoArgs, fetcher: &dyn GpackMetadataFetcher) -> Result<()> {
300 validate_gpack_id(&args.gpack_id)?;
302
303 println!("🔍 Fetching gpack metadata...");
305
306 let metadata = fetcher.fetch_metadata(&args.gpack_id)?;
307
308 println!("📦 Gpack Information:");
309 println!(" ID: {}", metadata.id);
310 println!(" Name: {}", metadata.name);
311 println!(" Description: {}", metadata.description);
312 println!(" Version: {}", metadata.version);
313
314 if let Some(author) = metadata.author {
315 println!(" Author: {}", author);
316 }
317 if let Some(license) = metadata.license {
318 println!(" License: {}", license);
319 }
320 if let Some(homepage) = metadata.homepage {
321 println!(" Homepage: {}", homepage);
322 }
323
324 Ok(())
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330 use mockall::predicate::*;
331
332 #[tokio::test]
333 async fn test_show_displays_metadata() {
334 let mut mock_fetcher = MockGpackMetadataFetcher::new();
335 mock_fetcher
336 .expect_fetch_metadata()
337 .with(eq(String::from("io.ggen.rust.cli")))
338 .times(1)
339 .returning(|id| {
340 Ok(GpackMetadata {
341 id: id.to_string(),
342 name: "Rust CLI".to_string(),
343 description: "CLI templates for Rust".to_string(),
344 version: "1.0.0".to_string(),
345 author: Some("ggen-team".to_string()),
346 license: Some("MIT".to_string()),
347 homepage: Some("https://github.com/ggen/rust-cli".to_string()),
348 stars: 100,
349 downloads: 1000,
350 updated_at: "2024-01-01T00:00:00Z".to_string(),
351 tags: vec!["rust".to_string(), "cli".to_string()],
352 health_score: Some(0.95),
353 dependencies: vec![],
354 examples: vec![],
355 })
356 });
357
358 let args = InfoArgs {
359 gpack_id: "io.ggen.rust.cli".to_string(),
360 examples: false,
361 dependencies: false,
362 health: false,
363 interactive: false,
364 };
365
366 let result = run_with_deps(&args, &mock_fetcher).await;
367 assert!(result.is_ok());
368 }
369}