visidata 2.11.1__py3-none-any.whl → 3.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.
Files changed (255) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +65 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +59 -50
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +5 -1
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +30 -5
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +163 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +75 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +180 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +17 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +48 -10
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +301 -148
  187. visidata/man/vd.txt +290 -153
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +50 -201
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +229 -257
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +113 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/conftest.py +3 -3
  213. visidata/tests/test_cliptext.py +39 -0
  214. visidata/tests/test_commands.py +62 -7
  215. visidata/tests/test_edittext.py +2 -2
  216. visidata/tests/test_features.py +17 -0
  217. visidata/tests/test_menu.py +14 -0
  218. visidata/tests/test_path.py +13 -4
  219. visidata/text_source.py +53 -0
  220. visidata/textsheet.py +10 -3
  221. visidata/theme.py +44 -0
  222. visidata/themes/__init__.py +0 -0
  223. visidata/themes/ascii8.py +84 -0
  224. visidata/themes/asciimono.py +84 -0
  225. visidata/themes/light.py +17 -0
  226. visidata/threads.py +87 -39
  227. visidata/tuiwin.py +22 -0
  228. visidata/type_currency.py +22 -3
  229. visidata/type_date.py +31 -9
  230. visidata/type_floatsi.py +5 -1
  231. visidata/undo.py +17 -5
  232. visidata/utils.py +106 -23
  233. visidata/vdobj.py +28 -17
  234. visidata/windows.py +10 -0
  235. visidata/wrappers.py +9 -3
  236. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  237. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/vd.1 +301 -148
  238. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +301 -148
  239. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  240. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/METADATA +12 -8
  241. visidata-3.0.dist-info/RECORD +257 -0
  242. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  243. vgit/__init__.py +0 -1
  244. vgit/gitsheet.py +0 -164
  245. visidata/layout.py +0 -44
  246. visidata/misc.py +0 -5
  247. visidata-2.11.1.data/scripts/vgit +0 -9
  248. visidata-2.11.1.dist-info/RECORD +0 -155
  249. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  250. {vgit → visidata/apps/vgit}/abort.py +0 -0
  251. /visidata/{repeat.py → features/repeat.py} +0 -0
  252. {visidata-2.11.1.data → visidata-3.0.data}/scripts/vd +0 -0
  253. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,161 @@
