grex_cli/cli/verbs/
import.rs1use crate::cli::args::{GlobalFlags, ImportArgs};
4use anyhow::{anyhow, Context, Result};
5use grex_core::import::{import_from_repos_json, ImportOpts, ImportPlan, SkipReason};
6use tokio_util::sync::CancellationToken;
7
8pub fn run(args: ImportArgs, global: &GlobalFlags, _cancel: &CancellationToken) -> Result<()> {
9 let from = args
10 .from_repos_json
11 .as_deref()
12 .ok_or_else(|| anyhow!("--from-repos-json <path> is required"))?;
13
14 let manifest = args
15 .manifest
16 .clone()
17 .unwrap_or_else(|| std::env::current_dir().unwrap_or_default().join("grex.jsonl"));
18
19 let dry_run = args.dry_run || global.dry_run;
20
21 let plan = import_from_repos_json(from, &manifest, ImportOpts { dry_run })
22 .context("grex import failed")?;
23
24 if global.json {
25 emit_json(&plan, dry_run)?;
26 } else {
27 emit_human(&plan, dry_run);
28 }
29 Ok(())
30}
31
32fn emit_human(plan: &ImportPlan, dry_run: bool) {
33 for entry in &plan.imported {
34 let prefix = if dry_run { "DRY-RUN: would add" } else { "added" };
35 println!(
36 "{prefix} {path:<32} {kind:<12} {url}",
37 path = entry.path,
38 kind = entry.kind.as_str(),
39 url = if entry.url.is_empty() { "-" } else { &entry.url },
40 );
41 }
42 for skip in &plan.skipped {
43 let reason = match skip.reason {
44 SkipReason::PathCollision => "path-collision",
45 SkipReason::DuplicateInInput => "duplicate-in-input",
46 };
47 eprintln!("skip {:<32} {}", skip.path, reason);
48 }
49 println!(
50 "\nsummary: imported={} skipped={} failed={}",
51 plan.imported.len(),
52 plan.skipped.len(),
53 plan.failed.len(),
54 );
55}
56
57fn emit_json(plan: &ImportPlan, dry_run: bool) -> Result<()> {
65 let imported: Vec<_> = plan
66 .imported
67 .iter()
68 .map(|e| {
69 serde_json::json!({
70 "path": e.path,
71 "url": e.url,
72 "kind": e.kind.as_str(),
73 "would_dispatch": e.would_dispatch,
74 })
75 })
76 .collect();
77 let skipped: Vec<_> = plan
78 .skipped
79 .iter()
80 .map(|s| {
81 serde_json::json!({
82 "path": s.path,
83 "reason": match s.reason {
84 SkipReason::PathCollision => "path_collision",
85 SkipReason::DuplicateInInput => "duplicate_in_input",
86 },
87 })
88 })
89 .collect();
90 let failed: Vec<_> = plan
91 .failed
92 .iter()
93 .map(|f| {
94 serde_json::json!({
95 "path": f.path,
96 "error": f.error,
97 })
98 })
99 .collect();
100 let out = serde_json::json!({
101 "dry_run": dry_run,
102 "imported": imported,
103 "skipped": skipped,
104 "failed": failed,
105 });
106 println!("{}", serde_json::to_string(&out)?);
109 Ok(())
110}