diffstory 0.2.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.
diffstory/syntax.py ADDED
@@ -0,0 +1,145 @@
1
+ """Syntax highlighting using Pygments, works entirely offline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ from pygments import highlight
8
+ from pygments.lexers import (
9
+ get_lexer_by_name,
10
+ get_lexer_for_filename,
11
+ ClassNotFound,
12
+ )
13
+ from pygments.formatters import HtmlFormatter
14
+
15
+
16
+ # Mapping of file extensions to language names for quick lookup
17
+ EXTENSION_MAP: dict[str, str] = {
18
+ ".py": "python",
19
+ ".js": "javascript",
20
+ ".jsx": "javascript",
21
+ ".ts": "typescript",
22
+ ".tsx": "typescript",
23
+ ".java": "java",
24
+ ".cs": "csharp",
25
+ ".go": "go",
26
+ ".rs": "rust",
27
+ ".php": "php",
28
+ ".rb": "ruby",
29
+ ".kt": "kotlin",
30
+ ".kts": "kotlin",
31
+ ".sql": "sql",
32
+ ".sh": "bash",
33
+ ".bash": "bash",
34
+ ".zsh": "bash",
35
+ ".yml": "yaml",
36
+ ".yaml": "yaml",
37
+ ".json": "json",
38
+ ".html": "html",
39
+ ".htm": "html",
40
+ ".css": "css",
41
+ ".md": "markdown",
42
+ ".markdown": "markdown",
43
+ ".xml": "xml",
44
+ ".svg": "xml",
45
+ ".toml": "ini",
46
+ ".cfg": "ini",
47
+ ".ini": "ini",
48
+ ".txt": "text",
49
+ ".dockerfile": "docker",
50
+ ".tf": "terraform",
51
+ ".vue": "html",
52
+ ".svelte": "html",
53
+ ".c": "c",
54
+ ".h": "c",
55
+ ".cpp": "cpp",
56
+ ".cc": "cpp",
57
+ ".hpp": "cpp",
58
+ ".swift": "swift",
59
+ ".scala": "scala",
60
+ ".ex": "elixir",
61
+ ".exs": "elixir",
62
+ ".erl": "erlang",
63
+ ".hrl": "erlang",
64
+ ".hs": "haskell",
65
+ ".lua": "lua",
66
+ ".r": "r",
67
+ ".R": "r",
68
+ ".m": "matlab",
69
+ ".mm": "matlab",
70
+ }
71
+
72
+ def get_lexer_for_file(filepath: str) -> object:
73
+ """Get the appropriate Pygments lexer for a file path."""
74
+ # Try by extension first
75
+ ext = "." + filepath.rsplit(".", 1)[-1].lower() if "." in filepath else ""
76
+ if ext in EXTENSION_MAP:
77
+ try:
78
+ return get_lexer_by_name(EXTENSION_MAP[ext])
79
+ except ClassNotFound:
80
+ pass
81
+
82
+ # Try by full filename
83
+ try:
84
+ return get_lexer_for_filename(filepath)
85
+ except ClassNotFound:
86
+ pass
87
+
88
+ # Try to guess from common filenames
89
+ basename = filepath.rsplit("/", 1)[-1].lower()
90
+ if basename in ("dockerfile",):
91
+ try:
92
+ return get_lexer_by_name("docker")
93
+ except ClassNotFound:
94
+ pass
95
+ if basename in ("makefile", "gnumakefile"):
96
+ try:
97
+ return get_lexer_by_name("make")
98
+ except ClassNotFound:
99
+ pass
100
+
101
+ return None
102
+
103
+
104
+ def get_highlighted_line(line: str, filepath: str, lexer_cache: dict) -> str:
105
+ """Highlight a single line of code using a cached lexer."""
106
+ from html import escape
107
+ lexer = lexer_cache.get(filepath)
108
+ if lexer is None:
109
+ lexer = get_lexer_for_file(filepath)
110
+ lexer_cache[filepath] = lexer
111
+
112
+ if lexer is None:
113
+ return escape(line)
114
+
115
+ try:
116
+ formatter = HtmlFormatter(nowrap=True, style="default")
117
+ return highlight(line, lexer, formatter)
118
+ except Exception:
119
+ return escape(line)
120
+
121
+
122
+ def get_syntax_css(style: str = "default") -> str:
123
+ """Get the CSS for syntax highlighting."""
124
+ formatter = HtmlFormatter(style=style)
125
+ return formatter.get_style_defs(".highlight")
126
+
127
+
128
+ def _scope_css(css: str, theme: str) -> str:
129
+ """Scope every .highlight rule under a data-theme attribute selector."""
130
+ lines = css.splitlines()
131
+ result = []
132
+ for line in lines:
133
+ stripped = line.strip()
134
+ if stripped.startswith(".highlight"):
135
+ indent = line[:len(line) - len(line.lstrip())]
136
+ line = indent + '[data-theme="' + theme + '"] ' + stripped
137
+ result.append(line)
138
+ return "\n".join(result)
139
+
140
+
141
+ def get_syntax_styles() -> str:
142
+ """Get both light and dark syntax highlight CSS, scoped by data-theme."""
143
+ light_css = get_syntax_css("default")
144
+ dark_css = get_syntax_css("monokai")
145
+ return "\n" + _scope_css(light_css, "light") + "\n\n" + _scope_css(dark_css, "dark") + "\n"
@@ -0,0 +1,207 @@
1
+ Metadata-Version: 2.4
2
+ Name: diffstory
3
+ Version: 0.2.0
4
+ Summary: Transform Git diffs into rich, interactive, self-contained HTML reports
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/lakshayjindal/diffstory
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Environment :: Console
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Topic :: Software Development :: Version Control :: Git
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: Pygments>=2.10
17
+
18
+ # DiffStory
19
+
20
+ **Transform Git diffs into rich, interactive, self-contained HTML reports.**
21
+
22
+ DiffStory turns any `git diff` into a beautiful, portable HTML report that answers not just *what* changed, but *who* changed it, *when*, and *why* — all offline, in a single file.
23
+
24
+ ```bash
25
+ pip install diffstory
26
+ cd my-repo
27
+ diffstory --staged -o report.html
28
+ # Open report.html in any browser
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Features
34
+
35
+ ### Phase 1 (MVP)
36
+ - **Unified View** — Classic git-style diff with syntax highlighting
37
+ - **Side-by-Side View** — Original and modified columns, synchronized
38
+ - **Inline Edit View** — Word-level diff showing exact token changes
39
+ - **Syntax Highlighting** — 30+ languages via Pygments, light + dark themes
40
+ - **Statistics Dashboard** — Files changed, +/-, authors breakdown
41
+ - **File Sidebar** — Navigate files with search, collapse/expand
42
+ - **Keyboard Shortcuts** — `U`/`S`/`I` to switch views, `D` for theme, `Esc` to close
43
+ - **Theme Toggle** — Light/dark with system preference detection and persistence
44
+ - **Export Formats** — HTML, JSON, Markdown, CSV
45
+
46
+ ### Phase 2 (Blame Integration)
47
+ - **Blame Tooltips** — Hover any changed line to see author, commit, date, and message
48
+ - **Commit Drawer** — Click a line to open a detailed side panel with full commit metadata
49
+ - **Relative Time** — "2h ago", "3d ago" for at-a-glance recency
50
+
51
+ ### Future Phases
52
+ - Search across filename, author, commit message, and code content
53
+ - Filtering by author, date range, file type, change type
54
+ - Commit evolution viewer and timeline
55
+ - Deep linking to specific lines and files
56
+
57
+ ---
58
+
59
+ ## Installation
60
+
61
+ ### From PyPI (once published)
62
+ ```bash
63
+ pip install diffstory
64
+ ```
65
+
66
+ ### From source
67
+ ```bash
68
+ git clone https://github.com/user/diffstory.git
69
+ cd diffstory
70
+ pip install -e .
71
+ ```
72
+
73
+ **Requirements:** Python 3.10+, Git
74
+
75
+ ---
76
+
77
+ ## Usage
78
+
79
+ ### Basic Commands
80
+
81
+ ```bash
82
+ # Working tree diff
83
+ diffstory
84
+
85
+ # Staged changes
86
+ diffstory --staged
87
+
88
+ # Compare commits
89
+ diffstory HEAD~3 HEAD
90
+
91
+ # Compare branches
92
+ diffstory main feature
93
+
94
+ # Custom output file
95
+ diffstory -o my-report.html
96
+
97
+ # Multiple export formats
98
+ diffstory --staged --json --md --csv -o report
99
+ ```
100
+
101
+ ### Keyboard Shortcuts (in the HTML report)
102
+
103
+ | Key | Action |
104
+ |---|---|
105
+ | `U` | Unified view |
106
+ | `S` | Side-by-side view |
107
+ | `I` | Inline edit view |
108
+ | `D` | Toggle theme |
109
+ | `Esc` | Close drawer / stats panel |
110
+ | `F` | Focus file search |
111
+
112
+ ---
113
+
114
+ ## Report Features
115
+
116
+ ### Three View Modes
117
+
118
+ | Mode | Description |
119
+ |---|---|
120
+ | **Unified** | Classic git diff format with line numbers |
121
+ | **Side-by-Side** | Two-column layout — original on left, modified on right |
122
+ | **Inline Edit** | Word-level diff showing additions (green) and removals (red strikethrough) within the same line |
123
+
124
+ ### Blame Tooltips (Phase 2)
125
+
126
+ Hover over any changed line to see:
127
+ - **Author** name
128
+ - **Commit hash** (short, 7 chars)
129
+ - **Commit subject**
130
+ - **Date** with relative time ("2h ago")
131
+
132
+ Click any line to open the **Commit Drawer** with full metadata: body, committer, parents, files changed, insertions/deletions.
133
+
134
+ ### Statistics
135
+
136
+ The statistics panel shows:
137
+ - Files changed, additions, deletions
138
+ - Added / deleted / modified / renamed file counts
139
+ - Top 10 most-changed files with per-file breakdown
140
+
141
+ ---
142
+
143
+ ## Output
144
+
145
+ Reports are fully self-contained single HTML files:
146
+ - All CSS inlined
147
+ - All JavaScript inlined
148
+ - All data embedded as JSON
149
+ - No external dependencies
150
+ - Works offline in any modern browser
151
+ - Safe to email or archive
152
+
153
+ ---
154
+
155
+ ## Project Structure
156
+
157
+ ```
158
+ diffstory/
159
+ ├── pyproject.toml # Build config & entry point
160
+ ├── requirements.md # Full product requirements
161
+ ├── .gitignore
162
+ ├── src/diffstory/
163
+ │ ├── __init__.py # Package version
164
+ │ ├── __main__.py # python -m diffstory
165
+ │ ├── cli.py # CLI argument parsing & orchestration
166
+ │ ├── git_utils.py # Git subprocess wrappers
167
+ │ ├── diff_parser.py # Unified diff → structured data
168
+ │ ├── syntax.py # Pygments syntax highlighting
169
+ │ └── html_generator.py # Self-contained HTML report generation
170
+ └── tests/
171
+ └── __init__.py
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Development
177
+
178
+ ```bash
179
+ # Install in editable mode
180
+ pip install -e .
181
+
182
+ # Run against a test repo
183
+ cd /tmp && mkdir test && cd test
184
+ git init
185
+ echo "hello" > test.py
186
+ git add -A && git commit -m "init"
187
+ echo "world" >> test.py
188
+ diffstory
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Security
194
+
195
+ DiffStory is designed for air-gapped, audit-safe use:
196
+ - Never uploads code
197
+ - Never transmits data
198
+ - No telemetry
199
+ - No accounts required
200
+ - No external API calls
201
+ - Never modifies your repository
202
+
203
+ ---
204
+
205
+ ## License
206
+
207
+ MIT
@@ -0,0 +1,12 @@
1
+ diffstory/__init__.py,sha256=CLe-2vhq8c6aj3_3Y4Nq37NngqZ9oPQwTy1r8Xqy9Ec,100
2
+ diffstory/__main__.py,sha256=Pc47yF5jQlbjKC_BSH_pna-n3MnEA3lIKsE3TvJ_Jlo,128
3
+ diffstory/cli.py,sha256=aZ7KbfT7CWlVTR-ERBmPWnYscafV10uFvmtiDHN7Nsk,12678
4
+ diffstory/diff_parser.py,sha256=n9L-MmWBSZP-nYohMb1jhOZrERv8O8rnJ3OzeGMpYgU,9417
5
+ diffstory/git_utils.py,sha256=eT77bnYKOlY30ZK07gK60VJWioBjKKgKBNsS-jzXCMg,10786
6
+ diffstory/html_generator.py,sha256=iyHkk9uxlVLpeYuYJXGRPnNOjRJKFNFjz7Spkab3yHQ,72226
7
+ diffstory/syntax.py,sha256=8KPUvBObrZ0uQ5n0-axbC6wRO_ygWhICsADwpYdoSmw,3867
8
+ diffstory-0.2.0.dist-info/METADATA,sha256=QlR_yL1h8bxzZw2uSeVudq2a85dCh8SBfXK0G86nRG0,5330
9
+ diffstory-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
10
+ diffstory-0.2.0.dist-info/entry_points.txt,sha256=_PGs0-KAfUwKvHnoDa7V-ouABl0dAMeRetsEgijp0LM,49
11
+ diffstory-0.2.0.dist-info/top_level.txt,sha256=qxbolVX6YxYMHor32Ih3RMUHGmhvspVJKnSKZsLwVPQ,10
12
+ diffstory-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ diffstory = diffstory.cli:main
@@ -0,0 +1 @@
1
+ diffstory