1
+ import io
2
+
3
+ from visidata import AttrDict, vd, Path, asyncthread, Sheet
4
+
5
+
6
+ class GitSheet(Sheet):
7
+ @property
8
+ def gitargstr(self):
9
+ return ' '.join(self.gitargs)
10
+
11
+ def git(self, subcmd, *args, **kwargs):
12
+ 'For non-modifying commands; not logged except in debug mode'
13
+ sh = vd.importExternal('sh')
14
+ args = list(subcmd.split()) + list(args)
15
+ vd.debug('git ' + ' '.join(str(x) for x in args))
16
+ return sh.git(*args,
17
+ _cwd=self.gitRootPath,
18
+ **kwargs)
19
+
20
+ def loggit(self, subcmd, *args, **kwargs):
21
+ 'Run git command with *args*, and post a status message.'
22
+ import sh
23
+ args = list(subcmd.split()) + list(args)
24
+ vd.warning('git ' + ' '.join(str(x) for x in args))
25
+ return sh.git(*args,
26
+ _cwd=self.gitRootPath,
27
+ **kwargs)
28
+
29
+ def git_all(self, *args, **kwargs):
30
+ 'Return entire output of git command.'
31
+ sh = vd.importExternal('sh')
32
+ try:
33
+ vd.debug('git ' + ' '.join(str(x) for x in args))
34
+ out = self.git('--no-pager',
35
+ *args,
36
+ _decode_errors='replace',
37
+ _bg_exc=False,
38
+ **kwargs)
39
+ except sh.ErrorReturnCode as e:
40
+ vd.warning('git '+' '.join(str(x) for x in args), 'error=%s' % e.exit_code)
41
+ out = e.stdout
42
+
43
+ return out
44
+
45
+ def git_lines(self, subcmd, *args, **kwargs):
46
+ 'Generator of stdout lines from given git command'
47
+ sh = vd.importExternal('sh')
48
+ err = io.StringIO()
49
+ args = list(subcmd.split()) + list(args)
50
+ try:
51
+ vd.debug('git ' + ' '.join(str(x) for x in args))
52
+ for line in self.git('--no-pager',
53
+ *args,
54
+ _decode_errors='replace',
55
+ _iter=True,
56
+ _bg_exc=False,
57
+ _err=err,
58
+ **kwargs):
59
+ yield line[:-1] # remove EOL
60
+
61
+ except sh.ErrorReturnCode as e:
62
+ vd.warning('git '+' '.join(str(x) for x in args), 'error=%s' % e.exit_code)
63
+
64
+ errlines = err.getvalue().splitlines()
65
+ if errlines:
66
+ vd.warning('git stderr: ' + '\n'.join(errlines))
67
+
68
+
69
+ def git_iter(self, subcmd, *args, sep='\0', **kwargs):
70
+ 'Generator of chunks of stdout from given git command *subcmd*, delineated by sep character.'
71
+ sh = vd.importExternal('sh')
72
+ import sh
73
+ err = io.StringIO()
74
+
75
+ args = list(subcmd.split()) + list(args)
76
+ bufsize = 512
77
+ chunks = []
78
+ try:
79
+ vd.debug('git ' + ' '.join(str(x) for x in args))
80
+ for data in self.git('--no-pager',
81
+ *args,
82
+ _decode_errors='replace',
83
+ _out_bufsize=bufsize,
84
+ _iter=True,
85
+ _bg_exc=False,
86
+ _err=err,
87
+ **kwargs):
88
+ while True:
89
+ i = data.find(sep)
90
+ if i < 0:
91
+ break
92
+ chunks.append(data[:i])
93
+ data = data[i+1:]
94
+ yield ''.join(chunks)
95
+ chunks.clear()
96
+
97
+ chunks.append(data)
98
+ except sh.ErrorReturnCode as e:
99
+ vd.warning('git '+' '.join(str(x) for x in args), 'error=%s' % e.exit_code)
100
+
101
+ if chunks:
102
+ yield ''.join(chunks)
103
+
104
+ errlines = err.getvalue().splitlines()
105
+ if errlines:
106
+ vd.warning('git stderr: ' + '\n'.join(errlines))
107
+
108
+ @asyncthread
109
+ def modifyGit(self, *args, **kwargs):
110
+ 'Run git command that modifies the repo'
111
+ vd.warning('git ' + ' '.join(str(x) for x in args))
112
+ ret = self.git_all(*args, **kwargs)
113
+ vd.status(ret)
114
+
115
+ if isinstance(self.source, GitSheet):
116
+ self.source.reload()
117
+
118
+ self.reload()
119
+
120
+ @property
121
+ def gitRootSheet(self):
122
+ if isinstance(self.source, GitSheet):
123
+ return self.source.gitRootSheet
124
+ return self
125
+
126
+ def iterload(self):
127
+ for line in self.git_lines(*self.gitargs):
128
+ yield AttrDict(line=line)
129
+
130
+
131
+ @GitSheet.lazy_property
132
+ def gitRootPath(self):
133
+ 'Return Path of git root (nearest ancestor directory with a .git/)'
134
+ def _getRepoPath(p):
135
+ 'Return path at p or above which has .git subdir'
136
+ if p.joinpath('.git').exists():
137
+ return p
138
+ if str(p) in ['/','']:
139
+ return None
140
+ return _getRepoPath(p.resolve().parent)
141
+
142
+ p = _getRepoPath(self.gitRootSheet.source)
143
+ if p:
144
+ return p
145
+
146
+
147
+ @GitSheet.lazy_property
148
+ def branch(self):
149
+ return self.git('rev-parse', '--abbrev-ref', 'HEAD').strip()
150
+
151
+
152
+ GitSheet.options.disp_note_none = ''
153
+ GitSheet.options.disp_status_fmt = '{sheet.progressStatus}‹{sheet.branchStatus}› {sheet.name}| '
154
+
155
+ GitSheet.addCommand('gi', 'git-exec', 'cmdstr=input("gi", type="git"); vd.push(GitSheet(cmdstr, gitargs=cmdstr.split()))', 'execute git command')
156
+
157
+ GitSheet.addCommand('Alt+g', 'menu-git', 'pressMenu("Git")', '')
158
+
159
+ vd.addMenuItems('''
160
+ Git > Execute command > git-exec
161
+ ''')
@@ -4,13 +4,13 @@ from .gitsheet import GitSheet
4
4
 
