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,55 @@
1
+ import os
2
+ import time
3
+
4
+ from visidata import vd, BaseSheet, Sheet, asyncthread, Path, ScopedSetattr
5
+
6
+
7
+ @BaseSheet.api
8
+ @asyncthread
9
+ def reload_every(sheet, seconds:int):
10
+ while True:
11
+ sheet.reload()
12
+ time.sleep(seconds)
13
+
14
+
15
+ @BaseSheet.api
16
+ @asyncthread
17
+ def reload_modified(sheet):
18
+ 'Spawn thread to call sheet.reload_rows when sheet.source mtime has changed.'
19
+ p = sheet.source
20
+ assert isinstance(p, Path)
21
+ assert not p.is_url()
22
+
23
+ mtime = os.stat(p).st_mtime
24
+ while True:
25
+ time.sleep(1)
26
+ t = os.stat(p).st_mtime
27
+ if t != mtime:
28
+ mtime = t
29
+ sheet.reload_rows()
30
+
31
+
32
+ @Sheet.api
33
+ @asyncthread
34
+ def reload_rows(self):
35
+ 'Reload rows from ``self.source``, keeping current columns intact. Async.'
36
+ with (ScopedSetattr(self, 'loading', True),
37
+ ScopedSetattr(self, 'checkCursor', lambda: True),
38
+ ScopedSetattr(self, 'cursorRowIndex', self.cursorRowIndex)):
39
+ self.beforeLoad()
40
+ try:
41
+ self.loader()
42
+ vd.status("finished loading rows")
43
+ finally:
44
+ self.afterLoad()
45
+
46
+
47
+ BaseSheet.addCommand('', 'reload-every', 'sheet.reload_every(input("reload interval (sec): ", value=1))', 'schedule sheet reload every N seconds') #683
48
+ BaseSheet.addCommand('', 'reload-modified', 'sheet.reload_modified()', 'reload sheet when source file modified (tail-like behavior)') #1686
49
+ BaseSheet.addCommand('z^R', 'reload-rows', 'preloadHook(); reload_rows(); status("reloaded")', 'Reload current sheet')
50
+
51
+ vd.addMenuItems('''
52
+ File > Reload > rows only > reload-rows
53
+ File > Reload > every N seconds > reload-every
54
+ File > Reload > when source modified > reload-modified
55
+ ''')
@@ -0,0 +1,30 @@
1
+ import ast
2
+
3
+ from visidata import vd, Column, ExprColumn, Sheet
4
+
5
+ vd.option('rename_cascade', False, 'cascade column renames into expressions') #2088
6
+
7
+
8
+ class Renamer(ast.NodeTransformer):
9
+ def __init__(self, find, replace):
10
+ self.find = find
11
+ self.replace = replace
12
+
13
+ def visit_Name(self, node):
14
+ if node.id == self.find:
15
+ node.id = self.replace
16
+
17
+ return node
18
+
19
+
20
+ @Column.before
21
+ def setName(col, newname):
22
+ if col.sheet and col.sheet.options.rename_cascade:
23
+ for c in col.sheet.columns:
24
+ if isinstance(c, ExprColumn):
25
+ parsed_expr = ast.parse(c.expr)
26
+ canon_expr = ast.unparse(parsed_expr)
27
+ new_expr = ast.unparse(Renamer(col.name, newname).visit(parsed_expr))
28
+ if new_expr != canon_expr:
29
+ vd.addUndo(setattr, c, 'expr', c.expr)
30
+ c.expr = new_expr
@@ -0,0 +1,60 @@
1
+ '''
2
+ Enables two functions:
3
+ 1. options.disp_scroll_context: the minimum number of lines to keep visible above and below the cursor when scrolling
4
+ 2. toggle-scrollfix: Lock the cursor to a particular row number in the view
5
+
6
+ Allows the user to fix the position on the page where they would like the
7
+ cursor to "stick". Helps to provide context about surrounding rows when
8
+ near the top and bottom of the page.
9
+
10
+ Usage: scroll a few lines down, `toggle-scrollfix' and scroll again!
11
+
12
+ NOTE:
13
+ - disables scroll-middle command (zz)
14
+ '''
15
+
16
+ __author__ = 'Geekscrapy'
17
+ __version__ = '1.2'
18
+
19
+ from visidata import vd, Sheet
20
+
21
+ vd.option('disp_scroll_context', 0, 'minimum number of lines to keep visible above/below cursor when scrolling')
22
+ vd.optalias('disp_scrolloff', 'disp_scroll_context')
23
+
24
+ @Sheet.after
25
+ def checkCursor(sheet):
26
+ nctx = sheet.options.disp_scroll_context
27
+ if nctx:
28
+ if nctx >= int(sheet.nScreenRows/2):
29
+ if sheet.cursorRowIndex+1 > int(sheet.nScreenRows/2):
30
+ sheet.topRowIndex = sheet.cursorRowIndex - int(sheet.nScreenRows/2)
31
+ else:
32
+ if sheet.cursorRowIndex-sheet.topRowIndex < nctx:
33
+ sheet.topRowIndex = max(0, sheet.cursorRowIndex-nctx)
34
+
35
+ if sheet.bottomRowIndex-sheet.cursorRowIndex < nctx:
36
+ sheet.bottomRowIndex = sheet.cursorRowIndex+nctx
37
+
38
+ nfix = getattr(sheet, 'disp_scrollfix', 0)
39
+ if nfix > 0:
40
+ cursorViewIndex = sheet.cursorRowIndex - sheet.topRowIndex
41
+ if cursorViewIndex > nfix:
42
+ sheet.topRowIndex += abs(nfix - cursorViewIndex)
43
+ if cursorViewIndex < nfix and sheet.topRowIndex > 0:
44
+ sheet.topRowIndex -= abs(nfix - cursorViewIndex)
45
+
46
+
47
+ @Sheet.api
48
+ def toggle_scrollfix(sheet):
49
+ if getattr(sheet, 'disp_scrollfix', -1) >= 0:
50
+ sheet.disp_scrollfix = -1
51
+ vd.status("cursor unlocked")
52
+ else:
53
+ sheet.disp_scrollfix = sheet.cursorRowIndex - sheet.topRowIndex
54
+ vd.status(f"cursor locked to screen row {sheet.disp_scrollfix}")
55
+
56
+
57
+ Sheet.addCommand('', 'toggle-scrollfix', 'toggle_scrollfix()', helpstr='toggle cursor lock to current screen row')
58
+
59
+
60
+ vd.addMenuItems('''View > Toggle display > lock cursor to screen row > toggle-scrollfix''')
@@ -0,0 +1,11 @@
1
+ from visidata import Sheet, asyncthread, Progress
2
+
3
+
4
+ @Sheet.api
5
+ @asyncthread
6
+ def select_equal_selected(sheet, col):
7
+ selectedVals = set(col.getDisplayValue(row) for row in Progress(sheet.selectedRows))
8
+ sheet.select(sheet.gatherBy(lambda r,c=col,vals=selectedVals: c.getDisplayValue(r) in vals), progress=False)
9
+
10
+
11
+ Sheet.addCommand('', 'select-equal-selected', 'select_equal_selected(cursorCol)', 'select rows with values in current column in already selected rows')
@@ -0,0 +1,65 @@
1
+ # to anonymize a column in vd: do "setcol-fake" with e.g. 'name' 'isbn10' or any of the functions on Faker()
2
+
3
+ import json
4
+
5
+ from visidata import vd, Column, Sheet, asyncthread, Progress, VisiData
6
+
7
+ vd.option('faker_locale', 'en_US', 'default locale to use for Faker', replay=True)
8
+ vd.option('faker_extra_providers', None, 'list of additional Provider classes to load via add_provider()', replay=True)
9
+ vd.option('faker_salt', '', 'Use a non-empty string to enable deterministic fakes')
10
+
11
+ def addFakerProviders(fake, providers):
12
+ '''
13
+ Add custom providers to Faker. Provider classes typically derive from
14
+ faker.providers.BaseProvider, so check for that here. This helps to
15
+ highlight likely misconfigurations instead of hiding them.
16
+
17
+ See also: https://faker.readthedocs.io/en/master/communityproviders.html
18
+
19
+ fake: Faker object
20
+ providers: List of provider classes to add
21
+ '''
22
+ faker = vd.importExternal('faker', 'Faker')
23
+ if isinstance(providers, str):
24
+ providers = [ getattr(faker.providers, p) for p in providers.split() ]
25
+
26
+ if not isinstance(providers, list):
27
+ vd.fail('options.faker_extra_providers must be a list')
28
+
29
+ for provider in providers:
30
+ if not issubclass(provider, faker.providers.BaseProvider):
31
+ vd.warning('"{}" not a Faker Provider'.format(provider.__name__))
32
+ continue
33
+ fake.add_provider(provider)
34
+
35
+ @Column.api
36
+ @asyncthread
37
+ def setValuesFromFaker(col, faketype, rows):
38
+ faker = vd.importExternal('faker', 'Faker')
39
+ fake = faker.Faker(col.sheet.options.faker_locale)
40
+ if col.sheet.options.faker_extra_providers:
41
+ addFakerProviders(fake, col.sheet.options.faker_extra_providers)
42
+ fakefunc = getattr(fake, faketype, None) or vd.fail(f'no such faker "{faketype}"')
43
+
44
+ fakeMap = {}
45
+ fakeMap[None] = None
46
+ fakeMap[col.sheet.options.null_value] = col.sheet.options.null_value
47
+
48
+ vd.addUndoSetValues([col], rows)
49
+ salt = col.sheet.options.faker_salt
50
+
51
+ for r in Progress(rows):
52
+ v = col.getValue(r)
53
+ if v in fakeMap:
54
+ newv = fakeMap[v]
55
+ else:
56
+ if salt:
57
+ # Reset the Faker seed for each value. For a given salt string,
58
+ # the same cell value will always generate the same fake value.
59
+ fake.seed_instance(json.dumps(v) + salt)
60
+ newv = fakefunc()
61
+ fakeMap[v] = newv
62
+ col.setValue(r, newv)
63
+
64
+
65
+ Sheet.addCommand(None, 'setcol-fake', 'cursorCol.setValuesFromFaker(input("faketype: ", type="faketype"), selectedRows)', 'replace values in current column for selected rows with fake values')
@@ -1,6 +1,7 @@
1
1
  '''slide rows/columns around'''
