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/selection.py CHANGED
@@ -1,10 +1,66 @@
1
- from visidata import vd, Sheet, Progress, asyncthread, options, rotateRange, Fanout, undoAttrCopyFunc, copy
1
+ from copy import copy
2
+ from visidata import vd, Sheet, Progress, asyncthread, options, rotateRange, Fanout, undoAttrCopyFunc, RowColorizer, GuideSheet
2
3
 
3
4
  vd.option('bulk_select_clear', False, 'clear selected rows before new bulk selections', replay=True)
4
5
  vd.option('some_selected_rows', False, 'if no rows selected, if True, someSelectedRows returns all rows; if False, fails')
5
6
 
6
7
  Sheet.init('_selectedRows', dict) # rowid(row) -> row
7
8
 
9
+ vd.rowNoters.append(
10
+ lambda sheet, row: sheet.isSelected(row) and sheet.options.disp_selected_note
11
+ )
12
+ Sheet.colorizers.append( RowColorizer(2, 'color_selected_row', lambda s,c,r,v:
13
+ r is not None and s.isSelected(r))
14
+ )
15
+
16
+ class SelectionGuide(GuideSheet):
17
+ sheettype = Sheet
18
+ guide_text ='''# Selecting and filtering
19
+
20
+ Some commands operate only on "selected rows". For instance, a common command to filter is {help.commands.dup_selected}.
21
+
22
+ Many g-prefixed commands are like this. For example, use {help.commands.edit_cell}, but use {help.commands.setcol_input}. Search for "selected rows" in the [:onclick help-commands-all]commands list[/] or the [:onclick sysopen-help]manpage[/] for a full list.
23
+
24
+ Rows on the **Frequency Table** or **Pivot Table** reference a group of rows from the source sheet. Selecting a row on those sheets also selects the referenced rows on the underlying source sheet.
25
+
26
+ Select and unselect rows with these commands:
27
+
28
+ ## One row at a time
29
+
30
+ - {help.commands.select_row}
31
+ - {help.commands.unselect_row}
32
+ - {help.commands.stoggle_row}
33
+
34
+ ## All rows at the same time
35
+
36
+ - {help.commands.select_rows}
37
+ - {help.commands.unselect_rows}
38
+ - {help.commands.stoggle_rows}
39
+
40
+ ## By matching patterns
41
+
42
+ - {help.commands.select_col_regex}
43
+ - {help.commands.unselect_col_regex}
44
+ - {help.commands.select_cols_regex}
45
+ - {help.commands.unselect_cols_regex}
46
+
47
+ - {help.commands.select_equal_cell}
48
+ - {help.commands.select_equal_row}
49
+
50
+ ## Select by Python expression
51
+
52
+ Python expressions can use a column value by the column name, if the
53
+ column name is a valid Python identifier (with only letters, digits, and underscores).
54
+
55
+ - {help.commands.select_expr}
56
+ - {help.commands.unselect_expr}
57
+
58
+ ## Options
59
+
60
+ - {help.options.bulk_select_clear}
61
+ - {help.options.some_selected_rows}
62
+ '''
63
+
8
64
  @Sheet.api
9
65
  def isSelected(self, row):
10
66
  'Return True if *row* is selected.'
