Skip to main content

chronicle/cli/
lookup.rs

1use crate::error::Result;
2use crate::git::CliOps;
3
4/// Run the `git chronicle lookup` command.
5pub fn run(path: String, anchor: Option<String>, format: String, compact: bool) -> Result<()> {
6    let repo_dir = std::env::current_dir().map_err(|e| crate::error::ChronicleError::Io {
7        source: e,
8        location: snafu::Location::default(),
9    })?;
10    let git_ops = CliOps::new(repo_dir);
11
12    let output =
13        crate::read::lookup::build_lookup(&git_ops, &path, anchor.as_deref()).map_err(|e| {
14            crate::error::ChronicleError::Git {
15                source: e,
16                location: snafu::Location::default(),
17            }
18        })?;
19
20    match format.as_str() {
21        "json" => {
22            let json = if compact {
23                let mut compact_out = serde_json::json!({
24                    "contracts": output.contracts,
25                    "dependencies": output.dependencies,
26                    "decisions": output.decisions,
27                    "recent_history": output.recent_history,
28                    "open_follow_ups": output.open_follow_ups,
29                    "staleness": output.staleness,
30                });
31                if let Some(ref k) = output.knowledge {
32                    compact_out["knowledge"] = serde_json::to_value(k).unwrap_or_default();
33                }
34                serde_json::to_string_pretty(&compact_out)
35            } else {
36                serde_json::to_string_pretty(&output)
37            }
38            .map_err(|e| crate::error::ChronicleError::Json {
39                source: e,
40                location: snafu::Location::default(),
41            })?;
42            println!("{json}");
43        }
44        _ => {
45            println!("Lookup for: {}", output.file);
46            println!();
47
48            if !output.contracts.is_empty() {
49                println!("Contracts:");
50                for c in &output.contracts {
51                    let anchor_str = c
52                        .anchor
53                        .as_ref()
54                        .map(|a| format!(":{}", a))
55                        .unwrap_or_default();
56                    println!(
57                        "  [{}] {}{}: {}",
58                        c.source, c.file, anchor_str, c.description
59                    );
60                }
61                println!();
62            }
63
64            if !output.dependencies.is_empty() {
65                println!("Dependencies:");
66                for d in &output.dependencies {
67                    let anchor_str = d
68                        .anchor
69                        .as_ref()
70                        .map(|a| format!(":{}", a))
71                        .unwrap_or_default();
72                    println!(
73                        "  {}{} -> {}:{} ({})",
74                        d.file, anchor_str, d.target_file, d.target_anchor, d.assumption
75                    );
76                }
77                println!();
78            }
79
80            if !output.decisions.is_empty() {
81                println!("Decisions:");
82                for d in &output.decisions {
83                    println!("  [{}] {}: {}", d.stability, d.what, d.why);
84                }
85                println!();
86            }
87
88            if !output.recent_history.is_empty() {
89                println!("Recent history:");
90                for h in &output.recent_history {
91                    println!(
92                        "  {} {}: {}",
93                        &h.commit[..7.min(h.commit.len())],
94                        h.timestamp,
95                        h.intent
96                    );
97                }
98                println!();
99            }
100
101            if !output.open_follow_ups.is_empty() {
102                println!("Open follow-ups:");
103                for f in &output.open_follow_ups {
104                    println!("  {} {}", &f.commit[..7.min(f.commit.len())], f.follow_up);
105                }
106                println!();
107            }
108
109            if let Some(ref knowledge) = output.knowledge {
110                if !knowledge.conventions.is_empty() {
111                    println!("Applicable conventions:");
112                    for c in &knowledge.conventions {
113                        println!("  [{}] {}", c.id, c.rule);
114                    }
115                    println!();
116                }
117                if !knowledge.boundaries.is_empty() {
118                    println!("Module boundaries:");
119                    for b in &knowledge.boundaries {
120                        println!("  [{}] {}: {}", b.id, b.owns, b.boundary);
121                    }
122                    println!();
123                }
124                if !knowledge.anti_patterns.is_empty() {
125                    println!("Anti-patterns:");
126                    for a in &knowledge.anti_patterns {
127                        println!("  [{}] Don't: {} -> {}", a.id, a.pattern, a.instead);
128                    }
129                    println!();
130                }
131            }
132
133            if !output.staleness.is_empty() {
134                let stale_entries: Vec<_> = output.staleness.iter().filter(|s| s.stale).collect();
135                if !stale_entries.is_empty() {
136                    println!("Stale annotations:");
137                    for s in &stale_entries {
138                        println!(
139                            "  {} ({} commits behind)",
140                            &s.annotation_commit[..7.min(s.annotation_commit.len())],
141                            s.commits_since
142                        );
143                    }
144                    println!();
145                }
146            }
147
148            if output.contracts.is_empty()
149                && output.dependencies.is_empty()
150                && output.decisions.is_empty()
151                && output.recent_history.is_empty()
152                && output.open_follow_ups.is_empty()
153                && output.staleness.is_empty()
154            {
155                println!("  (no context found)");
156            }
157        }
158    }
159
160    Ok(())
161}