vgt 0.0.1a0__tar.gz

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.
vgt-0.0.1a0/PKG-INFO ADDED
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: vgt
3
+ Version: 0.0.1a0
4
+ Summary: TUI git viewer
5
+ Home-page: https://www.mlx-code.com
6
+ Author: J Joe
7
+ Author-email: albersj66@gmail.com
8
+ License: Apache-2.0
9
+ Project-URL: Source, https://github.com/JosefAlbers/mlx-code
10
+ Project-URL: Issues, https://github.com/JosefAlbers/mlx-code/issues
11
+ Project-URL: Documentation, https://www.mlx-code.com/
12
+ Requires-Python: >=3.12.8
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: textual>=8.2.7
15
+ Requires-Dist: rich>=15.0.0
16
+ Provides-Extra: all
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license
23
+ Dynamic: project-url
24
+ Dynamic: provides-extra
25
+ Dynamic: requires-dist
26
+ Dynamic: requires-python
27
+ Dynamic: summary
28
+
29
+ view git
vgt-0.0.1a0/README.md ADDED
@@ -0,0 +1 @@
1
+ view git
vgt-0.0.1a0/main.py ADDED
@@ -0,0 +1,286 @@
1
+ from __future__ import annotations
2
+ import subprocess
3
+ import sys
4
+ from dataclasses import dataclass, field
5
+ from typing import Optional
6
+ from rich.console import Console
7
+ from rich.text import Text
8
+ from render import Node, TreeApp, dump_text
9
+ EMPTY_TREE = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
10
+
11
+ class Git:
12
+
13
+ def __init__(self, cwd: str):
14
+ self.cwd = cwd
15
+ self._run('rev-parse', '--git-dir')
16
+
17
+ def _run(self, *args: str) -> str:
18
+ r = subprocess.run(['git', *args], cwd=self.cwd, capture_output=True, text=True)
19
+ if r.returncode != 0:
20
+ raise RuntimeError(f'git {' '.join(args)}: {r.stderr.strip()}')
21
+ return r.stdout
22
+
23
+ def current_branch(self) -> Optional[str]:
24
+ out = self._run('branch', '--show-current').strip()
25
+ return out or None
26
+
27
+ def branch_tips(self) -> dict[str, str]:
28
+ out = self._run('for-each-ref', '--format=%(refname:short)%09%(objectname)', 'refs/heads/')
29
+ return dict((line.split('\t') for line in out.splitlines() if line.strip()))
30
+
31
+ def load_commits(self) -> dict[str, 'Commit']:
32
+ out = self._run('log', '--branches', '--date=iso-strict', '--format=%H%x09%P%x09%an%x09%ad%x09%s%x09%b%x1e')
33
+ commits: dict[str, Commit] = {}
34
+ for rec in out.split('\x1e'):
35
+ rec = rec.strip('\n')
36
+ if not rec:
37
+ continue
38
+ sha, parents, author, date, subject, body = rec.lstrip('\n').split('\t', 5)
39
+ commits[sha] = Commit(sha, parents.split(), author, date, subject, body)
40
+ return commits
41
+
42
+ def diff(self, sha: str, parent: Optional[str]) -> str:
43
+ base = parent or EMPTY_TREE
44
+ return self._run('diff', '--no-color', base, sha)
45
+
46
+ @dataclass
47
+ class Commit:
48
+ sha: str
49
+ parents: list[str]
50
+ author: str
51
+ date: str
52
+ subject: str
53
+ body: str
54
+
55
+ @property
56
+ def short(self) -> str:
57
+ return self.sha[:7]
58
+
59
+ @dataclass
60
+ class Branch:
61
+ name: str
62
+ tip: str
63
+ parent: Optional[str] = None
64
+ fork_point: Optional[str] = None
65
+ merged: bool = False
66
+ own_commits: list[str] = field(default_factory=list)
67
+ grafted: list[str] = field(default_factory=list)
68
+ children: list[str] = field(default_factory=list)
69
+
70
+ def first_parent_chain(commits: dict[str, Commit], tip: str) -> list[str]:
71
+ chain, cur, seen = ([], tip, set())
72
+ while cur and cur not in seen and (cur in commits):
73
+ seen.add(cur)
74
+ chain.append(cur)
75
+ parents = commits[cur].parents
76
+ cur = parents[0] if parents else None
77
+ return chain
78
+
79
+ def pick_trunk(git: Git, tips: dict[str, str]) -> str:
80
+ current = git.current_branch()
81
+ if current and current in tips:
82
+ return current
83
+ for candidate in ('main', 'master'):
84
+ if candidate in tips:
85
+ return candidate
86
+ return sorted(tips)[0]
87
+
88
+ def infer_hierarchy(commits: dict[str, Commit], tips: dict[str, str], trunk: str) -> dict[str, Branch]:
89
+ branches = {name: Branch(name=name, tip=tip) for name, tip in tips.items()}
90
+ chains = {name: first_parent_chain(commits, b.tip) for name, b in branches.items()}
91
+ index_in = {name: {sha: i for i, sha in enumerate(chain)} for name, chain in chains.items()}
92
+ trunk_owned = set(chains[trunk])
93
+ branches[trunk].own_commits = chains[trunk]
94
+ absorbs: dict[str, set[str]] = {name: set() for name in branches}
95
+ for name, chain in chains.items():
96
+ for sha in chain:
97
+ for p in commits[sha].parents[1:]:
98
+ for other, other_tip in tips.items():
99
+ if other != name and p == other_tip:
100
+ absorbs[name].add(other)
101
+ changed = True
102
+ while changed:
103
+ changed = False
104
+ for name, others in absorbs.items():
105
+ grown = others | {a for o in others for a in absorbs[o]}
106
+ if grown != others:
107
+ absorbs[name] = grown
108
+ changed = True
109
+ non_trunk_contenders: dict[str, list[str]] = {}
110
+ for name, chain in chains.items():
111
+ if name == trunk:
112
+ continue
113
+ for sha in chain:
114
+ non_trunk_contenders.setdefault(sha, []).append(name)
115
+
116
+ def owner_ignoring_trunk(sha: str) -> Optional[str]:
117
+ names = non_trunk_contenders[sha]
118
+ if len(names) == 1:
119
+ return names[0]
120
+ at_zero = [n for n in names if index_in[n][sha] == 0]
121
+ if len(at_zero) == 1:
122
+ return at_zero[0]
123
+ absorbing = [n for n in names if all((o == n or o in absorbs[n] for o in names))]
124
+ if absorbing:
125
+ return sorted(absorbing)[0]
126
+ min_index = min((index_in[n][sha] for n in names))
127
+ tied = [n for n in names if index_in[n][sha] == min_index]
128
+ return tied[0] if len(tied) == 1 else None
129
+
130
+ def owner(sha: str) -> Optional[str]:
131
+ return trunk if sha in trunk_owned else owner_ignoring_trunk(sha)
132
+ for name, binfo in branches.items():
133
+ if name == trunk:
134
+ continue
135
+ chain = chains[name]
136
+ boundary = next((i for i, sha in enumerate(chain) if owner(sha) not in (name, None)), len(chain))
137
+ boundary_ignoring_trunk = next((i for i, sha in enumerate(chain) if owner_ignoring_trunk(sha) != name), len(chain))
138
+ binfo.own_commits = chain[:boundary]
139
+ if boundary < len(chain):
140
+ binfo.fork_point = chain[boundary]
141
+ binfo.parent = owner(binfo.fork_point)
142
+ if boundary == 0 and boundary_ignoring_trunk > 0:
143
+ binfo.grafted = chain[:boundary_ignoring_trunk]
144
+ for name, binfo in branches.items():
145
+ if binfo.parent:
146
+ branches[binfo.parent].children.append(name)
147
+ owned_by: dict[str, str] = {sha: name for name, b in branches.items() for sha in b.own_commits}
148
+ for c in commits.values():
149
+ for p in c.parents[1:]:
150
+ owner_name = owned_by.get(p)
151
+ if owner_name:
152
+ branches[owner_name].merged = True
153
+ return branches
154
+
155
+ def root_branches(branches: dict[str, Branch]) -> list[str]:
156
+ return sorted((name for name, b in branches.items() if b.parent is None))
157
+
158
+ def merge_source_branch(c: Commit, branches: dict[str, Branch]) -> Optional[str]:
159
+ for p in c.parents[1:]:
160
+ for name, b in branches.items():
161
+ if p == b.tip or p in b.own_commits:
162
+ return name
163
+ return None
164
+
165
+ def branch_detail(b: Branch, commits: dict[str, Commit]) -> Text:
166
+ t = Text()
167
+ t.append('Branch: ', style='bold cyan')
168
+ t.append(b.name + '\n\n', style='bold')
169
+ t.append('Tip: ', style='dim')
170
+ t.append(f'{b.tip[:7]} {commits[b.tip].subject}\n')
171
+ t.append('Parent: ', style='dim')
172
+ t.append(f'{b.parent}\n' if b.parent else '(root -- no parent found)\n')
173
+ if b.fork_point:
174
+ t.append('Forked: ', style='dim')
175
+ t.append(f'{b.fork_point[:7]} {commits[b.fork_point].subject}\n')
176
+ t.append('Merged: ', style='dim')
177
+ t.append('yes (real merge commit)\n' if b.merged else 'no\n')
178
+ if b.grafted:
179
+ t.append('Note: ', style='dim')
180
+ t.append(f'{len(b.grafted)} commit(s) fast-forwarded into its parent, no merge commit\n')
181
+ t.append('Commits: ', style='dim')
182
+ t.append(f'{len(b.own_commits)} of its own\n')
183
+ return t
184
+
185
+ def commit_detail(c: Commit, git: Git) -> Text:
186
+ t = Text()
187
+ t.append('Commit: ', style='bold yellow')
188
+ t.append(c.sha + '\n\n', style='bold')
189
+ t.append('Author: ', style='dim')
190
+ t.append(f'{c.author} ({c.date})\n')
191
+ t.append('Parents: ', style='dim')
192
+ t.append(', '.join((p[:7] for p in c.parents)) or '(none -- root commit)')
193
+ t.append('\n\n')
194
+ t.append(c.subject + '\n', style='bold')
195
+ if c.body.strip():
196
+ t.append(c.body.strip() + '\n')
197
+ t.append('\n')
198
+ diff = git.diff(c.sha, c.parents[0] if c.parents else None)
199
+ for line in diff.splitlines():
200
+ style = 'green' if line.startswith('+') and (not line.startswith('+++')) else 'red' if line.startswith('-') and (not line.startswith('---')) else 'cyan' if line.startswith('@@') else 'white'
201
+ t.append(line + '\n', style=style)
202
+ return t
203
+
204
+ def build_nodes(branches: dict[str, Branch], commits: dict[str, Commit], roots: list[str], current_branch: Optional[str], git: Git) -> list[Node]:
205
+
206
+ def commit_node(sha: str, children: list[Node]) -> Node:
207
+ c = commits[sha]
208
+ merge_from = merge_source_branch(c, branches) if len(c.parents) > 1 else None
209
+ label = Text()
210
+ label.append('● ', style='magenta' if merge_from else 'yellow')
211
+ label.append(c.short + ' ')
212
+ label.append(c.subject)
213
+ if merge_from:
214
+ label.append(f' (⑂ {merge_from})', style='dim')
215
+ return Node(id=f'commit:{sha}', label=label, children=children, detail=lambda: commit_detail(c, git))
216
+
217
+ def graft_node(name: str) -> Node:
218
+ b = branches[name]
219
+ label = Text()
220
+ label.append('〇 ', style='dim cyan')
221
+ label.append(name, style='dim')
222
+ label.append(' (fast-forwarded, no merge commit)', style='dim italic')
223
+ children = [commit_node(sha, []) for sha in reversed(b.grafted)]
224
+ return Node(id=f'graft:{name}', label=label, children=children, detail=lambda: branch_detail(b, commits))
225
+
226
+ def branch_node(name: str) -> Node:
227
+ b = branches[name]
228
+ label = Text()
229
+ label.append('⑂ ', style='cyan')
230
+ label.append(name, style='bold' if name == current_branch else '')
231
+ if b.merged:
232
+ label.append(' *', style='yellow')
233
+ if name == current_branch:
234
+ label.append(' [HEAD]', style='bold green')
235
+ by_fork: dict[str, list[str]] = {}
236
+ grafts_by_tip: dict[str, str] = {}
237
+ for child in b.children:
238
+ cb = branches[child]
239
+ if cb.grafted:
240
+ grafts_by_tip[cb.grafted[0]] = child
241
+ else:
242
+ by_fork.setdefault(cb.fork_point, []).append(child)
243
+ children: list[Node] = []
244
+ commit_list = list(reversed(b.own_commits))
245
+ i, n = (0, len(commit_list))
246
+ while i < n:
247
+ sha = commit_list[i]
248
+ if sha in grafts_by_tip:
249
+ gname = grafts_by_tip[sha]
250
+ children.append(graft_node(gname))
251
+ i += len(branches[gname].grafted)
252
+ continue
253
+ kids = [branch_node(c) for c in by_fork.get(sha, [])]
254
+ children.append(commit_node(sha, kids))
255
+ i += 1
256
+ return Node(id=f'branch:{name}', label=label, children=children, detail=lambda: branch_detail(b, commits))
257
+ return [branch_node(name) for name in roots]
258
+
259
+ def load_repo(path: str) -> tuple[Git, dict[str, Branch], dict[str, Commit], list[str], Optional[str]]:
260
+ git = Git(path)
261
+ tips = git.branch_tips()
262
+ commits = git.load_commits()
263
+ trunk = pick_trunk(git, tips)
264
+ branches = infer_hierarchy(commits, tips, trunk)
265
+ roots = root_branches(branches)
266
+ current = git.current_branch()
267
+ return (git, branches, commits, roots, current)
268
+
269
+ def main() -> int:
270
+ args = sys.argv[1:]
271
+ dump = '--dump' in args
272
+ args = [a for a in args if a != '--dump']
273
+ path = args[0] if args else '.'
274
+ try:
275
+ git, branches, commits, roots, current = load_repo(path)
276
+ except RuntimeError as e:
277
+ print(f'Error: {e}', file=sys.stderr)
278
+ return 1
279
+ nodes = build_nodes(branches, commits, roots, current, git)
280
+ if dump:
281
+ Console().print(dump_text(nodes))
282
+ return 0
283
+ TreeApp(nodes, expanded={n.id for n in nodes}).run()
284
+ return 0
285
+ if __name__ == '__main__':
286
+ sys.exit(main())
vgt-0.0.1a0/render.py ADDED
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import Callable, Optional, Union
4
+ from rich.text import Text
5
+ from textual.app import App, ComposeResult
6
+ from textual.containers import Horizontal, VerticalScroll
7
+ from textual.widgets import Static
8
+ from textual.reactive import reactive
9
+ from textual.message import Message
10
+ Detail = Union[str, Text, Callable[[], Union[str, Text]], None]
11
+
12
+ @dataclass
13
+ class Node:
14
+ id: str
15
+ label: Union[str, Text]
16
+ children: list['Node'] = field(default_factory=list)
17
+ detail: Detail = None
18
+
19
+ @dataclass
20
+ class FlatRow:
21
+ node: Node
22
+ depth: int
23
+ ancestor_last: list[bool]
24
+ is_last: bool
25
+
26
+ def flatten(roots: list[Node], expanded: set[str]) -> list[FlatRow]:
27
+ rows: list[FlatRow] = []
28
+
29
+ def emit(node: Node, depth: int, ancestor_last: list[bool], is_last: bool) -> None:
30
+ rows.append(FlatRow(node, depth, list(ancestor_last), is_last))
31
+ if node.id not in expanded or not node.children:
32
+ return
33
+ child_ancestor = ancestor_last + [is_last]
34
+ for i, child in enumerate(node.children):
35
+ emit(child, depth + 1, child_ancestor, i == len(node.children) - 1)
36
+ for i, r in enumerate(roots):
37
+ emit(r, 0, [], i == len(roots) - 1)
38
+ return rows
39
+
40
+ def render_row_line(row: FlatRow, is_cursor: bool=False, max_width: Optional[int]=None) -> Text:
41
+ text = Text()
42
+ for last in row.ancestor_last:
43
+ text.append(' ' if last else '│ ')
44
+ if row.depth > 0:
45
+ text.append('└─ ' if row.is_last else '├─ ')
46
+ text.append(row.node.label if isinstance(row.node.label, Text) else Text(row.node.label))
47
+ if max_width is not None and text.cell_len > max_width:
48
+ text.truncate(max_width, overflow='ellipsis')
49
+ if is_cursor:
50
+ text.stylize('reverse')
51
+ return text
52
+
53
+ def all_ids(roots: list[Node]) -> set[str]:
54
+ ids: set[str] = set()
55
+
56
+ def walk(n: Node) -> None:
57
+ ids.add(n.id)
58
+ for c in n.children:
59
+ walk(c)
60
+ for r in roots:
61
+ walk(r)
62
+ return ids
63
+
64
+ def dump_text(roots: list[Node], expanded: Optional[set[str]]=None) -> Text:
65
+ rows = flatten(roots, expanded if expanded is not None else all_ids(roots))
66
+ out = Text()
67
+ for row in rows:
68
+ out.append(render_row_line(row))
69
+ out.append('\n')
70
+ return out
71
+
72
+ def _resolve(detail: Detail) -> Union[str, Text]:
73
+ value = detail() if callable(detail) else detail
74
+ return value if value is not None else ''
75
+
76
+ class TreeWidget(Static):
77
+ can_focus = True
78
+ cursor_index: reactive[int] = reactive(0)
79
+ viewport_y: reactive[int] = reactive(0)
80
+
81
+ class CursorMoved(Message):
82
+
83
+ def __init__(self, node: Optional[Node]) -> None:
84
+ self.node = node
85
+ super().__init__()
86
+
87
+ def __init__(self, roots: list[Node], expanded: Optional[set[str]]=None, **kwargs):
88
+ super().__init__(**kwargs)
89
+ self.roots = roots
90
+ self.expanded: set[str] = expanded if expanded is not None else set()
91
+ self.rows: list[FlatRow] = []
92
+
93
+ def on_mount(self) -> None:
94
+ self.recompute()
95
+ self.focus()
96
+
97
+ def recompute(self) -> None:
98
+ prev_id = self.rows[self.cursor_index].node.id if self.rows and 0 <= self.cursor_index < len(self.rows) else None
99
+ self.rows = flatten(self.roots, self.expanded)
100
+ if prev_id is not None:
101
+ for i, r in enumerate(self.rows):
102
+ if r.node.id == prev_id:
103
+ self.cursor_index = i
104
+ break
105
+ else:
106
+ self.cursor_index = min(self.cursor_index, len(self.rows) - 1)
107
+ self._ensure_visible()
108
+ self.refresh()
109
+ self._emit_cursor()
110
+
111
+ def _emit_cursor(self) -> None:
112
+ self.post_message(self.CursorMoved(self.rows[self.cursor_index].node if self.rows else None))
113
+
114
+ def _ensure_visible(self) -> None:
115
+ height = max(self.size.height, 1)
116
+ if self.cursor_index < self.viewport_y:
117
+ self.viewport_y = self.cursor_index
118
+ elif self.cursor_index >= self.viewport_y + height:
119
+ self.viewport_y = self.cursor_index - height + 1
120
+
121
+ def render(self) -> Text:
122
+ if not self.rows:
123
+ return Text('(empty)', style='dim')
124
+ height = max(self.size.height, 1)
125
+ width = max(self.size.width, 1)
126
+ visible = self.rows[self.viewport_y:self.viewport_y + height]
127
+ lines = [render_row_line(r, is_cursor=self.viewport_y + i == self.cursor_index, max_width=width) for i, r in enumerate(visible)]
128
+ return Text('\n').join(lines)
129
+
130
+ def move(self, delta: int) -> None:
131
+ if not self.rows:
132
+ return
133
+ self.cursor_index = max(0, min(len(self.rows) - 1, self.cursor_index + delta))
134
+ self._ensure_visible()
135
+ self.refresh()
136
+ self._emit_cursor()
137
+
138
+ def collapse_or_up(self) -> None:
139
+ if not self.rows:
140
+ return
141
+ row = self.rows[self.cursor_index]
142
+ if row.node.children and row.node.id in self.expanded:
143
+ self.expanded.discard(row.node.id)
144
+ self.recompute()
145
+ return
146
+ target_depth = row.depth - 1
147
+ for i in range(self.cursor_index - 1, -1, -1):
148
+ if self.rows[i].depth == target_depth:
149
+ self.cursor_index = i
150
+ self._ensure_visible()
151
+ self.refresh()
152
+ self._emit_cursor()
153
+ return
154
+
155
+ def expand_row(self) -> None:
156
+ if not self.rows:
157
+ return
158
+ row = self.rows[self.cursor_index]
159
+ if row.node.children:
160
+ self.expanded.add(row.node.id)
161
+ self.recompute()
162
+
163
+ class ContentWidget(VerticalScroll):
164
+
165
+ def compose(self) -> ComposeResult:
166
+ yield Static(id='content-text')
167
+
168
+ def show(self, node: Optional[Node]) -> None:
169
+ text = _resolve(node.detail) if node else '(nothing selected)'
170
+ if isinstance(text, str):
171
+ text = Text(text)
172
+ self.query_one('#content-text', Static).update(text)
173
+ self.scroll_home(animate=False)
174
+ CSS = '\nScreen { layout: horizontal; }\n#tree { width: 2fr; height: 100%; border: solid $primary; padding: 0 1; }\n#content { width: 3fr; height: 100%; border: solid $accent; padding: 0 1; overflow-y: auto; }\n#content-text { width: 100%; height: auto; }\n'
175
+
176
+ class TreeApp(App):
177
+ CSS = CSS
178
+ BINDINGS = [('j', 'move_down', 'Down'), ('k', 'move_up', 'Up'), ('h', 'collapse', 'Collapse/parent'), ('l', 'expand', 'Expand'), ('q', 'quit', 'Quit')]
179
+
180
+ def __init__(self, roots: list[Node], expanded: Optional[set[str]]=None):
181
+ super().__init__()
182
+ self.roots = roots
183
+ self.initial_expanded = expanded
184
+
185
+ def compose(self) -> ComposeResult:
186
+ with Horizontal():
187
+ yield TreeWidget(self.roots, self.initial_expanded, id='tree')
188
+ yield ContentWidget(id='content')
189
+
190
+ def on_tree_widget_cursor_moved(self, event: TreeWidget.CursorMoved) -> None:
191
+ self.query_one('#content', ContentWidget).show(event.node)
192
+
193
+ def action_move_down(self) -> None:
194
+ self.query_one('#tree', TreeWidget).move(1)
195
+
196
+ def action_move_up(self) -> None:
197
+ self.query_one('#tree', TreeWidget).move(-1)
198
+
199
+ def action_collapse(self) -> None:
200
+ self.query_one('#tree', TreeWidget).collapse_or_up()
201
+
202
+ def action_expand(self) -> None:
203
+ self.query_one('#tree', TreeWidget).expand_row()
vgt-0.0.1a0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
vgt-0.0.1a0/setup.py ADDED
@@ -0,0 +1,38 @@
1
+ from setuptools import setup, find_packages
2
+ from pathlib import Path
3
+
4
+ long_description = (Path(__file__).parent / "README.md").read_text(encoding="utf-8")
5
+
6
+ setup(
7
+ name="vgt",
8
+ url="https://www.mlx-code.com",
9
+ project_urls={
10
+ "Source": "https://github.com/JosefAlbers/mlx-code",
11
+ "Issues": "https://github.com/JosefAlbers/mlx-code/issues",
12
+ "Documentation": "https://www.mlx-code.com/",
13
+ },
14
+ author_email="albersj66@gmail.com",
15
+ author="J Joe",
16
+ license="Apache-2.0",
17
+ version="0.0.1a0",
18
+ readme="README.md",
19
+ description="TUI git viewer",
20
+ long_description=long_description,
21
+ long_description_content_type="text/markdown",
22
+ python_requires=">=3.12.8",
23
+ install_requires=[
24
+ "textual>=8.2.7",
25
+ "rich>=15.0.0",
26
+ ],
27
+ extras_require={
28
+ "all": [
29
+ ],
30
+ },
31
+ # packages=find_packages(),
32
+ py_modules=["main", "render"],
33
+ entry_points={
34
+ "console_scripts": [
35
+ "vgt=main:main",
36
+ ]
37
+ },
38
+ )
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: vgt
3
+ Version: 0.0.1a0
4
+ Summary: TUI git viewer
5
+ Home-page: https://www.mlx-code.com
6
+ Author: J Joe
7
+ Author-email: albersj66@gmail.com
8
+ License: Apache-2.0
9
+ Project-URL: Source, https://github.com/JosefAlbers/mlx-code
10
+ Project-URL: Issues, https://github.com/JosefAlbers/mlx-code/issues
11
+ Project-URL: Documentation, https://www.mlx-code.com/
12
+ Requires-Python: >=3.12.8
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: textual>=8.2.7
15
+ Requires-Dist: rich>=15.0.0
16
+ Provides-Extra: all
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: license
23
+ Dynamic: project-url
24
+ Dynamic: provides-extra
25
+ Dynamic: requires-dist
26
+ Dynamic: requires-python
27
+ Dynamic: summary
28
+
29
+ view git
@@ -0,0 +1,10 @@
1
+ README.md
2
+ main.py
3
+ render.py
4
+ setup.py
5
+ vgt.egg-info/PKG-INFO
6
+ vgt.egg-info/SOURCES.txt
7
+ vgt.egg-info/dependency_links.txt
8
+ vgt.egg-info/entry_points.txt
9
+ vgt.egg-info/requires.txt
10
+ vgt.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ vgt = main:main
@@ -0,0 +1,4 @@
1
+ textual>=8.2.7
2
+ rich>=15.0.0
3
+
4
+ [all]
@@ -0,0 +1,2 @@
1
+ main
2
+ render