5
5
 
6
6
  @VisiData.api
7
- def git_grep(vd, args):
8
- return GitGrep(args[0], regex=args[0], source=Path('.'))
7
+ def git_grep(vd, p, args):
8
+ return GitGrep(args[0], regex=args[0], source=p)
9
9
 
10
10
 
11
11
  class GitGrep(GitSheet):
12
12
  rowtype = 'results' # rowdef: list(file, line, line_contents)
13
- help = '''
13
+ guide = '''
14
14
  # vgit grep
15
15
  Each row on this sheet is a line matching the regex pattern `{sheet.regex}` in the tracked files of the current directory.
16
16
 
@@ -27,10 +27,11 @@ class GitGrep(GitSheet):
27
27
  tmp = (self.topRowIndex, self.cursorRowIndex)
28
28
  for line in self.git_lines('grep', '--no-color', '-z', '--line-number', '--ignore-case', self.regex):
29
29
  # line = line.replace(ESC+'[1;31m', '[:green]')
30
- # line = line.replace(ESC+'[m', '[:]')
30
+ # line = line.replace(ESC+'[m', '[/]')
31
31
  yield list(line.split('\0'))
32
32
  self.topRowIndex, self.cursorRowIndex = tmp
33
33
 
34
34
 
35
- GitSheet.addCommand('g/', 'git-grep', 'rex=input("git grep: "); vd.push(GitGrep(rex, regex=rex, source=sheet))', 'find in all files in this repo'),
35
+ GitSheet.addCommand('g/', 'git-grep', 'rex=inputRegex("git grep: "); vd.push(GitGrep(rex, regex=rex, source=sheet))', 'find in all files in this repo')
36
36
  GitGrep.addCommand('Ctrl+O', 'sysopen-row', 'launchExternalEditorPath(Path(cursorRow[0]), linenum=cursorRow[1]); reload()', 'open this file in $EDITOR')
