1use std::io::Write;
2
3use super::data::ShowData;
4
5pub fn run_plain(data: &ShowData, w: &mut dyn Write) -> std::io::Result<()> {
7 writeln!(
8 w,
9 "{} @ {}",
10 data.file_path,
11 &data.commit[..7.min(data.commit.len())]
12 )?;
13 writeln!(w)?;
14
15 if data.regions.is_empty() {
16 writeln!(w, " (no annotations)")?;
17 return Ok(());
18 }
19
20 for r in &data.regions {
21 writeln!(
23 w,
24 " {}-{} {} ({})",
25 r.region.lines.start,
26 r.region.lines.end,
27 r.region.ast_anchor.name,
28 r.region.ast_anchor.unit_type,
29 )?;
30
31 writeln!(w, " intent: {}", r.region.intent)?;
33
34 if let Some(ref reasoning) = r.region.reasoning {
36 writeln!(w, " reasoning: {reasoning}")?;
37 }
38
39 if !r.region.constraints.is_empty() {
41 writeln!(w, " constraints:")?;
42 for c in &r.region.constraints {
43 let source = match c.source {
44 crate::schema::v1::ConstraintSource::Author => "author",
45 crate::schema::v1::ConstraintSource::Inferred => "inferred",
46 };
47 writeln!(w, " - {} [{source}]", c.text)?;
48 }
49 }
50
51 if !r.region.semantic_dependencies.is_empty() {
53 writeln!(w, " deps:")?;
54 for d in &r.region.semantic_dependencies {
55 writeln!(w, " -> {} :: {}", d.file, d.anchor)?;
56 }
57 }
58
59 if let Some(ref risk) = r.region.risk_notes {
61 writeln!(w, " risk: {risk}")?;
62 }
63
64 if !r.region.corrections.is_empty() {
66 writeln!(w, " corrections: {}", r.region.corrections.len())?;
67 }
68
69 writeln!(w)?;
70 }
71
72 Ok(())
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78 use crate::schema::common::*;
79 use crate::schema::v1::*;
80 use crate::show::data::RegionRef;
81
82 fn make_test_data() -> ShowData {
83 ShowData {
84 file_path: "src/main.rs".to_string(),
85 commit: "abc1234567890".to_string(),
86 source_lines: vec!["fn main() {}".to_string()],
87 regions: vec![RegionRef {
88 region: RegionAnnotation {
89 file: "src/main.rs".to_string(),
90 ast_anchor: AstAnchor {
91 unit_type: "function".to_string(),
92 name: "main".to_string(),
93 signature: None,
94 },
95 lines: LineRange { start: 1, end: 1 },
96 intent: "Entry point".to_string(),
97 reasoning: Some("Standard main".to_string()),
98 constraints: vec![Constraint {
99 text: "Must not panic".to_string(),
100 source: ConstraintSource::Author,
101 }],
102 semantic_dependencies: vec![SemanticDependency {
103 file: "src/lib.rs".to_string(),
104 anchor: "run".to_string(),
105 nature: "calls".to_string(),
106 }],
107 related_annotations: vec![],
108 tags: vec![],
109 risk_notes: Some("None currently".to_string()),
110 corrections: vec![],
111 },
112 commit: "abc1234567890".to_string(),
113 timestamp: "2025-01-01T00:00:00Z".to_string(),
114 summary: "test".to_string(),
115 context_level: ContextLevel::Inferred,
116 provenance: Provenance {
117 operation: ProvenanceOperation::Initial,
118 derived_from: vec![],
119 original_annotations_preserved: false,
120 synthesis_notes: None,
121 },
122 }],
123 annotation_map: crate::show::data::LineAnnotationMap::build_from_regions(&[], 1),
124 }
125 }
126
127 #[test]
128 fn test_plain_output_contains_intent() {
129 let data = make_test_data();
130 let mut buf = Vec::new();
131 run_plain(&data, &mut buf).unwrap();
132 let output = String::from_utf8(buf).unwrap();
133 assert!(output.contains("intent: Entry point"));
134 }
135
136 #[test]
137 fn test_plain_output_contains_reasoning() {
138 let data = make_test_data();
139 let mut buf = Vec::new();
140 run_plain(&data, &mut buf).unwrap();
141 let output = String::from_utf8(buf).unwrap();
142 assert!(output.contains("reasoning: Standard main"));
143 }
144
145 #[test]
146 fn test_plain_output_contains_constraints() {
147 let data = make_test_data();
148 let mut buf = Vec::new();
149 run_plain(&data, &mut buf).unwrap();
150 let output = String::from_utf8(buf).unwrap();
151 assert!(output.contains("Must not panic [author]"));
152 }
153
154 #[test]
155 fn test_plain_output_contains_deps() {
156 let data = make_test_data();
157 let mut buf = Vec::new();
158 run_plain(&data, &mut buf).unwrap();
159 let output = String::from_utf8(buf).unwrap();
160 assert!(output.contains("-> src/lib.rs :: run"));
161 }
162
163 #[test]
164 fn test_plain_output_empty_annotations() {
165 let data = ShowData {
166 file_path: "src/empty.rs".to_string(),
167 commit: "abc1234".to_string(),
168 source_lines: vec![],
169 regions: vec![],
170 annotation_map: crate::show::data::LineAnnotationMap::build_from_regions(&[], 0),
171 };
172 let mut buf = Vec::new();
173 run_plain(&data, &mut buf).unwrap();
174 let output = String::from_utf8(buf).unwrap();
175 assert!(output.contains("(no annotations)"));
176 }
177}