gitview 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gitview/writer.py ADDED
@@ -0,0 +1,271 @@
1
+ """Output writers for git history stories."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Dict, List, Any
6
+ from datetime import datetime
7
+
8
+ from .chunker import Phase
9
+
10
+
11
+ class OutputWriter:
12
+ """Write git history stories to various formats."""
13
+
14
+ @staticmethod
15
+ def write_markdown(stories: Dict[str, str], phases: List[Phase],
16
+ output_path: str, repo_name: str = "Repository"):
17
+ """
18
+ Write comprehensive markdown report.
19
+
20
+ Args:
21
+ stories: Dict of story sections from storyteller
22
+ phases: List of Phase objects
23
+ output_path: Path to output markdown file
24
+ repo_name: Repository name for title
25
+ """
26
+ output_file = Path(output_path)
27
+ output_file.parent.mkdir(parents=True, exist_ok=True)
28
+
29
+ with open(output_file, 'w') as f:
30
+ # Header
31
+ f.write(f"# Evolution of {repo_name}\n\n")
32
+ f.write(f"*Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n")
33
+ f.write("---\n\n")
34
+
35
+ # Table of Contents
36
+ f.write("## Table of Contents\n\n")
37
+ f.write("1. [Executive Summary](#executive-summary)\n")
38
+ f.write("2. [Timeline](#timeline)\n")
39
+ f.write("3. [Full Narrative](#full-narrative)\n")
40
+ f.write("4. [Technical Evolution](#technical-evolution)\n")
41
+ f.write("5. [Story of Deletions](#story-of-deletions)\n")
42
+ f.write("6. [Phase Details](#phase-details)\n")
43
+ f.write("7. [Statistics](#statistics)\n\n")
44
+ f.write("---\n\n")
45
+
46
+ # Executive Summary
47
+ f.write("## Executive Summary\n\n")
48
+ f.write(stories['executive_summary'])
49
+ f.write("\n\n---\n\n")
50
+
51
+ # Timeline
52
+ f.write("## Timeline\n\n")
53
+ f.write(stories['timeline'])
54
+ f.write("\n\n---\n\n")
55
+
56
+ # Full Narrative
57
+ f.write("## Full Narrative\n\n")
58
+ f.write(stories['full_narrative'])
59
+ f.write("\n\n---\n\n")
60
+
61
+ # Technical Evolution
62
+ f.write("## Technical Evolution\n\n")
63
+ f.write(stories['technical_evolution'])
64
+ f.write("\n\n---\n\n")
65
+
66
+ # Deletion Story
67
+ f.write("## Story of Deletions\n\n")
68
+ f.write(stories['deletion_story'])
69
+ f.write("\n\n---\n\n")
70
+
71
+ # Phase Details
72
+ f.write("## Phase Details\n\n")
73
+
74
+ for phase in phases:
75
+ f.write(f"### Phase {phase.phase_number}\n\n")
76
+ f.write(f"**Period:** {phase.start_date[:10]} to {phase.end_date[:10]}\n\n")
77
+
78
+ # Stats table
79
+ f.write("| Metric | Value |\n")
80
+ f.write("|--------|-------|\n")
81
+ f.write(f"| Commits | {phase.commit_count} |\n")
82
+ f.write(f"| LOC Start | {phase.loc_start:,} |\n")
83
+ f.write(f"| LOC End | {phase.loc_end:,} |\n")
84
+ f.write(f"| LOC Delta | {phase.loc_delta:+,d} ({phase.loc_delta_percent:+.1f}%) |\n")
85
+ f.write(f"| Insertions | +{phase.total_insertions:,} |\n")
86
+ f.write(f"| Deletions | -{phase.total_deletions:,} |\n")
87
+ f.write(f"| Authors | {', '.join(phase.authors)} |\n")
88
+ f.write(f"| Primary Author | {phase.primary_author} |\n\n")
89
+
90
+ # Events
91
+ events = []
92
+ if phase.has_large_deletion:
93
+ events.append("🗑️ Large Deletion")
94
+ if phase.has_large_addition:
95
+ events.append("➕ Large Addition")
96
+ if phase.has_refactor:
97
+ events.append("♻️ Refactoring")
98
+ if phase.readme_changed:
99
+ events.append("📝 README Changed")
100
+
101
+ if events:
102
+ f.write(f"**Events:** {' | '.join(events)}\n\n")
103
+
104
+ # Summary
105
+ if phase.summary:
106
+ f.write("**Summary:**\n\n")
107
+ f.write(phase.summary)
108
+ f.write("\n\n")
109
+
110
+ f.write("---\n\n")
111
+
112
+ # Statistics
113
+ f.write("## Statistics\n\n")
114
+ OutputWriter._write_statistics(f, phases)
115
+
116
+ @staticmethod
117
+ def _write_statistics(f, phases: List[Phase]):
118
+ """Write statistics section to markdown file."""
119
+ total_commits = sum(p.commit_count for p in phases)
120
+ total_insertions = sum(p.total_insertions for p in phases)
121
+ total_deletions = sum(p.total_deletions for p in phases)
122
+
123
+ all_authors = set()
124
+ for p in phases:
125
+ all_authors.update(p.authors)
126
+
127
+ # Overall stats
128
+ f.write("### Overall Statistics\n\n")
129
+ f.write("| Metric | Value |\n")
130
+ f.write("|--------|-------|\n")
131
+ f.write(f"| Total Phases | {len(phases)} |\n")
132
+ f.write(f"| Total Commits | {total_commits:,} |\n")
133
+ f.write(f"| Total Insertions | +{total_insertions:,} |\n")
134
+ f.write(f"| Total Deletions | -{total_deletions:,} |\n")
135
+ f.write(f"| Net Change | {total_insertions - total_deletions:+,d} |\n")
136
+ f.write(f"| Contributors | {len(all_authors)} |\n")
137
+ f.write(f"| Time Span | {phases[0].start_date[:10]} to {phases[-1].end_date[:10]} |\n\n")
138
+
139
+ # Phase-by-phase stats
140
+ f.write("### Phase-by-Phase Statistics\n\n")
141
+ f.write("| Phase | Period | Commits | LOC Δ | Δ% | Insertions | Deletions |\n")
142
+ f.write("|-------|--------|---------|-------|-----|------------|------------|\n")
143
+
144
+ for p in phases:
145
+ f.write(f"| {p.phase_number} | {p.start_date[:10]} to {p.end_date[:10]} | "
146
+ f"{p.commit_count} | {p.loc_delta:+,d} | {p.loc_delta_percent:+.1f}% | "
147
+ f"+{p.total_insertions:,} | -{p.total_deletions:,} |\n")
148
+
149
+ f.write("\n")
150
+
151
+ # Language evolution
152
+ f.write("### Language Evolution\n\n")
153
+
154
+ # Collect all languages
155
+ all_languages = set()
156
+ for p in phases:
157
+ all_languages.update(p.languages_start.keys())
158
+ all_languages.update(p.languages_end.keys())
159
+
160
+ if all_languages:
161
+ f.write("Languages detected across phases:\n\n")
162
+ for lang in sorted(all_languages):
163
+ f.write(f"- {lang}\n")
164
+ f.write("\n")
165
+
166
+ @staticmethod
167
+ def write_json(stories: Dict[str, str], phases: List[Phase], output_path: str):
168
+ """
169
+ Write complete data to JSON file.
170
+
171
+ Args:
172
+ stories: Dict of story sections
173
+ phases: List of Phase objects
174
+ output_path: Path to output JSON file
175
+ """
176
+ output_file = Path(output_path)
177
+ output_file.parent.mkdir(parents=True, exist_ok=True)
178
+
179
+ data = {
180
+ 'generated_at': datetime.now().isoformat(),
181
+ 'total_phases': len(phases),
182
+ 'total_commits': sum(p.commit_count for p in phases),
183
+ 'stories': stories,
184
+ 'phases': [p.to_dict() for p in phases],
185
+ }
186
+
187
+ with open(output_file, 'w') as f:
188
+ json.dump(data, f, indent=2)
189
+
190
+ @staticmethod
191
+ def write_simple_timeline(phases: List[Phase], output_path: str):
192
+ """
193
+ Write a simple timeline markdown (without LLM-generated content).
194
+
195
+ Args:
196
+ phases: List of Phase objects
197
+ output_path: Path to output markdown file
198
+ """
199
+ output_file = Path(output_path)
200
+ output_file.parent.mkdir(parents=True, exist_ok=True)
201
+
202
+ with open(output_file, 'w') as f:
203
+ f.write("# Repository Timeline\n\n")
204
+ f.write(f"*Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*\n\n")
205
+
206
+ for phase in phases:
207
+ f.write(f"## Phase {phase.phase_number}: "
208
+ f"{phase.start_date[:10]} to {phase.end_date[:10]}\n\n")
209
+
210
+ f.write(f"- **Commits:** {phase.commit_count}\n")
211
+ f.write(f"- **LOC Change:** {phase.loc_delta:+,d} "
212
+ f"({phase.loc_delta_percent:+.1f}%)\n")
213
+ f.write(f"- **Authors:** {', '.join(phase.authors)}\n")
214
+
215
+ events = []
216
+ if phase.has_large_deletion:
217
+ events.append("Large Deletion")
218
+ if phase.has_large_addition:
219
+ events.append("Large Addition")
220
+ if phase.has_refactor:
221
+ events.append("Refactoring")
222
+ if phase.readme_changed:
223
+ events.append("README Changed")
224
+
225
+ if events:
226
+ f.write(f"- **Events:** {', '.join(events)}\n")
227
+
228
+ f.write("\n")
229
+
230
+ # Key commits
231
+ significant = [c for c in phase.commits
232
+ if c.is_large_deletion or c.is_large_addition or c.is_refactor]
233
+
234
+ if significant:
235
+ f.write("**Significant Commits:**\n\n")
236
+ for commit in significant[:5]:
237
+ f.write(f"- `{commit.short_hash}` - {commit.commit_subject} "
238
+ f"(+{commit.insertions}/-{commit.deletions})\n")
239
+ f.write("\n")
240
+
241
+ f.write("---\n\n")
242
+
243
+
244
+ def write_output(stories: Dict[str, str], phases: List[Phase],
245
+ output_dir: str = "docs", repo_name: str = "Repository"):
246
+ """
247
+ Write all output formats.
248
+
249
+ Args:
250
+ stories: Dict of story sections
251
+ phases: List of Phase objects
252
+ output_dir: Directory for output files
253
+ repo_name: Repository name
254
+ """
255
+ output_path = Path(output_dir)
256
+ output_path.mkdir(parents=True, exist_ok=True)
257
+
258
+ # Write main markdown report
259
+ markdown_path = output_path / "history_story.md"
260
+ OutputWriter.write_markdown(stories, phases, str(markdown_path), repo_name)
261
+ print(f"Wrote markdown report to: {markdown_path}")
262
+
263
+ # Write JSON data
264
+ json_path = output_path / "history_data.json"
265
+ OutputWriter.write_json(stories, phases, str(json_path))
266
+ print(f"Wrote JSON data to: {json_path}")
267
+
268
+ # Write simple timeline
269
+ timeline_path = output_path / "timeline.md"
270
+ OutputWriter.write_simple_timeline(phases, str(timeline_path))
271
+ print(f"Wrote timeline to: {timeline_path}")