37
+ GitGrep.bindkey('Enter', 'sysopen-row')
@@ -0,0 +1,81 @@
1
+ import functools
2
+
3
+ from visidata import vd, VisiData, Column, ItemColumn, date, RowColorizer, asyncthread, Progress, AttrDict
4
+
5
+ from .gitsheet import GitSheet
6
+
7
+
8
+ @VisiData.api
9
+ def git_log(vd, p, *args):
10
+ return GitLogSheet('git-log', source=p, gitargs=args)
11
+
12
+ # rowdef: (commit_hash, refnames, author, author_date, body, notes)
13
+ class GitLogSheet(GitSheet):
14
+ guide = '''
15
+ # git log {sheet.gitargstr}
16
+ {sheet.cursorRow.message}
17
+ '''
18
+ GIT_LOG_FORMAT = ['%H', '%D', '%an <%ae>', '%ai', '%B', '%N']
19
+ rowtype = 'commits' # rowdef: AttrDict
20
+ defer = True
21
+ savesToSource = True
22
+ columns = [
23
+ ItemColumn('commitid', width=8),
24
+ ItemColumn('refnames', width=12),
25
+ ItemColumn('message', type=str.strip, setter=lambda c,r,v: c.sheet.git('commit --amend --no-edit --quiet --message', v), width=50),
26
+ ItemColumn('author', setter=lambda c,r,v: c.sheet.git('commit --amend --no-edit --quiet --author', v)),
27
+ ItemColumn('author_date', type=date, setter=lambda c,r,v: c.sheet.git('commit --amend --no-edit --quiet --date', v)),
28
+ ItemColumn('notes', setter=lambda c,r,v: c.sheet.git('notes add --force --message', v, r.commitid)),
29
+ ]
30
+ colorizers = [
31
+ RowColorizer(5, 'color_vgit_unpushed', lambda s,c,r,v: r and not s.inRemoteBranch(r.commitid)),
32
+ ]
33
+
34
+ def __init__(self, *args, **kwargs):
35
+ super().__init__(*args, **kwargs)
36
+
37
+ @functools.lru_cache()
38
+ def inRemoteBranch(self, commitid):
39
+ return self.git_all('branch -r --contains', commitid, _ok_code=[0, 1])
40
+
41
+ def iterload(self):
42
+ lines = self.git_iter('log --no-color -z', '--pretty=format:' + '%x1f'.join(self.GIT_LOG_FORMAT), *self.gitargs)
43
+ for record in Progress(tuple(lines)):
44
+ r = record.split('\x1f')
45
+ yield AttrDict(
46
+ commitid=r[0],
47
+ refnames=r[1],
48
+ author=r[2],
49
+ author_date=r[3],
50
+ message=r[4],
51
+ notes=r[5],
52
+ )
53
+
54
+ def openRow(self, row):
55
+ 'open this commit'
56
+ return getCommitSheet(row[0][:7], self, row[0])
57
+
58
+ @asyncthread
59
+ def commit(self, path, adds, mods, dels):
60
+
61
+ assert not adds
62
+ assert not dels
63
+
64
+ for row, rowmods in mods.values():
65
+ for col, val in rowmods.values():
66
+ vd.callNoExceptions(col.putValue, row, val)
67
+
68
+ self.reload()
69
+ self.resetDeferredCommit()
70
+
71
+
72
+ GitLogSheet.addCommand(None, 'delete-row', 'error("delete is not supported")')
73
+ GitLogSheet.addCommand(None, 'add-row', 'error("commits cannot be added")')
74
+ #GitLogSheet.addCommand('x', 'git-pick', 'git("cherry-pick", cursorRow.commitid)', 'cherry-pick this commit onto current branch')
75
+ #GitLogSheet.addCommand('r', 'git-reset-here', 'git("update-ref", "refs/heads/"+source, cursorRow[0])', 'reset this branch to this commit')
76
+
77
+ GitSheet.addCommand('', 'git-log', 'vd.push(git_log(gitRootPath, branch))', 'push log of current branch')
78
+
79
+ vd.addMenuItems('''
80
+ Git > Open > log > git-log
81
+ ''')
@@ -4,9 +4,10 @@
4
4
  The syntax for vgit is the same as the syntax for git.
5
5
  By default, will pass the command to git verbatim, as quickly as possible.
6
6
  If vgit can provide an interactive interface for a particular subcommand,
