visidata 2.11.dev0__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 (253) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +263 -44
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +22 -4
  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. visidata/apps/vgit/__main__.py +3 -0
  18. visidata/apps/vgit/abort.py +23 -0
  19. visidata/apps/vgit/blame.py +76 -0
  20. visidata/apps/vgit/branch.py +153 -0
  21. visidata/apps/vgit/config.py +95 -0
  22. visidata/apps/vgit/diff.py +169 -0
  23. visidata/apps/vgit/gitsheet.py +161 -0
  24. visidata/apps/vgit/grep.py +37 -0
  25. visidata/apps/vgit/log.py +81 -0
  26. visidata/apps/vgit/main.py +55 -0
  27. visidata/apps/vgit/remote.py +57 -0
  28. visidata/apps/vgit/repos.py +71 -0
  29. visidata/apps/vgit/setup.py +37 -0
  30. visidata/apps/vgit/stash.py +69 -0
  31. visidata/apps/vgit/status.py +204 -0
  32. visidata/apps/vgit/statusbar.py +34 -0
  33. visidata/basesheet.py +59 -50
  34. visidata/canvas.py +251 -99
  35. visidata/choose.py +15 -11
  36. visidata/clean_names.py +29 -0
  37. visidata/clipboard.py +84 -18
  38. visidata/cliptext.py +220 -46
  39. visidata/cmdlog.py +89 -114
  40. visidata/color.py +142 -56
  41. visidata/column.py +134 -131
  42. visidata/ddw/input.ddw +74 -79
  43. visidata/ddw/regex.ddw +57 -0
  44. visidata/ddwplay.py +33 -14
  45. visidata/deprecated.py +77 -3
  46. visidata/desktop/visidata.desktop +7 -0
  47. visidata/editor.py +12 -6
  48. visidata/errors.py +5 -1
  49. visidata/experimental/__init__.py +0 -0
  50. visidata/experimental/diff_sheet.py +29 -0
  51. visidata/experimental/digit_autoedit.py +6 -0
  52. visidata/experimental/gdrive.py +89 -0
  53. visidata/experimental/google.py +37 -0
  54. visidata/experimental/gsheets.py +79 -0
  55. visidata/experimental/live_search.py +37 -0
  56. visidata/experimental/liveupdate.py +45 -0
  57. visidata/experimental/mark.py +133 -0
  58. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  59. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  60. visidata/experimental/rownum.py +73 -0
  61. visidata/experimental/slide_cells.py +26 -0
  62. visidata/expr.py +8 -4
  63. visidata/extensible.py +32 -6
  64. visidata/features/__init__.py +0 -0
  65. visidata/features/addcol_audiometadata.py +42 -0
  66. visidata/features/addcol_histogram.py +34 -0
  67. visidata/features/canvas_save_svg.py +69 -0
  68. visidata/features/change_precision.py +46 -0
  69. visidata/features/cmdpalette.py +163 -0
  70. visidata/features/colorbrewer.py +363 -0
  71. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  72. visidata/features/command_server.py +105 -0
  73. visidata/features/currency_to_usd.py +70 -0
  74. visidata/{customdate.py → features/customdate.py} +2 -0
  75. visidata/features/dedupe.py +132 -0
  76. visidata/{describe.py → features/describe.py} +17 -15
  77. visidata/features/errors_guide.py +26 -0
  78. visidata/features/expand_cols.py +202 -0
  79. visidata/{fill.py → features/fill.py} +4 -2
  80. visidata/{freeze.py → features/freeze.py} +11 -6
  81. visidata/features/graph_seaborn.py +79 -0
  82. visidata/features/helloworld.py +10 -0
  83. visidata/features/hint_types.py +17 -0
  84. visidata/{incr.py → features/incr.py} +5 -0
  85. visidata/{join.py → features/join.py} +107 -53
  86. visidata/features/known_cols.py +21 -0
  87. visidata/features/layout.py +62 -0
  88. visidata/{melt.py → features/melt.py} +33 -21
  89. visidata/features/normcol.py +118 -0
  90. visidata/features/open_config.py +7 -0
  91. visidata/features/open_syspaste.py +18 -0
  92. visidata/features/ping.py +157 -0
  93. visidata/features/procmgr.py +208 -0
  94. visidata/features/random_sample.py +6 -0
  95. visidata/{regex.py → features/regex.py} +47 -31
  96. visidata/features/reload_every.py +55 -0
  97. visidata/features/rename_col_cascade.py +30 -0
  98. visidata/features/scroll_context.py +60 -0
  99. visidata/features/select_equal_selected.py +11 -0
  100. visidata/features/setcol_fake.py +65 -0
  101. visidata/{slide.py → features/slide.py} +75 -21
  102. visidata/features/sparkline.py +48 -0
  103. visidata/features/status_source.py +20 -0
  104. visidata/{sysedit.py → features/sysedit.py} +2 -1
  105. visidata/features/sysopen_mailcap.py +46 -0
  106. visidata/features/term_extras.py +13 -0
  107. visidata/{transpose.py → features/transpose.py} +5 -4
  108. visidata/features/type_ipaddr.py +73 -0
  109. visidata/features/type_url.py +11 -0
  110. visidata/{unfurl.py → features/unfurl.py} +9 -9
  111. visidata/{window.py → features/window.py} +2 -2
  112. visidata/form.py +50 -21
  113. visidata/freqtbl.py +81 -33
  114. visidata/fuzzymatch.py +414 -0
  115. visidata/graph.py +105 -33
  116. visidata/guide.py +180 -0
  117. visidata/help.py +75 -44
  118. visidata/hint.py +39 -0
  119. visidata/indexsheet.py +109 -0
  120. visidata/input_history.py +55 -0
  121. visidata/interface.py +58 -0
  122. visidata/keys.py +17 -16
  123. visidata/loaders/__init__.py +9 -0
  124. visidata/loaders/_pandas.py +61 -21
  125. visidata/loaders/api_airtable.py +70 -0
  126. visidata/loaders/api_bitio.py +102 -0
  127. visidata/loaders/api_matrix.py +148 -0
  128. visidata/loaders/api_reddit.py +306 -0
  129. visidata/loaders/api_zulip.py +249 -0
  130. visidata/loaders/archive.py +41 -7
  131. visidata/loaders/arrow.py +7 -7
  132. visidata/loaders/conll.py +49 -0
  133. visidata/loaders/csv.py +25 -7
  134. visidata/loaders/eml.py +3 -4
  135. visidata/loaders/f5log.py +1204 -0
  136. visidata/loaders/fec.py +325 -0
  137. visidata/loaders/fixed_width.py +3 -5
  138. visidata/loaders/frictionless.py +3 -3
  139. visidata/loaders/geojson.py +8 -5
  140. visidata/loaders/google.py +48 -0
  141. visidata/loaders/graphviz.py +4 -4
  142. visidata/loaders/hdf5.py +4 -4
  143. visidata/loaders/html.py +48 -10
  144. visidata/loaders/http.py +84 -30
  145. visidata/loaders/imap.py +20 -10
  146. visidata/loaders/jrnl.py +52 -0
  147. visidata/loaders/json.py +83 -29
  148. visidata/loaders/jsonla.py +74 -0
  149. visidata/loaders/lsv.py +15 -11
  150. visidata/loaders/mailbox.py +40 -0
  151. visidata/loaders/markdown.py +1 -3
  152. visidata/loaders/mbtiles.py +4 -5
  153. visidata/loaders/mysql.py +11 -13
  154. visidata/loaders/npy.py +7 -7
  155. visidata/loaders/odf.py +4 -1
  156. visidata/loaders/orgmode.py +428 -0
  157. visidata/loaders/pandas_freqtbl.py +14 -20
  158. visidata/loaders/parquet.py +62 -6
  159. visidata/loaders/pcap.py +3 -3
  160. visidata/loaders/pdf.py +4 -3
  161. visidata/loaders/png.py +19 -13
  162. visidata/loaders/postgres.py +9 -8
  163. visidata/loaders/rec.py +7 -3
  164. visidata/loaders/s3.py +342 -0
  165. visidata/loaders/sas.py +5 -5
  166. visidata/loaders/scrape.py +186 -0
  167. visidata/loaders/shp.py +6 -5
  168. visidata/loaders/spss.py +5 -6
  169. visidata/loaders/sqlite.py +68 -28
  170. visidata/loaders/texttables.py +1 -1
  171. visidata/loaders/toml.py +60 -0
  172. visidata/loaders/tsv.py +61 -19
  173. visidata/loaders/ttf.py +19 -7
  174. visidata/loaders/unzip_http.py +6 -5
  175. visidata/loaders/usv.py +1 -1
  176. visidata/loaders/vcf.py +16 -16
  177. visidata/loaders/vds.py +10 -7
  178. visidata/loaders/vdx.py +30 -5
  179. visidata/loaders/xlsb.py +8 -1
  180. visidata/loaders/xlsx.py +145 -25
  181. visidata/loaders/xml.py +6 -3
  182. visidata/loaders/xword.py +4 -4
  183. visidata/loaders/yaml.py +15 -5
  184. visidata/macos.py +1 -1
  185. visidata/macros.py +130 -41
  186. visidata/main.py +119 -94
  187. visidata/mainloop.py +101 -154
  188. visidata/man/parse_options.py +2 -2
  189. visidata/man/vd.1 +302 -147
  190. visidata/man/vd.txt +291 -151
  191. visidata/memory.py +3 -3
  192. visidata/menu.py +104 -423
  193. visidata/metasheets.py +59 -141
  194. visidata/modify.py +79 -23
  195. visidata/motd.py +3 -3
  196. visidata/mouse.py +137 -0
  197. visidata/movement.py +43 -35
  198. visidata/optionssheet.py +99 -0
  199. visidata/path.py +131 -43
  200. visidata/pivot.py +74 -47
  201. visidata/plugins.py +65 -192
  202. visidata/pyobj.py +50 -201
  203. visidata/rename_col.py +20 -0
  204. visidata/save.py +42 -20
  205. visidata/search.py +54 -10
  206. visidata/selection.py +84 -5
  207. visidata/settings.py +162 -24
  208. visidata/sheets.py +229 -257
  209. visidata/shell.py +51 -21
  210. visidata/sidebar.py +162 -0
  211. visidata/sort.py +11 -4
  212. visidata/statusbar.py +113 -104
  213. visidata/stored_list.py +43 -0
  214. visidata/stored_prop.py +38 -0
  215. visidata/tests/conftest.py +3 -3
  216. visidata/tests/test_cliptext.py +39 -0
  217. visidata/tests/test_commands.py +62 -7
  218. visidata/tests/test_edittext.py +2 -2
  219. visidata/tests/test_features.py +17 -0
  220. visidata/tests/test_menu.py +14 -0
  221. visidata/tests/test_path.py +13 -4
  222. visidata/text_source.py +53 -0
  223. visidata/textsheet.py +10 -3
  224. visidata/theme.py +44 -0
  225. visidata/themes/__init__.py +0 -0
  226. visidata/themes/ascii8.py +84 -0
  227. visidata/themes/asciimono.py +84 -0
  228. visidata/themes/light.py +17 -0
  229. visidata/threads.py +87 -39
  230. visidata/tuiwin.py +22 -0
  231. visidata/type_currency.py +22 -3
  232. visidata/type_date.py +31 -9
  233. visidata/type_floatsi.py +5 -1
  234. visidata/undo.py +18 -6
  235. visidata/utils.py +106 -23
  236. visidata/vdobj.py +28 -17
  237. visidata/windows.py +10 -0
  238. visidata/wrappers.py +9 -3
  239. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  240. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
  241. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
  242. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  243. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
  244. visidata-3.0.dist-info/RECORD +257 -0
  245. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  246. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
  247. visidata/layout.py +0 -44
  248. visidata/misc.py +0 -5
  249. visidata-2.11.dev0.dist-info/RECORD +0 -142
  250. /visidata/{repeat.py → features/repeat.py} +0 -0
  251. {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
  252. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  253. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
visidata/indexsheet.py ADDED
@@ -0,0 +1,109 @@
1
+ from visidata import vd, VisiData, BaseSheet, Sheet, Column, AttrColumn, ItemColumn, setitem, asyncthread
2
+
3
+
4
+ class IndexSheet(Sheet):
5
+ 'Base class for tabular sheets with rows that are Sheets.'
6
+ guide = '''
7
+ # Index Sheet
8
+ This is a list of sheets from `{sheet.source}`.
9
+
10
+ - `Enter` to open {sheet.cursorRow}
11
+ - `g Enter` to open all selected sheets
12
+ '''
13
+ rowtype = 'sheets' # rowdef: Sheet
14
+
15
+ columns = [
16
+ Column('name', getter=lambda c,r: r.names[-1], setter=lambda c,r,v: setitem(r.names, -1, v)),
17
+ AttrColumn('rows', 'nRows', type=int, width=9),
18
+ AttrColumn('cols', 'nCols', type=int),
19
+ AttrColumn('keys', 'keyColNames'),
20
+ AttrColumn('source'),
21
+ ]
22
+ nKeys = 1
23
+
24
+ def newRow(self):
25
+ return Sheet('', columns=[ItemColumn('', 0)], rows=[])
26
+
27
+ def openRow(self, row):
28
+ return row # rowdef is Sheet
29
+
30
+ def getSheet(self, k):
31
+ for vs in self.rows:
32
+ if vs.name == k:
33
+ return vs
34
+
35
+ def addRow(self, sheet, **kwargs):
36
+ super().addRow(sheet, **kwargs)
37
+ if not self.options.load_lazy and not sheet.options.load_lazy:
38
+ sheet.ensureLoaded()
39
+
40
+ @asyncthread
41
+ def reloadSheets(self, sheets):
42
+ for vs in vd.Progress(sheets):
43
+ vs.reload()
44
+
45
+
46
+ class SheetsSheet(IndexSheet):
47
+ columns = [
48
+ AttrColumn('name'),
49
+ AttrColumn('type', '__class__.__name__'),
50
+ AttrColumn('pane', type=int),
51
+ Column('shortcut', getter=lambda c,r: getattr(r, 'shortcut'), setter=lambda c,r,v: setattr(r, '_shortcut', v)),
52
+ AttrColumn('nRows', type=int),
53
+ AttrColumn('nCols', type=int),
54
+ AttrColumn('nVisibleCols', type=int),
55
+ AttrColumn('cursorDisplay'),
56
+ AttrColumn('keyColNames'),
57
+ AttrColumn('source'),
58
+ AttrColumn('progressPct'),
59
+ # AttrColumn('threads', 'currentThreads', type=vlen),
60
+ ]
61
+ precious = False
62
+ nKeys = 1
63
+ def reload(self):
64
+ self.rows = self.source
65
+
66
+ def sort(self):
67
+ self.rows[1:] = sorted(self.rows[1:], key=self.sortkey)
68
+
69
+
70
+ class GlobalSheetsSheet(SheetsSheet): #1620
71
+ def sort(self):
72
+ IndexSheet.sort(self)
73
+
74
+
75
+ @VisiData.lazy_property
76
+ def sheetsSheet(vd):
77
+ return SheetsSheet("sheets", source=vd.sheets)
78
+
79
+
80
+ @VisiData.lazy_property
81
+ def allSheetsSheet(vd):
82
+ return GlobalSheetsSheet("sheets_all", source=vd.allSheets)
83
+
84
+
85
+
86
+ @Sheet.api
87
+ def nextRow(sheet, n=1):
88
+ sheet.cursorRowIndex += n
89
+ sheet.checkCursor()
90
+ return sheet.rows[sheet.cursorRowIndex] # cursorRow itself might be cached
91
+
92
+
93
+ vd.addCommand('S', 'sheets-stack', 'vd.push(vd.sheetsSheet)', 'open Sheets Stack: join or jump between the active sheets on the current stack')
94
+ vd.addCommand('gS', 'sheets-all', 'vd.push(vd.allSheetsSheet)', 'open Sheets Sheet: join or jump between all sheets from current session')
95
+
96
+ BaseSheet.addCommand('g>', 'open-source-next', 'vd.replace(openSource(source.nextRow())) if isinstance(source, IndexSheet) else fail("parent sheet must be Index Sheet")', 'open next sheet on parent index sheet')
97
+ BaseSheet.addCommand('g<', 'open-source-prev', 'vd.replace(openSource(source.nextRow(-1))) if isinstance(source, IndexSheet) else fail("parent sheet must be Index Sheet")', 'open prev sheet on parent index sheet')
98
+
99
+ IndexSheet.addCommand('g^R', 'reload-selected', 'reloadSheets(selectedRows or rows)', 'reload all selected sheets')
100
+
101
+ # when diving into a sheet, remove the index unless it is precious
102
+ SheetsSheet.addCommand('gC', 'columns-selected', 'vd.push(ColumnsSheet("all_columns", source=selectedRows))', 'open Columns Sheet with all visible columns from selected sheets')
103
+ SheetsSheet.addCommand('z^C', 'cancel-row', 'cancelThread(*cursorRow.currentThreads)', 'abort async thread for current sheet')
104
+ SheetsSheet.addCommand('gz^C', 'cancel-rows', 'for vs in selectedRows: cancelThread(*vs.currentThreads)', 'abort async threads for selected sheets')
105
+ SheetsSheet.addCommand('Enter', 'open-row', 'dest=cursorRow; vd.sheets.remove(sheet) if not sheet.precious else None; vd.push(openRow(dest))', 'open sheet referenced in current row')
106
+
107
+ vd.addGlobals(IndexSheet=IndexSheet,
108
+ SheetsSheet=SheetsSheet,
109
+ GlobalSheetsSheet=GlobalSheetsSheet)
@@ -0,0 +1,55 @@
1
+ from visidata import vd, VisiData, Sheet, ItemColumn, asyncthread
2
+
3
+
4
+ vd._inputHistoryList = vd.StoredList(name='input_history')
5
+ vd.inputHistory = {} # [input_type][input] -> anything
6
+
7
+
8
+ @VisiData.api
9
+ def addInputHistory(vd, input:str, type:str=''):
10
+ r = vd.processInputHistory(input, type)
11
+ if r:
12
+ vd._inputHistoryList.append(r)
13
+
14
+
15
+ @VisiData.api
16
+ def processInputHistory(vd, input:str, type:str=''):
17
+ hist = list(vd.inputHistory.setdefault(type, {}).keys())
18
+ if hist and hist[-1] == input:
19
+ return
20
+ if input in vd.inputHistory[type]:
21
+ n = vd.inputHistory[type][input].get('n', 0)
22
+ del vd.inputHistory[type][input] # make it the most recent entry
23
+ else:
24
+ n = 0
25
+
26
+ r = dict(type=type, input=input, n=n+1)
27
+ vd.inputHistory[type][input] = r
28
+ return r
29
+
30
+
31
+ class InputHistorySheet(Sheet):
32
+ # rowdef: dict(type=, input=, n=)
33
+ # .source=vd.inputHistory
34
+ columns = [
35
+ ItemColumn('type'),
36
+ ItemColumn('input'),
37
+ ]
38
+ def iterload(self):
39
+ yield from vd._inputHistoryList
40
+
41
+
42
+ @VisiData.before
43
+ @asyncthread
44
+ def run(vd, *args, **kwargs):
45
+ vd._inputHistoryList.reload()
46
+ for x in vd._inputHistoryList:
47
+ vd.processInputHistory(x.input, x.type)
48
+
49
+
50
+ @VisiData.property
51
+ def inputHistorySheet(vd):
52
+ return InputHistorySheet('input_history', source=vd._inputHistoryList.path)
53
+
54
+
55
+ vd.addCommand(None, 'open-input-history', 'vd.push(inputHistorySheet)', 'open sheet with previous inputs')
visidata/interface.py ADDED
@@ -0,0 +1,58 @@
1
+ from visidata import VisiData, vd
2
+
3
+ vd.theme_option('disp_splitwin_pct', 0, 'height of second sheet on screen')
4
+ vd.theme_option('disp_note_none', '⌀', 'visible contents of a cell whose value is None')
5
+ vd.theme_option('disp_truncator', '…', 'indicator that the contents are only partially visible')
6
+ vd.theme_option('disp_oddspace', '\u00b7', 'displayable character for odd whitespace')
7
+ vd.theme_option('disp_more_left', '<', 'header note indicating more columns to the left')
8
+ vd.theme_option('disp_more_right', '>', 'header note indicating more columns to the right')
9
+ vd.theme_option('disp_error_val', '', 'displayed contents for computation exception')
10
+ vd.theme_option('disp_ambig_width', 1, 'width to use for unicode chars marked ambiguous')
11
+
12
+ vd.theme_option('disp_pending', '', 'string to display in pending cells')
13
+ vd.theme_option('note_pending', '⌛', 'note to display for pending cells')
14
+ vd.theme_option('note_format_exc', '?', 'cell note for an exception during formatting')
15
+ vd.theme_option('note_getter_exc', '!', 'cell note for an exception during computation')
16
+ vd.theme_option('note_type_exc', '!', 'cell note for an exception during type conversion')
17
+
18
+ vd.theme_option('color_note_pending', 'bold magenta', 'color of note in pending cells')
19
+ vd.theme_option('color_note_type', '226 yellow', 'color of cell note for non-str types in anytype columns')
20
+ vd.theme_option('color_note_row', '220 yellow', 'color of row note on left edge')
21
+ vd.option('scroll_incr', -3, 'amount to scroll with scrollwheel')
22
+ vd.theme_option('disp_column_sep', '│', 'separator between columns')
23
+ vd.theme_option('disp_keycol_sep', '║', 'separator between key columns and rest of columns')
24
+ vd.theme_option('disp_rowtop_sep', '│', '') # ╷│┬╽⌜⌐▇
25
+ vd.theme_option('disp_rowmid_sep', '⁝', '') # ┃┊│█
26
+ vd.theme_option('disp_rowbot_sep', '⁝', '') # ┊┴╿⌞█⍿╵⎢┴⌊ ⋮⁝
27
+ vd.theme_option('disp_rowend_sep', '║', '') # ┊┴╿⌞█⍿╵⎢┴⌊
28
+ vd.theme_option('disp_keytop_sep', '║', '') # ╽╿┃╖╟
29
+ vd.theme_option('disp_keymid_sep', '║', '') # ╽╿┃
30
+ vd.theme_option('disp_keybot_sep', '║', '') # ╽╿┃╜‖
31
+ vd.theme_option('disp_endtop_sep', '║', '') # ╽╿┃╖╢
32
+ vd.theme_option('disp_endmid_sep', '║', '') # ╽╿┃
33
+ vd.theme_option('disp_endbot_sep', '║', '') # ╽╿┃╜‖
34
+ vd.theme_option('disp_selected_note', '•', '') #
35
+ vd.theme_option('disp_sort_asc', '↑↟⇞⇡⇧⇑', 'characters for ascending sort') # ↑▲↟↥↾↿⇞⇡⇧⇈⤉⤒⥔⥘⥜⥠⍏˄ˆ
36
+ vd.theme_option('disp_sort_desc', '↓↡⇟⇣⇩⇓', 'characters for descending sort') # ↓▼↡↧⇂⇃⇟⇣⇩⇊⤈⤓⥕⥙⥝⥡⍖˅ˇ
37
+ vd.theme_option('color_default', 'white on black', 'the default fg and bg colors')
38
+ vd.theme_option('color_default_hdr', 'bold', 'color of the column headers')
39
+ vd.theme_option('color_bottom_hdr', 'underline', 'color of the bottom header row')
40
+ vd.theme_option('color_current_row', 'reverse', 'color of the cursor row')
41
+ vd.theme_option('color_current_col', 'bold', 'color of the cursor column')
42
+ vd.theme_option('color_current_cell', '', 'color of current cell, if different from color_current_row+color_current_col')
43
+ vd.theme_option('color_current_hdr', 'bold reverse', 'color of the header for the cursor column')
44
+ vd.theme_option('color_column_sep', '246 blue', 'color of column separators')
45
+ vd.theme_option('color_key_col', '81 cyan', 'color of key columns')
46
+ vd.theme_option('color_hidden_col', '8', 'color of hidden columns on metasheets')
47
+ vd.theme_option('color_selected_row', '215 yellow', 'color of selected rows')
48
+ vd.theme_option('color_clickable', 'underline', 'color of internally clickable item')
49
+ vd.theme_option('color_code', 'bold white on 237', 'color of code sample')
50
+ vd.theme_option('color_heading', 'bold 200', 'color of header')
51
+ vd.theme_option('color_guide_unwritten', '243 on black', 'color of unwritten guides in GuideGuide')
52
+
53
+ vd.theme_option('force_256_colors', False, 'use 256 colors even if curses reports fewer')
54
+
55
+ vd.option('quitguard', False, 'confirm before quitting modified sheet')
56
+ vd.option('default_width', 20, 'default column width', replay=True, max_help=1) # TODO: make not replay and remove from markdown saver
57
+ vd.option('default_height', 4, 'default column height', max_help=-1)
58
+ vd.option('textwrap_cells', True, 'wordwrap text for multiline rows', max_help=1)
visidata/keys.py CHANGED
@@ -8,6 +8,8 @@ visidata.vd.prettykeys_trdict = {
8
8
  '^J': 'Enter',
9
9
  '^M': 'Enter',
10
10
  '^I': 'Tab',
11
+ 'KEY_BTAB': 'Shift+Tab',
12
+ '^@': 'Ctrl+Space',
11
13
  'KEY_UP': 'Up',
12
14
  'KEY_DOWN': 'Down',
13
15
  'KEY_LEFT': 'Left',
@@ -30,6 +32,8 @@ visidata.vd.prettykeys_trdict = {
30
32
  'kNXT5': 'Ctrl+PgDn',
31
33
  'KEY_IC5': 'Ctrl+Ins',
32
34
  'KEY_DC5': 'Ctrl+Del',
35
+ 'kDC5': 'Ctrl+Del',
36
+ 'KEY_SDC': 'Shift+Del',
33
37
 
34
38
  'KEY_IC': 'Ins',
35
39
  'KEY_DC': 'Del',
@@ -47,24 +51,21 @@ visidata.vd.prettykeys_trdict = {
47
51
  'BUTTON1_PRESSED': 'LeftClick',
48
52
  'BUTTON2_PRESSED': 'MiddleClick',
49
53
  'BUTTON3_PRESSED': 'RightClick',
50
- 'BUTTON4_PRESSED': 'ScrollwheelUp',
51
- 'BUTTON5_PRESSED': 'ScrollwheelDown',
52
- 'REPORT_MOUSE_POSITION': 'ScrollwheelDown',
53
- '2097152': 'ScrollwheelDown',
54
- 'KEY_F(1)': 'F1',
55
- 'KEY_F(2)': 'F2',
56
- 'KEY_F(3)': 'F3',
57
- 'KEY_F(4)': 'F4',
58
- 'KEY_F(5)': 'F5',
59
- 'KEY_F(6)': 'F6',
60
- 'KEY_F(7)': 'F7',
61
- 'KEY_F(8)': 'F8',
62
- 'KEY_F(9)': 'F9',
63
- 'KEY_F(10)': 'F10',
64
- 'KEY_F(11)': 'F11',
65
- 'KEY_F(12)': 'F12',
54
+ 'BUTTON4_PRESSED': 'ScrollUp',
55
+ 'BUTTON5_PRESSED': 'ScrollDown',
56
+ 'REPORT_MOUSE_POSITION': 'ScrollDown',
57
+ '2097152': 'ScrollDown',
66
58
  }
67
59
 
60
+ for i in range(1, 13):
61
+ d = visidata.vd.prettykeys_trdict
62
+ d[f'KEY_F({i})'] = f'F{i}'
63
+ d[f'KEY_F({i+12})'] = f'Shift+F{i}'
64
+ d[f'KEY_F({i+24})'] = f'Ctrl+F{i}'
65
+ d[f'KEY_F({i+36})'] = f'Ctrl+Shift+F{i}'
66
+ d[f'KEY_F({i+48})'] = f'Alt+F{i}'
67
+ d[f'KEY_F({i+60})'] = f'Alt+Shift+F{i}'
68
+
68
69
 
69
70
  @visidata.VisiData.api
70
71
  def prettykeys(vd, key):
@@ -0,0 +1,9 @@
1
+ from visidata import vd, IndexSheet
2
+
3
+
4
+ vd.option('load_lazy', False, 'load subsheets always (False) or lazily (True)')
5
+ vd.option('skip', 0, 'skip N rows before header', replay=True)
6
+ vd.option('header', 1, 'parse first N rows as column names', replay=True)
7
+
8
+ IndexSheet.options.header = 0
9
+ IndexSheet.options.skip = 0
@@ -4,23 +4,57 @@ from visidata import VisiData, vd, Sheet, date, anytype, Path, options, Column,
4
4
 
5
5
  @VisiData.api
6
6
  def open_pandas(vd, p):
7
- return PandasSheet(p.name, source=p)
7
+ return PandasSheet(p.base_stem, source=p)
8
8
 
9
9
  @VisiData.api
10
10
  def open_dta(vd, p):
11
- return PandasSheet(p.name, source=p, filetype='stata')
11
+ return PandasSheet(p.base_stem, source=p, filetype='stata')
12
12
 
13
13
  VisiData.open_stata = VisiData.open_pandas
14
14
 
15
15
  for ft in 'feather gbq orc pickle sas stata'.split():
16
16
  funcname ='open_'+ft
17
17
  if not getattr(VisiData, funcname, None):
18
- setattr(VisiData, funcname, lambda vd,p,ft=ft: PandasSheet(p.name, source=p, filetype=ft))
18
+ setattr(VisiData, funcname, lambda vd,p,ft=ft: PandasSheet(p.base_stem, source=p, filetype=ft))
19
19
 
20
+ @VisiData.api
21
+ @asyncthread
22
+ def save_dta(vd, p, *sheets):
23
+ import pandas as pd
24
+ import numpy as np
25
+
26
+ # STATA is a one-sheet software
27
+ # Save only the first sheet
28
+ vs = sheets[0]
29
+
30
+ columns = [col.name for col in vs.visibleCols]
31
+
32
+ # Get data types
33
+ types = list()
34
+ dispvals = next(vs.iterdispvals(format=True))
35
+ for col,_ in dispvals.items():
36
+ if col.type in [bool, int, float]:
37
+ types.append(col.type)
38
+ elif vd.isNumeric(col):
39
+ types.append(float)
40
+ else:
41
+ types.append(str)
42
+
43
+ # Populate numpy array
44
+ data = np.empty((vs.nRows, len(columns)), dtype=object)
45
+ for r_i, dispvals in enumerate(vs.iterdispvals(format=True)):
46
+ for c_i, v in enumerate(dispvals.values()):
47
+ data[r_i, c_i] = v
48
+
49
+ # Convert to pandas DataFrame and save
50
+ dtype = {col:t for col,t in zip(columns, types)}
51
+ df = pd.DataFrame(data, columns=columns)
52
+ df = df.astype(dtype)
53
+ df.to_stata(p, version=118, write_index=False)
20
54
 
21
55
  class DataFrameAdapter:
22
56
  def __init__(self, df):
23
- import pandas as pd
57
+ pd = vd.importExternal('pandas')
24
58
  if not isinstance(df, pd.DataFrame):
25
59
  vd.fail('%s is not a dataframe' % type(df).__name__)
26
60
 
@@ -57,7 +91,7 @@ class PandasSheet(Sheet):
57
91
  '''
58
92
 
59
93
  def dtype_to_type(self, dtype):
60
- import numpy as np
94
+ np = vd.importExternal('numpy')
61
95
  # Find the underlying numpy dtype for any pandas extension dtypes
62
96
  dtype = getattr(dtype, 'numpy_dtype', dtype)
63
97
  try:
@@ -74,7 +108,7 @@ class PandasSheet(Sheet):
74
108
 
75
109
  def read_tsv(self, path, **kwargs):
76
110
  'Partial function for reading TSV files using pd.read_csv'
77
- import pandas as pd
111
+ pd = vd.importExternal('pandas')
78
112
  return pd.read_csv(path, sep='\t', **kwargs)
79
113
 
80
114
  @property
@@ -111,7 +145,7 @@ class PandasSheet(Sheet):
111
145
 
112
146
  @asyncthread
113
147
  def reload(self):
114
- import pandas as pd
148
+ pd = vd.importExternal('pandas')
115
149
  if isinstance(self.source, pd.DataFrame):
116
150
  df = self.source
117
151
  elif isinstance(self.source, Path):
@@ -124,6 +158,12 @@ class PandasSheet(Sheet):
124
158
  readfunc = getattr(pd, 'read_'+filetype) or vd.error('no pandas.read_'+filetype)
125
159
  # readfunc() handles binary and text open()
126
160
  df = readfunc(self.source, **options.getall('pandas_'+filetype+'_'))
161
+ # some read methods (html, for example) return a list of dataframes
162
+ if isinstance(df, list):
163
+ for idx, inner_df in enumerate(df[1:], start=1):
164
+ vd.push(PandasSheet(f'{self.name}[{idx}]', source=inner_df))
165
+ df = df[0]
166
+ self.name += '[0]'
127
167
  if (filetype == 'pickle') and not isinstance(df, pd.DataFrame):
128
168
  vd.fail('pandas loader can only unpickle dataframes')
129
169
  else:
@@ -169,7 +209,7 @@ class PandasSheet(Sheet):
169
209
  self.rows.sort_values(by=by_cols, ascending=ascending, inplace=True)
170
210
 
171
211
  def _checkSelectedIndex(self):
172
- import pandas as pd
212
+ pd = vd.importExternal('pandas')
173
213
  if self._selectedMask.index is not self.df.index:
174
214
  # DataFrame was modified inplace, so the selection is no longer valid
175
215
  vd.status('pd.DataFrame.index updated, clearing {} selected rows'
@@ -224,7 +264,7 @@ class PandasSheet(Sheet):
224
264
  self.unselectRow(row)
225
265
 
226
266
  def clearSelected(self):
227
- import pandas as pd
267
+ pd = vd.importExternal('pandas')
228
268
  self._selectedMask = pd.Series(False, index=self.df.index)
229
269
 
230
270
  def selectByIndex(self, start=None, end=None):
@@ -251,7 +291,7 @@ class PandasSheet(Sheet):
251
291
  matching rows to the selection. If unselect is True, remove from the
252
292
  active selection instead.
253
293
  '''
254
- import pandas as pd
294
+ pd = vd.importExternal('pandas')
255
295
  case_sensitive = 'I' not in vd.options.regex_flags
256
296
  masks = pd.DataFrame([
257
297
  self.df[col.expr].astype(str).str.contains(pat=regex, case=case_sensitive, regex=True)
@@ -278,13 +318,13 @@ class PandasSheet(Sheet):
278
318
  DataFrame's dtypes.
279
319
  '''
280
320
 
281
- import pandas as pd
321
+ pd = vd.importExternal('pandas')
282
322
  return pd.DataFrame({
283
323
  col: [None] * n for col in self.df.columns
284
324
  }).astype(self.df.dtypes.to_dict(), errors='ignore')
285
325
 
286
326
  def addRows(self, rows, index=None, undo=True):
287
- import pandas as pd
327
+ pd = vd.importExternal('pandas')
288
328
  if index is None:
289
329
  self.df = self.df.append(pd.DataFrame(rows))
290
330
  else:
@@ -296,7 +336,7 @@ class PandasSheet(Sheet):
296
336
  vd.addUndo(self._deleteRows, range(index, index + len(rows)))
297
337
 
298
338
  def _deleteRows(self, which):
299
- import pandas as pd
339
+ pd = vd.importExternal('pandas')
300
340
  self.df.drop(which, inplace=True)
301
341
  self.df.index = pd.RangeIndex(self.nRows)
302
342
  self._checkSelectedIndex()
@@ -306,7 +346,7 @@ class PandasSheet(Sheet):
306
346
  vd.addUndo(self._deleteRows, index or self.nRows - 1)
307
347
 
308
348
  def delete_row(self, rowidx):
309
- import pandas as pd
349
+ pd = vd.importExternal('pandas')
310
350
  oldrow = self.df.iloc[rowidx:rowidx+1]
311
351
 
312
352
  # Use to_dict() here to work around an edge case when applying undos.
@@ -321,7 +361,7 @@ class PandasSheet(Sheet):
321
361
 
322
362
  def deleteBy(self, by):
323
363
  '''Delete rows for which func(row) is true. Returns number of deleted rows.'''
324
- import pandas as pd
364
+ pd = vd.importExternal('pandas')
325
365
  nRows = self.nRows
326
366
  vd.addUndo(setattr, self, 'df', self.df.copy())
327
367
  self.df = self.df[~by]
@@ -353,17 +393,17 @@ PandasSheet.addCommand(None, 'unselect-before', 'unselectByIndex(end=cursorRowIn
353
393
  PandasSheet.addCommand(None, 'stoggle-after', 'toggleByIndex(start=cursorRowIndex)', 'toggle selection of rows from cursor to bottom')
354
394
  PandasSheet.addCommand(None, 'select-after', 'selectByIndex(start=cursorRowIndex)', 'select all rows from cursor to bottom')
355
395
  PandasSheet.addCommand(None, 'unselect-after', 'unselectByIndex(start=cursorRowIndex)', 'unselect all rows from cursor to bottom')
356
- PandasSheet.addCommand(None, 'random-rows', 'nrows=int(input("random number to select: ", value=nRows)); vs=copy(sheet); vs.name=name+"_sample"; vs.rows=DataFrameAdapter(sheet.df.sample(nrows or nRows)); vd.push(vs)', 'open duplicate sheet with a random population subset of N rows'),
396
+ PandasSheet.addCommand(None, 'random-rows', 'nrows=int(input("random number to select: ", value=nRows)); vs=copy(sheet); vs.name=name+"_sample"; vs.rows=DataFrameAdapter(sheet.df.sample(nrows or nRows)); vd.push(vs)', 'open duplicate sheet with a random population subset of N rows')
357
397
 
358
398
  # Handle the regex selection family of commands through a single method,
359
399
  # since the core logic is shared
360
- PandasSheet.addCommand('|', 'select-col-regex', 'selectByRegex(regex=input("select regex: ", type="regex", defaultLast=True), columns=[cursorCol])', 'select rows matching regex in current column')
361
- PandasSheet.addCommand('\\', 'unselect-col-regex', 'selectByRegex(regex=input("select regex: ", type="regex", defaultLast=True), columns=[cursorCol], unselect=True)', 'unselect rows matching regex in current column')
362
- PandasSheet.addCommand('g|', 'select-cols-regex', 'selectByRegex(regex=input("select regex: ", type="regex", defaultLast=True), columns=visibleCols)', 'select rows matching regex in any visible column')
363
- PandasSheet.addCommand('g\\', 'unselect-cols-regex', 'selectByRegex(regex=input("select regex: ", type="regex", defaultLast=True), columns=visibleCols, unselect=True)', 'unselect rows matching regex in any visible column')
400
+ PandasSheet.addCommand('|', 'select-col-regex', 'selectByRegex(regex=inputRegex("select regex: ", defaultLast=True), columns=[cursorCol])', 'select rows matching regex in current column')
401
+ PandasSheet.addCommand('\\', 'unselect-col-regex', 'selectByRegex(regex=inputRegex("select regex: ", defaultLast=True), columns=[cursorCol], unselect=True)', 'unselect rows matching regex in current column')
402
+ PandasSheet.addCommand('g|', 'select-cols-regex', 'selectByRegex(regex=inputRegex("select regex: ", defaultLast=True), columns=visibleCols)', 'select rows matching regex in any visible column')
403
+ PandasSheet.addCommand('g\\', 'unselect-cols-regex', 'selectByRegex(regex=inputRegex("select regex: ", defaultLast=True), columns=visibleCols, unselect=True)', 'unselect rows matching regex in any visible column')
364
404
 
365
405
  # Override with a pandas/dataframe-aware implementation
366
- PandasSheet.addCommand('"', 'dup-selected', 'vs=PandasSheet(sheet.name, "selectedref", source=selectedRows.df); vd.push(vs)', 'open duplicate sheet with only selected rows'),
406
+ PandasSheet.addCommand('"', 'dup-selected', 'vs=PandasSheet(sheet.name, "selectedref", source=selectedRows.df); vd.push(vs)', 'open duplicate sheet with only selected rows')
367
407
 
368
408
  vd.addGlobals({
369
409
  'PandasSheet': PandasSheet,
@@ -0,0 +1,70 @@
1
+ import re
2
+ import os
3
+
4
+ from visidata import vd, date, asyncthread, VisiData, Progress, Sheet, Column, ItemColumn, deduceType, TypedWrapper, setitem
5
+
6
+
7
+ vd.option('airtable_auth_token', '', 'Airtable API key from https://airtable.com/account')
8
+
9
+ airtable_regex = r'^https://airtable.com/(app[A-Za-z0-9]+)/(tbl[A-Za-z0-9]+)/?(viw[A-z0-9]+)?'
10
+
11
+ @VisiData.api
12
+ def guessurl_airtable(vd, p, response):
13
+ m = re.search(airtable_regex, p.given)
14
+ if m:
15
+ return dict(filetype='airtable', _likelihood=10)
16
+
17
+
18
+ @VisiData.api
19
+ def open_airtable(vd, p):
20
+ pyairtable = vd.importExternal('pyairtable')
21
+
22
+ token = os.environ.get('AIRTABLE_AUTH_TOKEN') or vd.options.airtable_auth_token
23
+ if not token:
24
+ vd.requireOptions('airtable_auth_token', help='https://support.airtable.com/docs/creating-and-using-api-keys-and-access-tokens')
25
+
26
+ m = re.search(airtable_regex, p.given)
27
+ if not m:
28
+ vd.fail('invalid airtable url')
29
+
30
+ app, tbl, viw = m.groups()
31
+ return AirtableSheet('airtable', source=p,
32
+ airtable_auth_token=token,
33
+ airtable_base=app,
34
+ airtable_table=tbl,
35
+ airtable_view=viw)
36
+
37
+
38
+ class AirtableSheet(Sheet):
39
+ guide = '''
40
+ # Airtable
41
+ This sheet is a read-only download of all records in a table at _airtable.com_.
42
+ '''
43
+ rowtype = 'records' # rowdef: dict
44
+
45
+ columns = [
46
+ ItemColumn('id', 'id', type=str, width=0),
47
+ ItemColumn('createdTime', 'createdTime', type=date, width=0)
48
+ ]
49
+
50
+ def iterload(self):
51
+ self.fields = set()
52
+
53
+ for page in self.api.iterate(self.airtable_base, self.airtable_table, view=self.airtable_view):
54
+ for row in page:
55
+ yield row
56
+
57
+ for field, value in row['fields'].items():
58
+ if field not in self.fields:
59
+ col = ItemColumn('fields.'+field, type=deduceType(value))
60
+ self.addColumn(col)
61
+ self.fields.add(field)
62
+
63
+ def newRow(self):
64
+ return AttrDict(fields=AttrDict())
65
+
66
+
67
+ @AirtableSheet.lazy_property
68
+ def api(self):
69
+ import pyairtable
70
+ return pyairtable.Api(self.airtable_auth_token)
@@ -0,0 +1,102 @@
1
+
2
+ from visidata import *
3
+
4
+ vd.option('bitio_api_key', '', 'API key')
5
+
6
+ @VisiData.api
7
+ def new_bitio(vd, p):
8
+ vd.importExternal('bitdotio')
9
+ vd.requireOptions('bitio_api_key', help='https://docs.bit.io/docs/connecting-via-the-api')
10
+ return BitioReposSheet(p.name, source=p)
11
+
12
+ vd.openhttp_bitio = vd.new_bitio
13
+
14
+ @VisiData.lazy_property
15
+ def bitio_client(vd):
16
+ import bitdotio
17
+ return bitdotio.bitdotio(vd.options.bitio_api_key)
18
+
19
+ @VisiData.api
20
+ def bitio_api(vd, path, method, **kwargs):
21
+ t = vd.bitio_client.api_client.call_api(path, method,
22
+ header_params={'Accept': 'application/json',
23
+ 'Authorization': 'Bearer '+vd.bitio_client.access_token,
24
+ 'Content-Type': 'application/json'}, async_req=True, body=kwargs)
25
+ if not t.successful():
26
+ vd.warning(resp['Reason'])
27
+ return t.get()
28
+
29
+
30
+ class BitioReposSheet(Sheet):
31
+ rowtype = 'repos'
32
+ columns = [
33
+ AttrColumn('name'),
34
+ AttrColumn('creator'),
35
+ AttrColumn('owner'),
36
+ AttrColumn('bytes', type=int, width=0),
37
+ AttrColumn('collaborators'),
38
+ AttrColumn('description'),
39
+ AttrColumn('documentation'),
40
+ AttrColumn('endpoints'),
41
+ AttrColumn('is_private'),
42
+ AttrColumn('license'),
43
+ AttrColumn('query_count'),
44
+ AttrColumn('stars'),
45
+ AttrColumn('tables'),
46
+ AttrColumn('url'),
47
+ AttrColumn('watchers'),
48
+ ]
49
+ defer = True
50
+ def iterload(self):
51
+ yield from vd.bitio_client.list_repos(self.source.name)
52
+
53
+
54
+ @asyncthread
55
+ def putChanges(self):
56
+ adds, mods, dels = self.getDeferredChanges()
57
+ for row in dels.values():
58
+ vd.bitio_api(f'/users/{self.source.name}/repos/{row.name}/', 'DELETE')
59
+
60
+ for row, rowmods in Progress(list(mods.values()), gerund="updating"):
61
+ kwargs = {col.name: val for col, val in rowmods.items()}
62
+ vd.bitio_api(f'/users/{self.source.name}/repos/{row.name}/', 'PATCH', **kwargs)
63
+
64
+ def openRow(self, row):
65
+ return BitioRepoSheet(self.source.name, row.name, source=row)
66
+
67
+
68
+ class BitioRepoSheet(Sheet):
69
+ rowtype = 'tables'
70
+ columns = [
71
+ AttrColumn('url'),
72
+ AttrColumn('current_name'),
73
+ AttrColumn('description'),
74
+ AttrColumn('columns'),
75
+ AttrColumn('num_records', type=int),
76
+ AttrColumn('bytes', type=int),
77
+ AttrColumn('repo'),
78
+ AttrColumn('documentation'),
79
+ ]
80
+ def iterload(self):
81
+ yield from vd.bitio_client.list_tables(self.source.owner.split('/')[-2], self.source.name)
82
+
83
+ def openRow(self, row):
84
+ username = row.repo.split('/')[-4]
85
+ repo = row.repo.split('/')[-2]
86
+ return BitioTable(username, repo, source=row)
87
+
88
+
89
+ class BitioTable(Sheet):
90
+ def iterload(self):
91
+ username = self.source.repo.split('/')[-4]
92
+ repo = self.source.repo.split('/')[-2]
93
+ conn = vd.bitio_client.get_connection()
94
+ with conn.cursor() as cur:
95
+ cur.execute(f'SELECT * FROM "{username}/{repo}"."{self.source.current_name}"')
96
+ r = cur.fetchone()
97
+ if r:
98
+ yield r
99
+ self.columns = []
100
+ for c in vd.postgresGetColumns(cur):
101
+ self.addColumn(c)
102
+ yield from cur