@@ -174,10 +230,10 @@ Sheet.addCommand('gzt', 'stoggle-after', 'toggle(rows[cursorRowIndex:])', 'toggl
174
230
  Sheet.addCommand('gzs', 'select-after', 'select(rows[cursorRowIndex:])', 'select all rows from cursor to bottom')
175
231
  Sheet.addCommand('gzu', 'unselect-after', 'unselect(rows[cursorRowIndex:])', 'unselect all rows from cursor to bottom')
176
232
 
177
- Sheet.addCommand('|', 'select-col-regex', 'selectByIdx(vd.searchRegex(sheet, regex=input("select regex: ", type="regex", defaultLast=True), columns="cursorCol"))', 'select rows matching regex in current column')
178
- Sheet.addCommand('\\', 'unselect-col-regex', 'unselectByIdx(vd.searchRegex(sheet, regex=input("unselect regex: ", type="regex", defaultLast=True), columns="cursorCol"))', 'unselect rows matching regex in current column')
179
- Sheet.addCommand('g|', 'select-cols-regex', 'selectByIdx(vd.searchRegex(sheet, regex=input("select regex: ", type="regex", defaultLast=True), columns="visibleCols"))', 'select rows matching regex in any visible column')
180
- Sheet.addCommand('g\\', 'unselect-cols-regex', 'unselectByIdx(vd.searchRegex(sheet, regex=input("unselect regex: ", type="regex", defaultLast=True), columns="visibleCols"))', 'unselect rows matching regex in any visible column')
233
+ Sheet.addCommand('|', 'select-col-regex', 'selectByIdx(searchInputRegex("select", columns="cursorCol"))', 'select rows matching regex in current column')
234
+ Sheet.addCommand('\\', 'unselect-col-regex', 'unselectByIdx(searchInputRegex("unselect", columns="cursorCol"))', 'unselect rows matching regex in current column')
235
+ Sheet.addCommand('g|', 'select-cols-regex', 'selectByIdx(searchInputRegex("select", columns="visibleCols"))', 'select rows matching regex in any visible column')
236
+ Sheet.addCommand('g\\', 'unselect-cols-regex', 'unselectByIdx(searchInputRegex("unselect", columns="visibleCols"))', 'unselect rows matching regex in any visible column')
181
237
 
182
238
  Sheet.addCommand(',', 'select-equal-cell', 'select(gatherBy(lambda r,c=cursorCol,v=cursorDisplay: c.getDisplayValue(r) == v), progress=False)', 'select rows matching current cell in current column')
183
239
  Sheet.addCommand('g,', 'select-equal-row', 'select(gatherBy(lambda r,currow=cursorRow,vcols=visibleCols: all([c.getDisplayValue(r) == c.getDisplayValue(currow) for c in vcols])), progress=False)', 'select rows matching current row in all visible columns')
@@ -189,3 +245,26 @@ Sheet.addCommand('z\\', 'unselect-expr', 'expr=inputExpr("unselect by expr: ");
189
245
 
190
246
  Sheet.addCommand(None, 'select-error-col', 'select(gatherBy(lambda r,c=cursorCol: c.isError(r)), progress=False)', 'select rows with errors in current column')
191
247
  Sheet.addCommand(None, 'select-error', 'select(gatherBy(lambda r,vcols=visibleCols: isinstance(r, TypedExceptionWrapper) or any([c.isError(r) for c in vcols])), progress=False)', 'select rows with errors in any column')
248
+
249
+ vd.addMenuItems('''
250
+ Row > Select > current row > select-row
251
+ Row > Select > all rows > select-rows
252
+ Row > Select > from top > select-before
253
+ Row > Select > to bottom > select-after
254
+ Row > Select > by Python expr > select-expr
255
+ Row > Select > equal to current cell > select-equal-cell
256
+ Row > Select > equal to current row > select-equal-row
257
+ Row > Select > errors > current column > select-error-col
258
+ Row > Select > errors > any column > select-error
259
+ Row > Unselect > current row > unselect-row
260
+ Row > Unselect > all rows > unselect-rows
261
+ Row > Unselect > from top > unselect-before
262
+ Row > Unselect > to bottom > unselect-after
263
+ Row > Unselect > by Python expr > unselect-expr
264
+ Row > Toggle select > current row > stoggle-row
265
+ Row > Toggle select > all rows > stoggle-rows
266
+ Row > Toggle select > from top > stoggle-before
267
+ Row > Toggle select > to bottom > stoggle-after
268
+ ''')
269
+
270
+ vd.addGuide('SelectionGuide', SelectionGuide)
visidata/settings.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import collections
2
+ import functools
2
3
  import sys
3
4
  import inspect
4
5
  import argparse
@@ -16,6 +17,12 @@ class SettingsMgr(collections.OrderedDict):
16
17
  super().__init__()
17
18
  self.allobjs = {}
18
19
 
20
+ def __hash__(self):
21
+ return hash(id(self))
22
+
23
+ def __eq__(self, other):
24
+ return self is other
25
+
19
26
  def objname(self, obj):
20
27
  if isinstance(obj, str):
21
28
  v = obj
@@ -51,6 +58,7 @@ class SettingsMgr(collections.OrderedDict):
51
58
  def setdefault(self, k, v):
52
59
  return self.set(k, v, 'default')
53
60
 
61
+ @functools.lru_cache()
54
62
  def _mappings(self, obj):
55
63
  '''Return list of contexts in order to resolve settings. ordering is, from lowest to highest precedence:
56
64
 
@@ -65,7 +73,7 @@ class SettingsMgr(collections.OrderedDict):
65
73
  '''
66
74
  mappings = []
67
75
  if obj:
68
- mappings += [self.objname(obj)]
76
+ mappings += [obj]
69
77
  mappings += [self.objname(cls) for cls in inspect.getmro(type(obj))]
70
78
 
71
79
  mappings += ['global', 'default']
@@ -75,7 +83,7 @@ class SettingsMgr(collections.OrderedDict):
75
83
  d = self.get(key, None)
76
84
  if d:
77
85
  for m in self._mappings(obj or vd.activeSheet):
78
- v = d.get(m)
86
+ v = d.get(self.objname(m))
79
87
  if v:
80
88
  return v
81
89
 
@@ -85,6 +93,7 @@ class SettingsMgr(collections.OrderedDict):
85
93
  obj = vd.activeSheet
86
94
 
87
95
  for o in self._mappings(obj):
96
+ o = self.objname(o)
88
97
  for k in self.keys():
89
98
  for o2 in self[k]:
90
99
  if o == o2:
@@ -98,19 +107,25 @@ class SettingsMgr(collections.OrderedDict):
98
107
 
99
108
 
100
109
  class Command:
101
- def __init__(self, longname, execstr, helpstr=''):
110
+ def __init__(self, longname, execstr, helpstr='', module='', deprecated=False):
102
111
  self.longname = longname
103
112
  self.execstr = execstr
104
113
  self.helpstr = helpstr
114
+ self.module = module
115
+ self.deprecated = deprecated
105
116
 
106
117
 
107
118
  class Option:
108
- def __init__(self, name, value, helpstr=''):
119
+ def __init__(self, name, value, description='', module='', help='', max_help=10):
120
+ # description gets shows on the manpage and the optionssheet; help is shown on the sidebar while editing
109
121
  self.name = name
110
122
  self.value = value
111
- self.helpstr = helpstr
123
+ self.helpstr = description
124
+ self.extrahelp = help
112
125
  self.replayable = False
113
126
  self.sheettype = BaseSheet
127
+ self.module = module
128
+ self.max_help = max_help
114
129
 
115
130
  def __str__(self):
116
131
  return str(self.value)
@@ -140,9 +155,12 @@ class OptionsObject:
140
155
  self._cache[(k, obj or vd.activeSheet)] = opt
141
156
  return opt
142
157
 
143
- def _set(self, k, v, obj=None, helpstr=''):
158
+ def _set(self, k, v, obj=None, helpstr='', module=None):
159
+ k, v = vd._resolve_optalias(k, v) # to set deprecated and abbreviated options
160
+
161
+ opt = self._get(k) or Option(k, v, '', module)
144
162
  self._cache.clear() # invalidate entire cache on any change
145
- return self._opts.set(k, Option(k, v, helpstr), obj)
163
+ return self._opts.set(k, Option(k, v, opt.helpstr or helpstr, opt.module or module), obj)
146
164
 
147
165
  def is_set(self, k, obj=None):
148
166
  d = self._opts.get(k, None)
@@ -175,6 +193,7 @@ class OptionsObject:
175
193
  def set(self, optname, value, obj='global'):
176
194
  "Override *value* for *optname* in the options context, or in the *obj* context if given."
177
195
  opt = self._get(optname)
196
+ module = None # keep default
178
197
  if opt:
179
198
  curval = opt.value
180
199
  t = type(curval)
@@ -192,30 +211,44 @@ class OptionsObject:
192
211
  if curval != value and self._get(optname, 'default').replayable:
193
212
  if obj != 'default' and type(obj) is not type: # default and class options set on init aren't recorded
194
213
  if vd.cmdlog:
195
- objname = self._opts.objname(obj)
196
- vd.cmdlog.addRow(vd.cmdlog.newRow(sheet=objname, row=optname,
197
- keystrokes='', input=str(value),
198
- longname='set-option', undofuncs=[]))
214
+ self.add_option_to_cmdlogs(obj, optname, value, 'set-option')
199
215
  else:
200
216
  curval = None
201
217
  vd.warning('setting unknown option %s' % optname)
218
+ module = 'unknown'
202
219
 
203
- return self._set(optname, value, obj)
220
+ return self._set(optname, value, obj, module=module)
204
221
 
205
222
  def unset(self, optname, obj=None):
206
223
  'Remove setting value for given context.'
207
224
  v = self._opts.unset(optname, obj)
208
225
  opt = self._get(optname)
209
226
  if vd.cmdlog and opt and opt.replayable:
210
- objname = self._opts.objname(obj)
211
- vd.cmdlog.addRow(vd.cmdlog.newRow(sheet=objname, row=optname,
212
- keystrokes='', input='',
213
- longname='unset-option'))
227
+ self.add_option_to_cmdlogs(obj, optname, value='', longname='unset-option')
214
228
  self._cache.clear() # invalidate entire cache on any change
215
229
  return v
216
230
 
217
- def setdefault(self, optname, value, helpstr):
218
- return self._set(optname, value, 'default', helpstr=helpstr)
231
+ def add_option_to_cmdlogs(self, obj, optname, value='', longname='set-option'):
232
+ 'Records option-set on cmdlogs'
233
+ objname = self._opts.objname(obj)
234
+ # all options are recorded on global cmdlog
235
+ vd.cmdlog.addRow(vd.cmdlog.newRow(sheet=objname, row=optname,
236
+ keystrokes='', input=str(value),
237
+ longname=longname, undofuncs=[]))
238
+ # global options are recorded on all cmdlog_sheet's
239
+ if obj == 'global':
240
+ for vs in vd.sheets:
241
+ vs.cmdlog_sheet.addRow(vd.cmdlog.newRow(sheet=objname, row=optname,
242
+ keystrokes='', input=str(value),
243
+ longname=longname, undofuncs=[]))
244
+ # sheet-specific options are recorded on that sheet
245
+ elif isinstance(obj, BaseSheet):
246
+ obj.cmdlog_sheet.addRow(vd.cmdlog.newRow(sheet=objname, row=optname,
247
+ keystrokes='', input=str(value),
248
+ longname=longname, undofuncs=[]))
249
+
250
+ def setdefault(self, optname, value, helpstr, module):
251
+ return self._set(optname, value, 'default', helpstr=helpstr, module=module)
219
252
 
220
253
  def getall(self, prefix=''):
221
254
  'Return dictionary of all options beginning with `prefix` (with `prefix` removed from the name).'
@@ -246,10 +279,27 @@ vd.bindkeys = SettingsMgr()
246
279
  vd._options = SettingsMgr()
247
280
 
248
281
  vd.options = vd.OptionsObject(vd._options) # global option settings
282
+ vd.option_aliases = {}
249
283
 
250
284
 
251
285
  @VisiData.api
252
- def option(vd, name, default, helpstr, replay=False, sheettype=BaseSheet):
286
+ def optalias(vd, altname, optname, val=None):
287
+ 'Create an alias `altname` for option `optname`, setting the value to a particular `val` (if not None).'
288
+ vd.option_aliases[altname] = (optname, val)
289
+
290
+
291
+ @VisiData.api
292
+ def _resolve_optalias(vd, optname, optval):
293
+ while optname in vd.option_aliases:
294
+ optname, v = vd.option_aliases.get(optname)
295
+ if v is not None: # value might be given
296
+ optval = v
297
+
298
+ return optname, optval
299
+
300
+
301
+ @VisiData.api
302
+ def option(vd, name, default, description, replay=False, sheettype=BaseSheet, help:str='', max_help=10):
253
303
  '''Declare a new option.
254
304
 
255
305
  - `name`: name of option
@@ -258,12 +308,21 @@ def option(vd, name, default, helpstr, replay=False, sheettype=BaseSheet):
258
308
  - `replay`: ``True`` if changes to the option should be stored in the **Command Log**
259
309
  - `sheettype`: ``None`` if the option is not sheet-specific, to make it global on CLI
260
310
  '''
261
- opt = vd.options.setdefault(name, default, helpstr)
311
+ opt = vd.options.setdefault(name, default, description, vd.importingModule)
262
312
  opt.replayable = replay
263
313
  opt.sheettype=sheettype
314
+ opt.extrahelp = help
315
+ opt.max_help = max_help
264
316
  return opt
265
317
 
266
318
 
319
+ @VisiData.api
320
+ def theme_option(vd, name, *args, **kwargs):
321
+ if name.startswith('color_'):
322
+ kwargs.setdefault('help', vd.help_color)
323
+ return vd.option(name, *args, **kwargs, max_help=-1)
324
+
325
+
267
326
  @BaseSheet.class_api
268
327
  @classmethod
269
328
  def addCommand(cls, keystrokes, longname, execstr, helpstr='', **kwargs):
@@ -274,9 +333,9 @@ def addCommand(cls, keystrokes, longname, execstr, helpstr='', **kwargs):
274
333
  - *execstr*: Python statement to pass to `exec()`'ed when the command is executed.
275
334
  - *helpstr*: help string shown in the **Commands Sheet**.
276
335
  '''
277
- vd.commands.set(longname, Command(longname, execstr, helpstr=helpstr, **kwargs), cls)
336
+ vd.commands.set(longname, Command(longname, execstr, helpstr=helpstr, module=vd.importingModule, **kwargs), cls)
278
337
  if keystrokes:
279
- vd.bindkeys.set(vd.prettykeys(keystrokes), longname, cls)
338
+ vd.bindkeys.set(vd.prettykeys(keystrokes.replace(' ', '')), longname, cls)
280
339
  return longname
281
340
 
282
341
  def _command(cls, binding, longname, helpstr, **kwargs):
@@ -332,9 +391,12 @@ def loadConfigFile(vd, fn='', _globals=None):
332
391
  try:
333
392
  with open(p) as fd:
334
393
  code = compile(fd.read(), str(p), 'exec')
394
+ vd.importingModule = 'visidatarc'
335
395
  exec(code, _globals)
336
396
  except Exception as e:
337
397
  vd.exceptionCaught(e)
398
+ finally:
399
+ vd.importingModule = None
338
400
 
339
401
  vd.addGlobals(_globals)
340
402
 
@@ -384,13 +446,15 @@ def loadConfigAndPlugins(vd, args=AttrDict()):
384
446
 
385
447
  for ep in eps_visidata:
386
448
  try:
449
+ vd.importingModule = ep.name
387
450
  plug = ep.load()
388
451
  sys.modules[f'visidata.plugins.{ep.name}'] = plug
389
452
  vd.debug(f'Plugin {ep.name} loaded')
390
453
  except Exception as e:
391
454
  vd.warning(f'Plugin {ep.name} failed to load')
392
455
  vd.exceptionCaught(e)
393
- continue
456
+ finally:
457
+ vd.importingModule = None
394
458
 
395
459
  # import plugins from .visidata/plugins before .visidatarc, so plugin options can be overridden
396
460
  for modname in (args.imports or vd.options.imports or '').split():
@@ -409,10 +473,84 @@ def loadConfigAndPlugins(vd, args=AttrDict()):
409
473
  vd.loadConfigFile(vd.options.config, vd.getGlobals())
410
474
 
411
475
 
476
+ @VisiData.api
477
+ def importModule(vd, pkgname):
478
+ 'Import the given *pkgname*, setting vd.importingModule to *pkgname* before import and resetting to None after.'
479
+ modparts = pkgname.split('.')
480
+ vd.importingModule = modparts[-1]
481
+ r = importlib.import_module(pkgname)
482
+ vd.importingModule = None
483
+ vd.importedModules.append(r)
484
+ return r
485
+
486
+
487
+ @VisiData.api
488
+ def importSubmodules(vd, pkgname):
489
+ 'Import all files below the given *pkgname*'
490
+ import pkgutil
491
+ import os.path
492
+
493
+ m = vd.importModule(pkgname)
494
+ for module in pkgutil.walk_packages(m.__path__):
495
+ vd.importModule(pkgname + '.' + module.name)
496
+
497
+
498
+ @VisiData.api
499
+ def importStar(vd, pkgname):
500
+ 'Add all symbols from *pkgname* into visidata globals.'
501
+ import pkgutil
502
+ import os.path
503
+
504
+ m = vd.importModule(pkgname)
505
+ vd.addGlobals({pkgname:m})
506
+ vd.addGlobals(m.__dict__)
507
+
508
+
509
+ @VisiData.api
510
+ def importExternal(vd, modname, pipmodname=''):
511
+ pipmodname = pipmodname or modname
512
+ try:
513
+ m = importlib.import_module(modname)
514
+ vd.addGlobals({modname:m})
515
+ return m
516
+ except ModuleNotFoundError as e:
517
+ vd.fail(f'External package "{modname}" not installed; run: pip install {pipmodname}')
518
+
519
+
520
+ @VisiData.api
521
+ def requireOptions(vd, *args, help=''):
522
+ '''Prompt user to input values for option names in *args* if current values
523
+ are non-false. Offer to persist the values to visidatarc.'''
524
+
525
+ optvals = {}
526
+ for optname in args:
527
+ if not getattr(vd.options, optname):
528
+ if help:
529
+ vd.status(help)
530
+ v = vd.input(f'{optname}: ', record=False, display='password' not in optname)
531
+ optvals[optname] = v
532
+
533
+ vd.setPersistentOptions(**optvals)
534
+
535
+
536
+ @VisiData.api
537
+ def setPersistentOptions(vd, **kwargs):
538
+ '''Set options from *kwargs* and offer to save them to visidatarc.'''
539
+ for optname, optval in kwargs.items():
540
+ setattr(vd.options, optname, optval)
541
+
542
+ optnames = ' '.join(kwargs.keys())
543
+ yn = vd.input(f'Save {len(kwargs)} options ({optnames}) to {vd.options.config}? ', record=False)[0:1]
544
+
545
+ if yn and yn in 'Yy':
546
+ with open(str(visidata.Path(vd.options.config)), mode='a') as fp:
547
+ for optname, optval in kwargs.items():
548
+ fp.write(f'options.{optname}={repr(optval)}\n')
549
+
550
+
412
551
  vd.option('visidata_dir', '~/.visidata/', 'directory to load and store additional files', sheettype=None)
413
552
 
414
553
  BaseSheet.bindkey('^M', '^J') # for windows ENTER
415
- BaseSheet.addCommand('gO', 'open-config', 'vd.push(open_txt(Path(options.config)))', 'open options.config as text sheet')
416
554
 
417
555
  vd.addGlobals({
418
556
  'options': vd.options, # legacy