7
- it will open the sheet returned by vd.git_<subcommand>().
7
+ it will open the sheet returned by vd.git_<subcommand>(path, args).
8
8
  '''
9
9
 
10
+ import os
10
11
  import sys
11
12
 
12
13
 
@@ -20,23 +21,35 @@ def vgit_cli():
20
21
  args.remove('--debug')
21
22
 
22
23
  if not args:
23
- # return vd.run(vd.git_help())
24
- return
24
+ args = ['help']
25
25
 
26
26
  func = getattr(vd, 'git_'+args[0], None)
27
27
  if func:
28
28
  vd.loadConfigAndPlugins()
29
29
  vd.status(visidata.__version_info__)
30
30
  vd.domotd()
31
+ if flDebug:
32
+ vd.options.debug = True
31
33
 
34
+ rc = 0
32
35
  try:
33
- vs = func(args[1:])
36
+ p = Path('.')
37
+ vs = func(p, args[1:])
34
38
  if vs:
35
- return vd.run(vs)
39
+ vd.run(vs)
40
+ except BrokenPipeError:
41
+ os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno()) # handle broken pipe gracefully
42
+ except visidata.ExpectedException as e:
43
+ print(str(e))
36
44
  except Exception as e:
45
+ rc = 1
37
46
  vd.exceptionCaught(e)
38
47
  if flDebug:
39
48
  raise
40
49
 
50
+ sys.stderr.flush()
51
+ sys.stdout.flush()
52
+ os._exit(rc) # cleanup can be expensive
53
+
41
54
  import subprocess
42
55
  return subprocess.run(['git', *args]).returncode
@@ -3,13 +3,13 @@ from visidata import vd, VisiData, ItemColumn, AttrDict, RowColorizer, Path
3
3
  from .gitsheet import GitSheet
4
4
 
5
5
  @VisiData.api
6
- def git_remote(vd, args=None):
6
+ def git_remote(vd, p, args):
7
7
  if not args or 'show' in args:
8
- return GitRemotes('remotes', source=Path('.'))
8
+ return GitRemotes('remotes', source=p)
9
9
 
10
10
 
11
11
  class GitRemotes(GitSheet):
12
- help = '''
12
+ guide = '''
13
13
  # git remote
14
14
  Manage the set of repositories ("remotes") whose branches you track.
15
15
 
@@ -50,4 +50,8 @@ class GitRemotes(GitSheet):
50
50
  return AttrDict()
51
51
 
52
52
 
53
- vd.addCommand('', 'git-open-remotes', 'vd.push(git_remote())', 'open git remotes sheet')
53
+ GitSheet.addCommand('', 'git-open-remotes', 'vd.push(git_remote(Path("."), ""))', 'open git remotes sheet')
54
+
55
+ vd.addMenuItems('''
56
+ Git > Open > remotes > git-open-remotes
57
+ ''')
@@ -0,0 +1,71 @@
1
+ from visidata import vd, VisiData, Sheet, Column, AttrColumn, date, vlen, asyncthread, Path, namedlist, PyobjSheet, modtime, AttrDict
2
+
3
+ from .gitsheet import GitSheet
4
+
5
+ @VisiData.api
6
+ def guess_git(vd, p):
7
+ if (p/'.git').is_dir():
8
+ return dict(filetype='git', _likelihood=10)
9
+
10
+
11
+ @VisiData.api
12
+ def open_git(vd, p):
13
+ return vd.git_status(p, [])
14
+
15
+
16
+ @VisiData.api
17
+ def git_repos(vd, p, args):
18
+ return GitRepos(p.base_stem, source=p)
19
+
20
+
21
+ class GitLinesColumn(Column):
22
+ def __init__(self, name, cmd, *args, **kwargs):
23
+ super().__init__(name, cache='async', **kwargs)
24
+ cmdparts = cmd.split()
25
+ if cmdparts[0] == 'git':
26
+ cmdparts = cmdparts[1:]
27
+ self.gitargs = cmdparts + list(args)
28
+
29
+ def calcValue(self, r):
30
+ lines = list(GitSheet(source=r).git_lines(*self.gitargs))
31
+ if lines:
32
+ return lines
33
+
34
+
35
+ class GitAllColumn(GitLinesColumn):
36
+ def calcValue(self, r):
37
+ return GitSheet(source=r).git_all(*self.gitargs).strip()
38
+
39
+
40
+
41
+ class GitRepos(GitSheet):
42
+ guide = '''
43
+ # git repos
44
+ A list of git repositories under `{sheet.source}`
45
+
46
+ - `Enter` to open the status sheet for the current repo
47
+ '''
48
+ rowtype = 'git repos' # rowdef: Path
49
+ columns = [
50
+ Column('repo', type=str, width=30),
51
+ GitAllColumn('branch', 'git rev-parse --abbrev-ref HEAD', width=8),
52
+ GitLinesColumn('diffs', 'git diff --no-color', type=vlen, width=8),
53
+ GitLinesColumn('staged_diffs', 'git diff --cached', type=vlen, width=8),
54
+ GitLinesColumn('branches', 'git branch --no-color', type=vlen, width=10),
55
+ GitLinesColumn('stashes', 'git stash list', type=vlen, width=8),
56
+ Column('modtime', type=date, getter=lambda c,r: modtime(r)),
57
+ ]
58
+ nKeys = 1
59
+
60
+ def iterload(self):
61
+ import glob
62
+ for fn in glob.glob('**/.git', root_dir=self.source, recursive=True):
63
+ yield Path(fn).parent
64
+
65
+
66
+ def openRow(self, row):
67
+ return vd.git_status(row, [])
68
+
69
+ def openCell(self, col, row):
70
+ val = col.getValue(row)
71
+ return PyobjSheet(getattr(val, '__name__', ''), source=val)
@@ -2,19 +2,21 @@
2
2
 
3
3
  from setuptools import setup, find_packages
4
4
 
5
+ # Note: use `python3 visidata/apps/setup.py install` from the root directory
6
+
5
7
  __version__ = '0.2-dev'
6
8
 
7
9
  setup(name='vgit',
8
10
  version=__version__,
9
11
  description='a sleek terminal user interface for git',
10
12
  # long_description=open('README.md').read(),
11
- install_requires=['sh'], # visidata
12
- package_dir={'':'visidata/apps'},
13
- packages=['vgit'],
13
+ install_requires=['sh<2'], # visidata
14
+ packages=find_packages(exclude=["tests"]),
15
+ scripts=['vgit'],
16
+ entry_points={'visidata.plugins': 'vgit=visidata.apps.vgit'},
14
17
  author='Saul Pwanson',
15
18
  author_email='vgit@saul.pw',
16
19
  url='https://github.com/saulpw/visidata/vgit',
17
- scripts=['bin/vgit'],
18
20
  license='GPLv3',
19
21
  python_requires='>=3.7',
20
22
  classifiers=[
@@ -0,0 +1,69 @@
1
+ from visidata import vd, VisiData, ItemColumn, AttrDict
2
+
3
+
4
+ from .gitsheet import GitSheet
5
+ from .diff import GitDiffSheet
6
+
7
+
8
+ @VisiData.api
9
+ def git_stash(vd, p, args):
10
+ if 'list' in args:
11
+ return GitStashes('git-stash-list', source=p, gitargs=args)
12
+
13
+
14
+ class GitStashes(GitSheet):
15
+ guide = '''
16
+ # git stash
17
+ This is the list of changes that have been stashed previously.
18
+
19
+ `a` to apply this stashed change (without removing it)
20
+ `d` to drop this stashed change
21
+ `b` to create a branch from this stashed change'),
22
+ '''
23
+ rowtype = 'stashed change' # rowdef: AttrDict(stashid=, branched_from=, sha1=, msg=)
24
+ columns = [
25
+ ItemColumn('stashid'),
26
+ ItemColumn('branched_from'),
27
+ ItemColumn('sha1'),
28
+ ItemColumn('msg'),
29
+ ItemColumn('line', width=0),
30
+ ]
31
+
32
+ def iterload(self):
33
+ for line in self.git_lines('stash', *self.gitargs):
34
+ stashid, ctx, rest = line.split(': ', 2)
35
+ if ctx.startswith('WIP on '):
36
+ branched_from = ctx[len('WIP on '):]
37
+ sha1, msg = rest.split(' ', 1)
38
+ elif ctx.startswith('On '):
39
+ branched_from = ctx[len('On '):]
40
+ sha1 = ''
41
+ msg = rest
42
+ yield AttrDict(
43
+ line=line,
44
+ stashid=stashid,
45
+ branched_from=branched_from,
46
+ sha1=sha1,
47
+ msg=msg.strip(),
48
+ )
49
+
50
+ def openRow(self, row):
51
+ 'open this stashed change'
52
+ return GitDiffSheet(row.stashid, "diffs", gitargs=['stash show --no-color --patch', row.stashid], source=self.source)
53
+
54
+
55
+ GitSheet.addCommand('', 'git-open-stashes', 'vd.push(git_stash(source, ["list"]))', 'push stashes sheet')
56
+
57
+ GitStashes.addCommand('a', 'git-stash-apply', 'loggit("stash", "apply", cursorRow[0])', 'apply this stashed change without removing')
58
+ GitStashes.addCommand('', 'git-stash-pop', 'loggit("stash", "pop", cursorRow[0])', 'apply this stashed change and drop it')
59
+ GitStashes.addCommand('d', 'git-stash-drop', 'loggit("stash", "drop", cursorRow[0])', 'drop this stashed change')
60
+ GitStashes.addCommand('b', 'git-stash-branch', 'loggit("stash", "branch", input("create branch from stash named: "), cursorRow[0])', 'create branch from stash')
61
+
62
+
63
+ vd.addMenuItems('''
64
+ Git > Open > stashes > git-open-stashes
65
+ Git > Stash > apply > git-stash-apply
66
+ Git > Stash > drop > git-stash-drop
67
+ Git > Stash > apply then drop > git-stash-pop
68
+ Git > Stash > create branch > git-stash-branch
69
+ ''')