2
2
 
3
- from visidata import Sheet, moveListItem, globalCommand, vd
3
+ import visidata
4
+ from visidata import Sheet, moveListItem, vd
4
5
 
5
6
  @Sheet.api
6
7
  def slide_col(sheet, colidx, newcolidx):
@@ -19,26 +20,6 @@ def slide_row(sheet, rowidx, newcolidx):
19
20
  return moveListItem(sheet.rows, rowidx, newcolidx)
20
21
 
21
22
 
22
- @Sheet.api
23
- def onClick(sheet, vcolidx, rowidx):
24
- pass
25
-
26
- @Sheet.api
27
- def onRelease(sheet, vcolidx, rowidx, destx, desty):
28
- newvcolidx = sheet.visibleColAtX(destx)
29
- newrowidx = sheet.visibleRowAtY(desty)
30
-
31
- if newvcolidx is not None and newvcolidx != vcolidx:
32
- sheet.cursorVisibleColIndex = sheet.slide_col(vcolidx, newvcolidx)
33
-
34
- # else: only move row if within same column (if column not moved above)
35
- elif newrowidx is not None and newrowidx != rowidx:
36
- sheet.cursorRowIndex = sheet.slide_row(rowidx, newrowidx)
37
-
38
- else:
39
- sheet.onClick(vcolidx, rowidx)
40
-
41
-
42
23
  def moveKeyCol(sheet, fromKeyColIdx, toKeyColIdx):
