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.
- visidata/__init__.py +72 -91
- visidata/_input.py +263 -44
- visidata/_open.py +84 -29
- visidata/_types.py +22 -4
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +65 -25
- visidata/apps/__init__.py +0 -0
- visidata/apps/vdsql/__about__.py +8 -0
- visidata/apps/vdsql/__init__.py +5 -0
- visidata/apps/vdsql/__main__.py +27 -0
- visidata/apps/vdsql/_ibis.py +748 -0
- visidata/apps/vdsql/bigquery.py +61 -0
- visidata/apps/vdsql/clickhouse.py +53 -0
- visidata/apps/vdsql/setup.py +40 -0
- visidata/apps/vdsql/snowflake.py +67 -0
- visidata/apps/vgit/__init__.py +13 -0
- visidata/apps/vgit/__main__.py +3 -0
- visidata/apps/vgit/abort.py +23 -0
- visidata/apps/vgit/blame.py +76 -0
- visidata/apps/vgit/branch.py +153 -0
- visidata/apps/vgit/config.py +95 -0
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- visidata/apps/vgit/grep.py +37 -0
- visidata/apps/vgit/log.py +81 -0
- visidata/apps/vgit/main.py +55 -0
- visidata/apps/vgit/remote.py +57 -0
- visidata/apps/vgit/repos.py +71 -0
- visidata/apps/vgit/setup.py +37 -0
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- visidata/apps/vgit/statusbar.py +34 -0
- visidata/basesheet.py +59 -50
- visidata/canvas.py +251 -99
- visidata/choose.py +15 -11
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +84 -18
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +89 -114
- visidata/color.py +142 -56
- visidata/column.py +134 -131
- visidata/ddw/input.ddw +74 -79
- visidata/ddw/regex.ddw +57 -0
- visidata/ddwplay.py +33 -14
- visidata/deprecated.py +77 -3
- visidata/desktop/visidata.desktop +7 -0
- visidata/editor.py +12 -6
- visidata/errors.py +5 -1
- visidata/experimental/__init__.py +0 -0
- visidata/experimental/diff_sheet.py +29 -0
- visidata/experimental/digit_autoedit.py +6 -0
- visidata/experimental/gdrive.py +89 -0
- visidata/experimental/google.py +37 -0
- visidata/experimental/gsheets.py +79 -0
- visidata/experimental/live_search.py +37 -0
- visidata/experimental/liveupdate.py +45 -0
- visidata/experimental/mark.py +133 -0
- visidata/experimental/noahs_tapestry/__init__.py +1 -0
- visidata/experimental/noahs_tapestry/tapestry.py +147 -0
- visidata/experimental/rownum.py +73 -0
- visidata/experimental/slide_cells.py +26 -0
- visidata/expr.py +8 -4
- visidata/extensible.py +32 -6
- visidata/features/__init__.py +0 -0
- visidata/features/addcol_audiometadata.py +42 -0
- visidata/features/addcol_histogram.py +34 -0
- visidata/features/canvas_save_svg.py +69 -0
- visidata/features/change_precision.py +46 -0
- visidata/features/cmdpalette.py +163 -0
- visidata/features/colorbrewer.py +363 -0
- visidata/{colorsheet.py → features/colorsheet.py} +17 -16
- visidata/features/command_server.py +105 -0
- visidata/features/currency_to_usd.py +70 -0
- visidata/{customdate.py → features/customdate.py} +2 -0
- visidata/features/dedupe.py +132 -0
- visidata/{describe.py → features/describe.py} +17 -15
- visidata/features/errors_guide.py +26 -0
- visidata/features/expand_cols.py +202 -0
- visidata/{fill.py → features/fill.py} +4 -2
- visidata/{freeze.py → features/freeze.py} +11 -6
- visidata/features/graph_seaborn.py +79 -0
- visidata/features/helloworld.py +10 -0
- visidata/features/hint_types.py +17 -0
- visidata/{incr.py → features/incr.py} +5 -0
- visidata/{join.py → features/join.py} +107 -53
- visidata/features/known_cols.py +21 -0
- visidata/features/layout.py +62 -0
- visidata/{melt.py → features/melt.py} +33 -21
- visidata/features/normcol.py +118 -0
- visidata/features/open_config.py +7 -0
- visidata/features/open_syspaste.py +18 -0
- visidata/features/ping.py +157 -0
- visidata/features/procmgr.py +208 -0
- visidata/features/random_sample.py +6 -0
- visidata/{regex.py → features/regex.py} +47 -31
- visidata/features/reload_every.py +55 -0
- visidata/features/rename_col_cascade.py +30 -0
- visidata/features/scroll_context.py +60 -0
- visidata/features/select_equal_selected.py +11 -0
- visidata/features/setcol_fake.py +65 -0
- visidata/{slide.py → features/slide.py} +75 -21
- visidata/features/sparkline.py +48 -0
- visidata/features/status_source.py +20 -0
- visidata/{sysedit.py → features/sysedit.py} +2 -1
- visidata/features/sysopen_mailcap.py +46 -0
- visidata/features/term_extras.py +13 -0
- visidata/{transpose.py → features/transpose.py} +5 -4
- visidata/features/type_ipaddr.py +73 -0
- visidata/features/type_url.py +11 -0
- visidata/{unfurl.py → features/unfurl.py} +9 -9
- visidata/{window.py → features/window.py} +2 -2
- visidata/form.py +50 -21
- visidata/freqtbl.py +81 -33
- visidata/fuzzymatch.py +414 -0
- visidata/graph.py +105 -33
- visidata/guide.py +180 -0
- visidata/help.py +75 -44
- visidata/hint.py +39 -0
- visidata/indexsheet.py +109 -0
- visidata/input_history.py +55 -0
- visidata/interface.py +58 -0
- visidata/keys.py +17 -16
- visidata/loaders/__init__.py +9 -0
- visidata/loaders/_pandas.py +61 -21
- visidata/loaders/api_airtable.py +70 -0
- visidata/loaders/api_bitio.py +102 -0
- visidata/loaders/api_matrix.py +148 -0
- visidata/loaders/api_reddit.py +306 -0
- visidata/loaders/api_zulip.py +249 -0
- visidata/loaders/archive.py +41 -7
- visidata/loaders/arrow.py +7 -7
- visidata/loaders/conll.py +49 -0
- visidata/loaders/csv.py +25 -7
- visidata/loaders/eml.py +3 -4
- visidata/loaders/f5log.py +1204 -0
- visidata/loaders/fec.py +325 -0
- visidata/loaders/fixed_width.py +3 -5
- visidata/loaders/frictionless.py +3 -3
- visidata/loaders/geojson.py +8 -5
- visidata/loaders/google.py +48 -0
- visidata/loaders/graphviz.py +4 -4
- visidata/loaders/hdf5.py +4 -4
- visidata/loaders/html.py +48 -10
- visidata/loaders/http.py +84 -30
- visidata/loaders/imap.py +20 -10
- visidata/loaders/jrnl.py +52 -0
- visidata/loaders/json.py +83 -29
- visidata/loaders/jsonla.py +74 -0
- visidata/loaders/lsv.py +15 -11
- visidata/loaders/mailbox.py +40 -0
- visidata/loaders/markdown.py +1 -3
- visidata/loaders/mbtiles.py +4 -5
- visidata/loaders/mysql.py +11 -13
- visidata/loaders/npy.py +7 -7
- visidata/loaders/odf.py +4 -1
- visidata/loaders/orgmode.py +428 -0
- visidata/loaders/pandas_freqtbl.py +14 -20
- visidata/loaders/parquet.py +62 -6
- visidata/loaders/pcap.py +3 -3
- visidata/loaders/pdf.py +4 -3
- visidata/loaders/png.py +19 -13
- visidata/loaders/postgres.py +9 -8
- visidata/loaders/rec.py +7 -3
- visidata/loaders/s3.py +342 -0
- visidata/loaders/sas.py +5 -5
- visidata/loaders/scrape.py +186 -0
- visidata/loaders/shp.py +6 -5
- visidata/loaders/spss.py +5 -6
- visidata/loaders/sqlite.py +68 -28
- visidata/loaders/texttables.py +1 -1
- visidata/loaders/toml.py +60 -0
- visidata/loaders/tsv.py +61 -19
- visidata/loaders/ttf.py +19 -7
- visidata/loaders/unzip_http.py +6 -5
- visidata/loaders/usv.py +1 -1
- visidata/loaders/vcf.py +16 -16
- visidata/loaders/vds.py +10 -7
- visidata/loaders/vdx.py +30 -5
- visidata/loaders/xlsb.py +8 -1
- visidata/loaders/xlsx.py +145 -25
- visidata/loaders/xml.py +6 -3
- visidata/loaders/xword.py +4 -4
- visidata/loaders/yaml.py +15 -5
- visidata/macos.py +1 -1
- visidata/macros.py +130 -41
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -154
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -147
- visidata/man/vd.txt +291 -151
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +79 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +131 -43
- visidata/pivot.py +74 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +50 -201
- visidata/rename_col.py +20 -0
- visidata/save.py +42 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -24
- visidata/sheets.py +229 -257
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +113 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +62 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +17 -0
- visidata/tests/test_menu.py +14 -0
- visidata/tests/test_path.py +13 -4
- visidata/text_source.py +53 -0
- visidata/textsheet.py +10 -3
- visidata/theme.py +44 -0
- visidata/themes/__init__.py +0 -0
- visidata/themes/ascii8.py +84 -0
- visidata/themes/asciimono.py +84 -0
- visidata/themes/light.py +17 -0
- visidata/threads.py +87 -39
- visidata/tuiwin.py +22 -0
- visidata/type_currency.py +22 -3
- visidata/type_date.py +31 -9
- visidata/type_floatsi.py +5 -1
- visidata/undo.py +18 -6
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
- visidata-3.0.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
- visidata-3.0.dist-info/RECORD +257 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.dev0.dist-info/RECORD +0 -142
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
- {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
|
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(
|
178
|
-
Sheet.addCommand('\\', 'unselect-col-regex', 'unselectByIdx(
|
179
|
-
Sheet.addCommand('g|', 'select-cols-regex', 'selectByIdx(
|
180
|
-
Sheet.addCommand('g\\', 'unselect-cols-regex', 'unselectByIdx(
|
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 += [
|
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,
|
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 =
|
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
|
-
|
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
|
-
|
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
|
218
|
-
|
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
|
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,
|
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
|
-
|
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
|