43
24
  'Move key column to another key column position in sheet.'
44
25
  if not (1 <= toKeyColIdx <= len(sheet.keyCols)):
@@ -101,3 +82,76 @@ Sheet.bindkey('gKEY_SLEFT', 'slide-leftmost')
101
82
  Sheet.bindkey('gkDN', 'slide-bottom')
102
83
  Sheet.bindkey('gkUP', 'slide-top')
103
84
  Sheet.bindkey('gKEY_SRIGHT', 'slide-rightmost')
85
+
86
+ vd.addMenuItems('''
87
+ Edit > Slide > Row > up > slide-up
88
+ Edit > Slide > Row > up N > slide-up-n
89
+ Edit > Slide > Row > down > slide-down
90
+ Edit > Slide > Row > down N > slide-down-n
91
+ Edit > Slide > Row > to top > slide-top
92
+ Edit > Slide > Row > to bottom > slide-bottom
93
+ Edit > Slide > Column > left > slide-left
94
+ Edit > Slide > Column > left N > slide-left-n
95
+ Edit > Slide > Column > leftmost > slide-leftmost
96
+ Edit > Slide > Column > right > slide-right
97
+ Edit > Slide > Column > right N > slide-right-n
98
+ Edit > Slide > Column > rightmost > slide-rightmost
99
+ ''')
100
+
101
+ ## tests
102
+
103
+
104
+ def make_tester(setup_vdx):
105
+ def t(vdx, golden):
106
+ global vd
107
+ vd = visidata.vd.resetVisiData()
108
+ vd.runvdx(setup_vdx)
109
+
110
+ vd.runvdx(vdx)
111
+ colnames = [c.name for c in vd.sheet.visibleCols]
112
+ assert colnames == golden.split(), ' '.join(colnames)
113
+
114
+ return t
115
+
116
+ def test_slide_keycol_1(vd):
117
+ t = make_tester('''
118
+ open-file sample_data/sample.tsv
119
+ +::OrderDate key-col
120
+ +::Region key-col
121
+ +::Rep key-col
122
+ ''')
123
+
124
+ t('', 'OrderDate Region Rep Item Units Unit_Cost Total')
125
+ t('+::Rep slide-leftmost', 'Rep OrderDate Region Item Units Unit_Cost Total')
126
+ t('+::OrderDate slide-rightmost', 'Region Rep OrderDate Item Units Unit_Cost Total')
127
+ t('+::Rep slide-left', 'OrderDate Rep Region Item Units Unit_Cost Total')
128
+ t('+::OrderDate slide-right', 'Region OrderDate Rep Item Units Unit_Cost Total')
129
+
130
+ t('''
131
+ +::Item key-col
132
+ +::Item slide-left
133
+ slide-left
134
+ slide-right
135
+ slide-right
136
+ slide-left
137
+ slide-left
138
+ ''', 'OrderDate Item Region Rep Units Unit_Cost Total')
139
+
140
+
141
+ def test_slide_leftmost(vd):
142
+ t = make_tester('''open-file sample_data/benchmark.csv''')
143
+
144
+ t('+::Paid slide-leftmost', 'Paid Date Customer SKU Item Quantity Unit')
145
+
146
+ t = make_tester('''
147
+ open-file sample_data/benchmark.csv
148
+ +::Date key-col
149
+ ''')
150
+
151
+ t('', 'Date Customer SKU Item Quantity Unit Paid')
152
+ t('''+::Item slide-leftmost''', 'Date Item Customer SKU Quantity Unit Paid')
153
+ t('''+::SKU key-col
154
+ +::Quantity slide-leftmost''', 'Date SKU Quantity Customer Item Unit Paid')
155
+ t('''+::Date slide-leftmost''', 'Date Customer SKU Item Quantity Unit Paid')
156
+ t('''+::Item slide-leftmost
157
+ +::SKU slide-leftmost''', 'Date SKU Item Customer Quantity Unit Paid')
@@ -0,0 +1,48 @@
1
+ """
2
+ Generate sparkline column for numeric columns
3
+ """
4
+
5
+ from visidata import vd, Column, Sheet
6
+
7
+ __author__ = 'Lucas Messenger @layertwo'
8
+
9
+ vd.theme_option('disp_sparkline', '▁▂▃▄▅▆▇', 'characters to display sparkline')
10
+
11
+
12
+ def sparkline(*values):
13
+ """
14
+ From *values generate string sparkline
15
+ """
16
+ lines = vd.options.disp_sparkline
17
+ values = [v for v in values if isinstance(v, (int, float))]
18
+ mx = max(values)
19
+ mn = min(values)
20
+ w = (mx - mn) / len(lines)
21
+ bounds = [(mn + w * i) for i in range(len(lines))]
22
+
23
+ output = ''
24
+ for val in values:
25
+ for b in bounds:
26
+ if mn == 0 and val == 0:
27
+ output += ' '
28
+ break
29
+ if val < b:
30
+ output += lines[bounds.index(b) - 1]
31
+ break
32
+ else:
33
+ output += max(lines)
34
+ return output
35
+
36
+
37
+ @Sheet.api
38
+ def addcol_sparkline(sheet, sourceCols):
39
+ """
40
+ Add sparkline column
41
+ """
42
+ c = Column('sparkline',
43
+ sourceCols=sourceCols,
44
+ getter=lambda c,r: sparkline(*tuple(c.getTypedValue(r) for c in c.sheet.sourceCols)))
45
+ sheet.addColumn(c)
46
+
47
+
48
+ Sheet.addCommand(None, 'addcol-sparkline', 'addcol_sparkline(numericCols(nonKeyVisibleCols))', 'add sparkline of all numeric columns')
@@ -0,0 +1,20 @@
1
+ import inspect
2
+
3
+ import visidata
4
+ from visidata import VisiData
5
+
6
+ @VisiData.api
7
+ def getStatusSource(vd):
8
+ stack = inspect.stack()
9
+ for i, sf in enumerate(stack):
10
+ if sf.function in 'status aside'.split():
11
+ if stack[i+1].function in 'error fail warning debug'.split():
12
+ sf = stack[i+2]
13
+ else:
14
+ sf = stack[i+1]
15
+ break
16
+
17
+ fn = sf.filename
18
+ if fn.startswith(visidata.__path__[0]):
19
+ fn = visidata.__package__ + fn[len(visidata.__path__[0]):]
20
+ return f'{fn}:{sf.lineno}:{sf.function}'
@@ -17,7 +17,8 @@ def syseditCells_async(sheet, cols, rows, filetype=None):
17
17
 
18
18
  import tempfile
19
19
  with tempfile.NamedTemporaryFile() as temp:
20
- p = Path(temp.name+'.'+filetype)
20
+ temp.close() #2118
21
+ p = Path(temp.name)
21
22
 
22
23
  vd.status(f'copying {vs.nRows} {vs.rowtype} to {p} as {filetype}')
23
24
  vd.sync(vd.saveSheets(p, vs))
@@ -0,0 +1,46 @@
1
+ ''' Plugin for viewing files with appropriate mailcap-specified application.
2
+ Add mailcap-view and mailcap-view-selected commands to DirSheet.
3
+
4
+ mimetype can be given explicitly with `mimetype` option; will be guessed by filename otherwise.
5
+
6
+ Usage:
7
+ - add `import experimental.mailcap_view` to .visidatarc
8
+ - on the DirSheet, `Ctrl+V` or `gCtrl+V` to view file(s) using mailcap entry for the guessed (or given via options) mimetype
9
+ '''
10
+
11
+ import os
12
+ from visidata import vd, DirSheet, SuspendCurses
13
+
14
+ vd.option('mailcap_mimetype', '', 'force mimetype for sysopen-mailcap')
15
+ vd.optalias('mimetype', 'mailcap_mimetype')
16
+
17
+
18
+ @DirSheet.api
19
+ def run_mailcap(sheet, p, key='view'):
20
+ import mailcap
21
+ import mimetypes
22
+
23
+ mimetype = sheet.options.mailcap_mimetype
24
+ if not mimetype:
25
+ mimetype, encoding = mimetypes.guess_type(str(p))
26
+
27
+ if not mimetype:
28
+ vd.fail('no mimetype given and no guess')
29
+
30
+ caps = mailcap.getcaps()
31
+
32
+ plist = [f'{k}={v}' for k, v in sheet.options.getall('mailcap_').items() if k != 'mailcap_mimetype']
33
+ cmdline, mcap_entry = mailcap.findmatch(caps, mimetype, key=key, filename=str(p), plist=plist)
34
+
35
+ with SuspendCurses():
36
+ os.system(cmdline)
37
+
38
+
39
+ DirSheet.addCommand('', 'sysopen-mailcap', 'run_mailcap(cursorRow)', 'open using mailcap entry for current row, guessing mimetype')
40
+ DirSheet.addCommand('', 'sysopen-mailcap-selected', 'for r in selectedRows: run_mailcap(r)', 'open selected files in succession, using mailcap')
41
+
42
+
43
+ vd.addMenuItems('''
44
+ File > Open > using mailcap > file at cursor > sysopen-mailcap
45
+ File > Open > using mailcap > selected files > sysopen-mailcap-selected
46
+ ''')
@@ -0,0 +1,13 @@
1
+ import os
2
+
3
+ from visidata import VisiData
4
+
5
+
6
+ @VisiData.api
7
+ def ansi(*args):
8
+ os.write(1, b'\x1b'+b''.join([str(x).encode('utf-8') for x in args]))
9
+
10
+
11
+ @VisiData.api
12
+ def set_titlebar(vd, title:str):
13
+ ansi(']2;', title, '\x07')
@@ -1,10 +1,9 @@
1
- from visidata import VisiData, Sheet, asyncthread, Progress, Column
1
+ from visidata import vd, VisiData, Sheet, asyncthread, Progress, Column
2
2
 
3
3
  # rowdef: Column
4
4
  @VisiData.api
5
5
  class TransposeSheet(Sheet):
6
- @asyncthread
7
- def reload(self):
6
+ def beforeLoad(self):
8
7
  # key rows become column names
9
8
  col = Column('_'.join(c.name for c in self.source.keyCols),
10
9
  getter=lambda c,origcol: origcol.name)
@@ -13,7 +12,7 @@ class TransposeSheet(Sheet):
13
12
  self.columns = [col]
14
13
  self.setKeys(self.columns)
15
14
 
16
-
15
+ def loader(self):
17
16
  # rows become columns
18
17
  for row in Progress(self.source.rows, 'transposing'):
19
18
  self.addColumn(Column('_'.join(map(str, self.source.rowkey(row))),
@@ -23,3 +22,5 @@ class TransposeSheet(Sheet):
23
22
  self.rows = list(self.source.nonKeyVisibleCols)
24
23
 
25
24
  Sheet.addCommand('T', 'transpose', 'vd.push(TransposeSheet(name+"_T", source=sheet))', 'open new sheet with rows and columns transposed')
25
+
26
+ vd.addMenuItems('Data > Transpose > transpose')
@@ -0,0 +1,73 @@
1
+ """
2
+ Column types and utility commands related to IP addresses.
3
+ """
4
+ from ipaddress import ip_address, ip_network, _BaseNetwork
5
+
6
+ from visidata import vd
7
+ from visidata.sheets import Column, TableSheet
8
+
9
+
10
+ vd.addType(ip_address, icon=":", formatter=lambda fmt, ip: str(ip))
11
+ vd.addType(ip_network, icon="/", formatter=lambda fmt, ip: str(ip))
12
+
13
+
14
+ def isSupernet(cell, network, isNull):
15
+ """Is `cell` a supernet of `network`?
16
+
17
+ Treat nulls as false, and perform conversions to IP network objects only
18
+ if necessary.
19
+ """
20
+ if isNull(cell):
21
+ return False
22
+ if not isinstance(cell, _BaseNetwork):
23
+ try:
24
+ cell = ip_network(str(cell).strip())
25
+ except ValueError:
26
+ return False
27
+ return cell.supernet_of(network)
28
+
29
+
30
+ @Column.api
31
+ def selectSupernets(col, ip):
32
+ """Select rows based on network containment
33
+
34
+ Given an IP address (e.g. 10.0.0.0) or network (e.g. 10.0.0.0/8) as input,
35
+ select rows whose network address space completely contains the input network.
36
+ """
37
+ if not ip:
38
+ return
39
+
40
+ sheet = col.sheet
41
+ network = ip_network(ip.strip())
42
+ isNull = sheet.isNullFunc()
43
+
44
+ vd.status(f'selecting rows where {col.name} is a supernet of "{str(network)}"')
45
+ sheet.select(
46
+ [
47
+ row
48
+ for row in sheet.rows
49
+ if isSupernet(col.getTypedValue(row), network, isNull)
50
+ ]
51
+ )
52
+
53
+
54
+ TableSheet.addCommand(
55
+ None,
56
+ "type-ipaddr",
57
+ "cursorCol.type=ip_address",
58
+ "set type of current column to IP address",
59
+ )
60
+ TableSheet.addCommand(
61
+ None,
62
+ "type-ipnet",
63
+ "cursorCol.type=ip_network",
64
+ "set type of current column to IP network",
65
+ )
66
+ TableSheet.addCommand(
67
+ None,
68
+ "select-supernets",
69
+ 'cursorCol.selectSupernets(input("ip or cidr block: "))',
70
+ "select rows where the CIDR block value includes the input address space",
71
+ )
72
+
73
+ vd.addGlobals(ip_address=ip_address, ip_network=ip_network)
@@ -0,0 +1,11 @@
1
+ from visidata import Sheet, Column, DisplayWrapper
2
+
3
+
4
+ @Column.api
5
+ def displayer_url(self, dw:DisplayWrapper, width=None):
6
+ 'Display cell text as clickable url'
7
+ yield ('onclick '+dw.text, dw.text)
8
+
9
+
10
+ Sheet.addCommand("", "type-url", "sheet.cursorCol.displayer = 'url'", "set column to open URLs in $BROWSER on mouse click")
11
+ Sheet.addCommand("", "open-url", "vd.launchBrowser(sheet.cursorValue)", "open current cursor value in $BROWSER")
@@ -7,7 +7,7 @@ Credit to Jeremy Singer-Vine for the idea and original implementation.
7
7
  '''
8
8
 
9
9
  from collections.abc import Iterable, Mapping
10
- from visidata import vd, Progress, Sheet, Column, ColumnItem, SettableColumn, SubColumnFunc, asyncthread, clean_to_id
10
+ from visidata import vd, Progress, Sheet, Column, ColumnItem, SettableColumn, SubColumnFunc, asyncthread
11
11
  from visidata import stacktrace, TypedExceptionWrapper
12
12
 
13
13
 
@@ -16,8 +16,7 @@ vd.option('unfurl_empty', False, 'if unfurl includes rows for empty containers',
16
16
 
17
17
  class UnfurledSheet(Sheet):
18
18
  # rowdef: [row, key, sub_value]
19
- @asyncthread
20
- def reload(self):
19
+ def resetCols(self):
21
20
  # Copy over base sheet, using SubColumnFunc
22
21
  self.columns = []
23
22
  for col in self.source.columns:
@@ -29,7 +28,7 @@ class UnfurledSheet(Sheet):
29
28
  else:
30
29
  self.addColumn(SubColumnFunc(col.name, col, 0, keycol=col.keycol))
31
30
 
32
- self.rows = []
31
+ def iterload(self):
33
32
  unfurl_empty = self.options.unfurl_empty
34
33
  for row in Progress(self.source.rows):
35
34
  try:
@@ -39,7 +38,7 @@ class UnfurledSheet(Sheet):
39
38
  if unfurl_empty:
40
39
  # TypedExceptionWrapper allows the use of z^E to see the stacktrace
41
40
  # the exception on its own lacks clarity
42
- self.addRow([row, TypedExceptionWrapper(None, exception=e), TypedExceptionWrapper(None, exception=e)])
41
+ yield [row, TypedExceptionWrapper(None, exception=e), TypedExceptionWrapper(None, exception=e)]
43
42
  else:
44
43
  vd.exceptionCaught(e)
45
44
  continue
@@ -54,17 +53,18 @@ class UnfurledSheet(Sheet):
54
53
 
55
54
  nadded = 0
56
55
  for key, sub_value in gen:
57
- new_row = [ row, key, sub_value ]
58
- self.addRow(new_row)
56
+ yield [ row, key, sub_value ]
59
57
  nadded += 1
60
58
 
61
59
  if unfurl_empty and not nadded:
62
- self.addRow([row, None, None])
60
+ yield [row, None, None]
63
61
 
64
62
 
65
63
  @Sheet.api
66
64
  def unfurl_col(sheet, col):
67
- return UnfurledSheet(sheet.name, clean_to_id(col.name), 'unfurled', source=sheet, source_col=col)
65
+ return UnfurledSheet(sheet.name, vd.cleanName(col.name), 'unfurled', source=sheet, source_col=col)
68
66
 
69
67
 
70
68
  Sheet.addCommand("zM", "unfurl-col", "vd.push(unfurl_col(cursorCol))", "row-wise expand current column of lists (e.g. [2]) or dicts (e.g. {3}) within that column")
69
+
70
+ vd.addMenuItems('Data > Unfurl column > unfurl-col')
@@ -47,10 +47,10 @@ def addcol_window(sheet, curcol):
47
47
 
48
48
  @Sheet.api
49
49
  def select_around(sheet, n):
50
- sheet.select(list(itertools.chain(*(winrows for row, winrows in sheet.window(n, n) if sheet.isSelected(row)))))
50
+ sheet.select(list(itertools.chain(*(winrows for row, winrows in sheet.window(int(n), int(n)) if sheet.isSelected(row)))))
51
51
 
52
52
 
53
53
  Sheet.addCommand('w', 'addcol-window', 'addcol_window(cursorCol)', 'add column where each row contains a list of that row, nBefore rows, and nAfter rows')
54
- Sheet.addCommand('', 'select-around-n', 'select_around(input("select rows around selected: ", value=1))')
54
+ Sheet.addCommand('', 'select-around-n', 'select_around(input("select rows around selected: ", value=1))', 'select additional N rows before/after each selected row')
55
55
 
56
56
  vd.addMenuItem('Row', 'Select', 'N rows around each selected row', 'select-around-n')