visidata 2.11.1__py3-none-any.whl → 3.0.1__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 +259 -42
- visidata/_open.py +84 -29
- visidata/_types.py +21 -3
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +78 -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
- {vgit → visidata/apps/vgit}/blame.py +5 -2
- {vgit → visidata/apps/vgit}/branch.py +31 -16
- {vgit → visidata/apps/vgit}/config.py +3 -3
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- {vgit → visidata/apps/vgit}/grep.py +6 -5
- visidata/apps/vgit/log.py +81 -0
- {vgit → visidata/apps/vgit}/main.py +18 -5
- {vgit → visidata/apps/vgit}/remote.py +8 -4
- visidata/apps/vgit/repos.py +71 -0
- {vgit → visidata/apps/vgit}/setup.py +6 -4
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- {vgit → visidata/apps/vgit}/statusbar.py +2 -0
- visidata/basesheet.py +63 -51
- visidata/canvas.py +208 -93
- visidata/choose.py +6 -6
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +73 -17
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +88 -114
- visidata/color.py +142 -56
- visidata/column.py +121 -129
- 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 +6 -2
- 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 +22 -4
- 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 +197 -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} +3 -1
- 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} +32 -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} +77 -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 +200 -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 +20 -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 +2 -4
- 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 +54 -12
- 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/macros.py +129 -42
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -155
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -149
- visidata/man/vd.txt +291 -154
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +78 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +113 -32
- visidata/pivot.py +73 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +55 -205
- visidata/rename_col.py +20 -0
- visidata/save.py +37 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -25
- visidata/sheets.py +239 -260
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +114 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/benchmark.csv +52 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +65 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +28 -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 +89 -40
- 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 +17 -5
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
- visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
- visidata-3.0.1.dist-info/RECORD +258 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
- vgit/__init__.py +0 -1
- vgit/gitsheet.py +0 -164
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.1.data/scripts/vgit +0 -9
- visidata-2.11.1.dist-info/RECORD +0 -155
- {vgit → visidata/apps/vgit}/__main__.py +0 -0
- {vgit → visidata/apps/vgit}/abort.py +0 -0
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/top_level.txt +0 -0
visidata/ddw/regex.ddw
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
{"id": null, "type": null, "x": 5, "y": 1, "text": ".", "color": "keystrokes", "tags": [], "group": "", "frame": null, "rows": null, "duration_ms": null, "ref": null, "href": null}
|
2
|
+
{"x": 5, "y": 2, "text": "^", "color": "keystrokes", "tags": [], "group": ""}
|
3
|
+
{"x": 5, "y": 3, "text": "$", "color": "keystrokes", "tags": [], "group": ""}
|
4
|
+
{"x": 38, "y": 1, "text": "*", "color": "keystrokes", "tags": [], "group": ""}
|
5
|
+
{"x": 38, "y": 2, "text": "+", "color": "keystrokes", "tags": [], "group": ""}
|
6
|
+
{"x": 38, "y": 3, "text": "?", "color": "keystrokes", "tags": [], "group": ""}
|
7
|
+
{"x": 34, "y": 5, "text": "{m,n}", "color": "keystrokes", "tags": [], "group": ""}
|
8
|
+
{"x": 8, "y": 1, "text": "any char except newline", "color": "", "tags": [], "group": ""}
|
9
|
+
{"x": 8, "y": 2, "text": "start of string", "color": "", "tags": [], "group": ""}
|
10
|
+
{"x": 8, "y": 3, "text": "end of string", "color": "", "tags": [], "group": ""}
|
11
|
+
{"x": 42, "y": 1, "text": "0 or more", "color": "", "tags": [], "group": ""}
|
12
|
+
{"x": 42, "y": 2, "text": "1 or more", "color": "", "tags": [], "group": ""}
|
13
|
+
{"x": 42, "y": 3, "text": "0 or 1", "color": "", "tags": [], "group": ""}
|
14
|
+
{"x": 42, "y": 5, "text": "between m and n", "color": "", "tags": [], "group": ""}
|
15
|
+
{"x": 1, "y": 11, "text": "[abc]", "color": "keystrokes", "tags": [], "group": ""}
|
16
|
+
{"x": 0, "y": 12, "text": "[^abc]", "color": "keystrokes", "tags": [], "group": ""}
|
17
|
+
{"x": 4, "y": 4, "text": "\\d", "color": "keystrokes", "tags": [], "group": ""}
|
18
|
+
{"x": 4, "y": 6, "text": "\\s", "color": "keystrokes", "tags": [], "group": ""}
|
19
|
+
{"x": 4, "y": 8, "text": "\\w", "color": "keystrokes", "tags": [], "group": ""}
|
20
|
+
{"x": 8, "y": 4, "text": "digit char", "color": "", "tags": [], "group": ""}
|
21
|
+
{"x": 8, "y": 6, "text": "whitespace char", "color": "", "tags": [], "group": ""}
|
22
|
+
{"x": 4, "y": 10, "text": "\\b", "color": "keystrokes", "tags": [], "group": ""}
|
23
|
+
{"x": 8, "y": 10, "text": "word boundary", "color": "", "tags": [], "group": ""}
|
24
|
+
{"x": 8, "y": 8, "text": "word char", "color": "", "tags": [], "group": ""}
|
25
|
+
{"x": 18, "y": 8, "text": "[a-zA-Z0-9_]", "color": "keystrokes", "tags": [], "group": ""}
|
26
|
+
{"x": 36, "y": 7, "text": "(\u2026)", "color": "keystrokes", "tags": [], "group": ""}
|
27
|
+
{"x": 34, "y": 8, "text": "(?:\u2026)", "color": "keystrokes", "tags": [], "group": ""}
|
28
|
+
{"x": 8, "y": 11, "text": "any of", "color": "", "tags": [], "group": ""}
|
29
|
+
{"x": 15, "y": 11, "text": "a", "color": "keystrokes", "tags": [], "group": ""}
|
30
|
+
{"x": 17, "y": 11, "text": "or", "color": "", "tags": [], "group": ""}
|
31
|
+
{"x": 20, "y": 11, "text": "b", "color": "keystrokes", "tags": [], "group": ""}
|
32
|
+
{"x": 22, "y": 11, "text": "or", "color": "", "tags": [], "group": ""}
|
33
|
+
{"x": 25, "y": 11, "text": "c", "color": "keystrokes", "tags": [], "group": ""}
|
34
|
+
{"x": 19, "y": 4, "text": "[0-9]", "color": "keystrokes", "tags": [], "group": ""}
|
35
|
+
{"x": 42, "y": 7, "text": "capturing group", "color": "", "tags": [], "group": ""}
|
36
|
+
{"x": 42, "y": 8, "text": "non-capturing group", "color": "", "tags": [], "group": ""}
|
37
|
+
{"x": 9, "y": 12, "text": "", "color": "", "tags": [], "group": ""}
|
38
|
+
{"x": 8, "y": 12, "text": "not", "color": "", "tags": [], "group": ""}
|
39
|
+
{"x": 12, "y": 12, "text": "a", "color": "keystrokes", "tags": [], "group": ""}
|
40
|
+
{"x": 14, "y": 12, "text": "or", "color": "", "tags": [], "group": ""}
|
41
|
+
{"x": 17, "y": 12, "text": "b", "color": "keystrokes", "tags": [], "group": ""}
|
42
|
+
{"x": 19, "y": 12, "text": "or", "color": "", "tags": [], "group": ""}
|
43
|
+
{"x": 22, "y": 12, "text": "c", "color": "keystrokes", "tags": [], "group": ""}
|
44
|
+
{"x": 38, "y": 10, "text": "|", "color": "keystrokes", "tags": [], "group": ""}
|
45
|
+
{"x": 42, "y": 10, "text": "logical OR", "color": "", "tags": [], "group": ""}
|
46
|
+
{"x": 4, "y": 5, "text": "\\D", "color": "keystrokes", "tags": [], "group": ""}
|
47
|
+
{"x": 4, "y": 7, "text": "\\S", "color": "keystrokes", "tags": [], "group": ""}
|
48
|
+
{"x": 4, "y": 9, "text": "\\W", "color": "keystrokes", "tags": [], "group": ""}
|
49
|
+
{"x": 8, "y": 5, "text": "non-digit char", "color": "", "tags": [], "group": ""}
|
50
|
+
{"x": 8, "y": 7, "text": "non-whitespace char", "color": "", "tags": [], "group": ""}
|
51
|
+
{"x": 8, "y": 9, "text": "non-word char", "color": "", "tags": [], "group": ""}
|
52
|
+
{"x": 36, "y": 4, "text": "{m}", "color": "keystrokes", "tags": [], "group": ""}
|
53
|
+
{"x": 42, "y": 4, "text": "exactly m", "color": "", "tags": [], "group": ""}
|
54
|
+
{"x": 4, "y": 0, "text": "Character classes", "color": "bold underline", "tags": [], "group": ""}
|
55
|
+
{"x": 35, "y": 0, "text": "Repetition", "color": "bold underline", "tags": [], "group": ""}
|
56
|
+
{"x": 2, "y": 14, "text": "For full documentation on Python regular expressions, see", "color": "", "tags": [], "group": ""}
|
57
|
+
{"x": 6, "y": 15, "text": "https://docs.python.org/3/library/re.html", "color": "underline", "tags": [], "group": "", "href": "https://docs.python.org/3/library/re.html"}
|
visidata/ddwplay.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
from collections import defaultdict
|
2
2
|
import json
|
3
3
|
import time
|
4
|
-
from visidata import colors, vd
|
4
|
+
from visidata import colors, vd, clipdraw, ColorAttr
|
5
5
|
|
6
6
|
__all__ = ['Animation', 'AnimationMgr']
|
7
7
|
|
@@ -31,24 +31,38 @@ class Animation:
|
|
31
31
|
self.width = 0
|
32
32
|
self.load_from(fp)
|
33
33
|
|
34
|
-
def iterdeep(self, rows, x=0, y=0, parents=None):
|
35
|
-
'Walk rows deeply and generate (row, x, y, [ancestors]) for each row.'
|
34
|
+
def iterdeep(self, rows, x=0, y=0, parents=None, **kwargs):
|
35
|
+
'Walk rows deeply and generate (row, x, y, [ancestors]) for each row, filtering on kwargs.'
|
36
36
|
for r in rows:
|
37
37
|
newparents = (parents or []) + [r]
|
38
38
|
if r.type == 'frame': continue
|
39
39
|
if r.ref:
|
40
40
|
assert r.type == 'ref'
|
41
41
|
g = self.groups[r.ref]
|
42
|
-
|
42
|
+
if self.matches(r, kwargs):
|
43
|
+
yield from self.iterdeep(map(AttrDict, g.rows or []), x+r.x, y+r.y, newparents)
|
43
44
|
else:
|
44
|
-
|
45
|
-
|
45
|
+
if self.matches(r, kwargs):
|
46
|
+
yield r, x+r.x, y+r.y, newparents
|
47
|
+
yield from self.iterdeep(map(AttrDict, r.rows or []), x+r.x, x+r.y, newparents)
|
48
|
+
|
49
|
+
def matches(self, row, values):
|
50
|
+
for k, v in values.items():
|
51
|
+
actualv = getattr(row, k, None)
|
52
|
+
if isinstance(actualv, (list, tuple)) and isinstance(v, (list, tuple)):
|
53
|
+
if not any(x in actualv for x in v):
|
54
|
+
return False
|
55
|
+
elif v != actualv:
|
56
|
+
return False
|
57
|
+
return True
|
46
58
|
|
47
59
|
def load_from(self, fp):
|
48
60
|
for line in fp.readlines():
|
49
61
|
r = AttrDict(json.loads(line))
|
50
62
|
if r.type == 'frame':
|
51
|
-
self.frames[r.id]
|
63
|
+
f = self.frames[r.id]
|
64
|
+
f.update(r)
|
65
|
+
f.rows = []
|
52
66
|
elif r.type == 'group':
|
53
67
|
self.groups[r.id].update(r)
|
54
68
|
|
@@ -66,9 +80,12 @@ class Animation:
|
|
66
80
|
self.width = max(self.width, x+len(r.text))
|
67
81
|
self.height = max(self.height, y)
|
68
82
|
|
69
|
-
def draw(self, scr, *, t=0, x=0, y=0, loop=False, **kwargs):
|
70
|
-
|
71
|
-
|
83
|
+
def draw(self, scr, *, t=0, x=0, y=0, loop=False, attr=ColorAttr(), **kwargs):
|
84
|
+
windowHeight, windowWidth = scr.getmaxyx()
|
85
|
+
for r, dx, dy, _ in self.iterdeep(self.frames[''].rows, **kwargs):
|
86
|
+
text = f'[:onclick {r.href}]{r.text}[/]' if r.href else r.text
|
87
|
+
if y+dy < windowHeight:
|
88
|
+
clipdraw(scr, y+dy, x+dx, text, attr.update(colors[r.color], 2))
|
72
89
|
|
73
90
|
if not self.total_ms:
|
74
91
|
return None
|
@@ -77,8 +94,10 @@ class Animation:
|
|
77
94
|
for f in self.frames.values():
|
78
95
|
ms -= int(f.duration_ms or 0)
|
79
96
|
if ms < 0:
|
80
|
-
for r, dx, dy, _ in self.iterdeep(f.rows):
|
81
|
-
|
97
|
+
for r, dx, dy, _ in self.iterdeep(f.rows, **kwargs):
|
98
|
+
text = f'[:onclick {r.href}]{r.text}[/]' if r.href else r.text
|
99
|
+
if y+dy < windowHeight:
|
100
|
+
clipdraw(scr, y+dy, x+dx, text, colors[r.color])
|
82
101
|
|
83
102
|
return -ms/1000
|
84
103
|
|
@@ -117,9 +136,9 @@ class AnimationMgr:
|
|
117
136
|
for row in self.active:
|
118
137
|
startt, anim, akwargs = row
|
119
138
|
kwargs.update(akwargs)
|
120
|
-
nextt = anim.draw(scr, t=t
|
139
|
+
nextt = anim.draw(scr, t=t+startt, **kwargs)
|
121
140
|
if nextt is None:
|
122
|
-
if not akwargs.get('loop'):
|
141
|
+
if not akwargs.get('loop', True):
|
123
142
|
done.append(row)
|
124
143
|
else:
|
125
144
|
times.append(t+nextt)
|
visidata/deprecated.py
CHANGED
@@ -73,7 +73,7 @@ visidata.Sheet.exec_command = deprecated('2.0')(visidata.Sheet.execCommand)
|
|
73
73
|
@VisiData.api
|
74
74
|
def filetype(vd, ext, constructor):
|
75
75
|
'Add constructor to handle the given file type/extension.'
|
76
|
-
globals().setdefault('open_'+ext, lambda p,ext=ext: constructor(p.
|
76
|
+
globals().setdefault('open_'+ext, lambda p,ext=ext: constructor(p.base_stem, source=p, filetype=ext))
|
77
77
|
|
78
78
|
@deprecated('2.0', 'Sheet(namepart1, namepart2, ...)')
|
79
79
|
@VisiData.global_api
|
@@ -135,11 +135,12 @@ alias('show-aggregate', 'memo-aggregate')
|
|
135
135
|
|
136
136
|
# 2.6
|
137
137
|
|
138
|
-
clean_name
|
138
|
+
def clean_name(s):
|
139
|
+
return visidata.vd.cleanName(s)
|
139
140
|
|
140
141
|
def maybe_clean(s, vs):
|
141
142
|
if (vs or visidata.vd).options.clean_names:
|
142
|
-
s = visidata.cleanName(s)
|
143
|
+
s = visidata.vd.cleanName(s)
|
143
144
|
return s
|
144
145
|
|
145
146
|
def load_tsv(fn):
|
@@ -164,3 +165,76 @@ exceptionCaught = deprecated('2.6', 'vd.exceptionCaught')(vd.exceptionCaught)
|
|
164
165
|
openSource = deprecated('2.6', 'vd.openSource')(vd.openSource)
|
165
166
|
globalCommand = visidata.BaseSheet.addCommand
|
166
167
|
visidata.Sheet.StaticColumn = deprecated('2.11', 'Sheet.freeze_col')(visidata.Sheet.freeze_col)
|
168
|
+
visidata.Path.open_text = deprecated('3.0', 'visidata.Path.open')(visidata.Path.open)
|
169
|
+
|
170
|
+
vd.sysclip_value = deprecated('3.0', 'vd.sysclipValue')(vd.sysclipValue)
|
171
|
+
|
172
|
+
def itemsetter(i):
|
173
|
+
def g(obj, v):
|
174
|
+
obj[i] = v
|
175
|
+
return g
|
176
|
+
|
177
|
+
|
178
|
+
vd.optalias('force_valid_colnames', 'clean_names')
|
179
|
+
vd.optalias('dir_recurse', 'dir_depth', 100000)
|
180
|
+
vd.optalias('confirm_overwrite', 'overwrite', 'confirm')
|
181
|
+
vd.optalias('show_graph_labels', 'disp_graph_labels')
|
182
|
+
vd.optalias('zoom_incr', 'disp_zoom_incr')
|
183
|
+
|
184
|
+
alias('visibility-sheet', 'toggle-multiline')
|
185
|
+
alias('visibility-col', 'toggle-multiline')
|
186
|
+
|
187
|
+
def clean_to_id(s):
|
188
|
+
return visidata.vd.cleanName(s)
|
189
|
+
|
190
|
+
@deprecated('3.0', 'use try/finally')
|
191
|
+
class OnExit:
|
192
|
+
'"with OnExit(func, ...):" calls func(...) when the context is exited'
|
193
|
+
def __init__(self, func, *args, **kwargs):
|
194
|
+
self.func = func
|
195
|
+
self.args = args
|
196
|
+
self.kwargs = kwargs
|
197
|
+
|
198
|
+
def __enter__(self):
|
199
|
+
return self
|
200
|
+
|
201
|
+
def __exit__(self, exc_type, exc_value, exc_traceback):
|
202
|
+
try:
|
203
|
+
self.func(*self.args, **self.kwargs)
|
204
|
+
except Exception as e:
|
205
|
+
vd.exceptionCaught(e)
|
206
|
+
|
207
|
+
alias('open-inputs', 'open-input-history')
|
208
|
+
|
209
|
+
#vd.option('plugins_url', 'https://visidata.org/plugins/plugins.jsonl', 'source of plugins sheet')
|
210
|
+
|
211
|
+
@visidata.VisiData.api
|
212
|
+
def inputRegexSubstOld(vd, prompt):
|
213
|
+
'Input regex transform via oneliner (separated with `/`). Return parsed transformer as dict(before=, after=).'
|
214
|
+
rex = vd.inputRegex(prompt, type='regex-subst')
|
215
|
+
before, after = vd.parse_sed_transform(rex)
|
216
|
+
return dict(before=before, after=after)
|
217
|
+
|
218
|
+
|
219
|
+
visidata.Sheet.addCommand('', 'addcol-subst', 'addColumnAtCursor(Column(cursorCol.name + "_re", getter=regexTransform(cursorCol, **inputRegexSubstOld("transform column by regex: "))))', 'add column derived from current column, replacing regex with subst (may include \1 backrefs)', deprecated=True)
|
220
|
+
visidata.Sheet.addCommand('', 'setcol-subst', 'setValuesFromRegex([cursorCol], someSelectedRows, **inputRegexSubstOld("transform column by regex: "))', 'regex/subst - modify selected rows in current column, replacing regex with subst, (may include backreferences \\1 etc)', deprecated=True)
|
221
|
+
visidata.Sheet.addCommand('', 'setcol-subst-all', 'setValuesFromRegex(visibleCols, someSelectedRows, **inputRegexSubstOld(f"transform {nVisibleCols} columns by regex: "))', 'modify selected rows in all visible columns, replacing regex with subst (may include \\1 backrefs)', deprecated=True)
|
222
|
+
|
223
|
+
visidata.Sheet.addCommand('', 'split-col', 'addRegexColumns(makeRegexSplitter, cursorCol, inputRegex("split regex: ", type="regex-split"))', 'Add new columns from regex split', deprecated=True)
|
224
|
+
visidata.Sheet.addCommand('', 'capture-col', 'addRegexColumns(makeRegexMatcher, cursorCol, inputRegex("capture regex: ", type="regex-capture"))', 'add new column from capture groups of regex; requires example row', deprecated=True)
|
225
|
+
|
226
|
+
#vd.option('cmdlog_histfile', '', 'file to autorecord each cmdlog action to', sheettype=None)
|
227
|
+
#BaseSheet.bindkey('KEY_BACKSPACE', 'menu-help')
|
228
|
+
|
229
|
+
@deprecated('3.0', 'vd.callNoExceptions(col.setValue, row, value)')
|
230
|
+
@visidata.Column.api
|
231
|
+
def setValueSafe(self, row, value):
|
232
|
+
'setValue and ignore exceptions.'
|
233
|
+
return vd.callNoExceptions(self.setValue, row, value)
|
234
|
+
|
235
|
+
@deprecated('3.0', 'vd.callNoExceptions(sheet.checkCursor)')
|
236
|
+
@visidata.BaseSheet.api
|
237
|
+
def checkCursorNoExceptions(sheet):
|
238
|
+
return vd.callNoExceptions(sheet.checkCursor)
|
239
|
+
|
240
|
+
vd.addGlobals(globals())
|
visidata/editor.py
CHANGED
@@ -12,14 +12,16 @@ visidata.vd.tstp_signal = None
|
|
12
12
|
class SuspendCurses:
|
13
13
|
'Context manager to leave windowed mode on enter and restore it on exit.'
|
14
14
|
def __enter__(self):
|
15
|
-
|
15
|
+
if visidata.vd.scrFull:
|
16
|
+
curses.endwin()
|
16
17
|
if visidata.vd.tstp_signal:
|
17
18
|
signal.signal(signal.SIGTSTP, visidata.vd.tstp_signal)
|
18
19
|
|
19
20
|
def __exit__(self, exc_type, exc_val, tb):
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
if visidata.vd.scrFull:
|
22
|
+
curses.reset_prog_mode()
|
23
|
+
visidata.vd.scrFull.refresh()
|
24
|
+
curses.doupdate()
|
23
25
|
|
24
26
|
|
25
27
|
@visidata.VisiData.api
|
@@ -34,7 +36,7 @@ def launchEditor(vd, *args):
|
|
34
36
|
@visidata.VisiData.api
|
35
37
|
def launchBrowser(vd, *args):
|
36
38
|
'Launch $BROWSER with *args* as arguments.'
|
37
|
-
browser = os.environ.get('BROWSER') or vd.fail('
|
39
|
+
browser = os.environ.get('BROWSER') or vd.fail('no $BROWSER for %s' % args[0])
|
38
40
|
args = [browser] + list(args)
|
39
41
|
subprocess.call(args)
|
40
42
|
|
@@ -44,6 +46,7 @@ def launchExternalEditor(vd, v, linenum=0):
|
|
44
46
|
'Launch $EDITOR to edit string *v* starting on line *linenum*.'
|
45
47
|
import tempfile
|
46
48
|
with tempfile.NamedTemporaryFile() as temp:
|
49
|
+
temp.close() #2118 must close before re-opening on windows
|
47
50
|
with open(temp.name, 'w') as fp:
|
48
51
|
fp.write(v)
|
49
52
|
return vd.launchExternalEditorPath(visidata.Path(temp.name), linenum)
|
@@ -65,7 +68,8 @@ def launchExternalEditorPath(vd, path, linenum=0):
|
|
65
68
|
return ''
|
66
69
|
|
67
70
|
|
68
|
-
|
71
|
+
@visidata.VisiData.api
|
72
|
+
def suspend(vd):
|
69
73
|
import signal
|
70
74
|
with SuspendCurses():
|
71
75
|
os.kill(os.getpid(), signal.SIGSTOP)
|
@@ -94,3 +98,5 @@ sys.breakpointhook = _breakpoint
|
|
94
98
|
|
95
99
|
visidata.BaseSheet.addCommand('^Z', 'suspend', 'suspend()', 'suspend VisiData process')
|
96
100
|
visidata.BaseSheet.addCommand('', 'breakpoint', 'breakpoint()', 'drop into pdb REPL')
|
101
|
+
|
102
|
+
visidata.vd.addGlobals(SuspendCurses=SuspendCurses)
|
visidata/errors.py
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
import traceback
|
2
2
|
|
3
|
-
from visidata import vd, VisiData
|
3
|
+
from visidata import vd, VisiData
|
4
|
+
|
5
|
+
vd.option('debug', False, 'exit on error and display stacktrace', max_help=0)
|
4
6
|
|
5
|
-
__all__ = ['stacktrace', 'ExpectedException']
|
6
7
|
|
7
8
|
class ExpectedException(Exception):
|
8
9
|
'Controlled Exception from fail() or confirm(). Status or other interface update is done by raiser.'
|
@@ -28,4 +29,7 @@ def exceptionCaught(vd, exc=None, status=True, **kwargs):
|
|
28
29
|
if vd.options.debug:
|
29
30
|
raise
|
30
31
|
|
32
|
+
|
33
|
+
vd.addGlobals(stacktrace=stacktrace, ExpectedException=ExpectedException)
|
34
|
+
|
31
35
|
# see textsheet.py for ErrorSheet and associated commands
|
File without changes
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# command setdiff-sheet adds a diff colorizer for all sheets against current sheet
|
2
|
+
|
3
|
+
from visidata import Sheet, CellColorizer, vd
|
4
|
+
|
5
|
+
vd.theme_option('color_diff', 'red', 'color of values different from --diff source')
|
6
|
+
vd.theme_option('color_diff_add', 'yellow', 'color of rows/columns added to --diff source')
|
7
|
+
|
8
|
+
|
9
|
+
def makeDiffColorizer(othersheet):
|
10
|
+
def colorizeDiffs(sheet, col, row, cellval):
|
11
|
+
if row is None or col is None:
|
12
|
+
return None
|
13
|
+
vcolidx = sheet.visibleCols.index(col)
|
14
|
+
rowidx = sheet.rows.index(row)
|
15
|
+
if vcolidx < len(othersheet.visibleCols) and rowidx < len(othersheet.rows):
|
16
|
+
otherval = othersheet.visibleCols[vcolidx].getDisplayValue(othersheet.rows[rowidx])
|
17
|
+
if cellval.display != otherval:
|
18
|
+
return 'color_diff'
|
19
|
+
else:
|
20
|
+
return 'color_diff_add'
|
21
|
+
return colorizeDiffs
|
22
|
+
|
23
|
+
|
24
|
+
@Sheet.api
|
25
|
+
def setDiffSheet(vs):
|
26
|
+
Sheet.colorizers.append(CellColorizer(8, None, makeDiffColorizer(vs)))
|
27
|
+
|
28
|
+
|
29
|
+
Sheet.addCommand(None, 'setdiff-sheet', 'setDiffSheet()', 'set this sheet as diff sheet for all new sheets')
|
@@ -0,0 +1,6 @@
|
|
1
|
+
'Enter edit mode automatically when typing numeric digits.'
|
2
|
+
|
3
|
+
from visidata import Sheet
|
4
|
+
|
5
|
+
for i in range(0, 10):
|
6
|
+
Sheet.addCommand(str(i), 'autoedit-%s' % i, 'cursorCol.setValues([cursorRow], editCell(cursorVisibleColIndex, value="%s", i=1))' % i, 'replace cell value with input starting with %s' % i)
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import visidata
|
2
|
+
from visidata import vd, VisiData, Sheet, IndexSheet, SequenceSheet, ColumnItem, Path, AttrDict, ColumnAttr, asyncthread, Progress, ColumnExpr, date
|
3
|
+
|
4
|
+
from .gsheets import GSheetsIndex
|
5
|
+
|
6
|
+
@VisiData.api
|
7
|
+
def open_gdrive(vd, p):
|
8
|
+
return GDriveSheet(p.base_stem)
|
9
|
+
|
10
|
+
|
11
|
+
FILES_FIELDS_VISIBLE='''name size modifiedTime mimeType description'''.split()
|
12
|
+
|
13
|
+
FILES_FIELDS='''
|
14
|
+
id name size modifiedTime mimeType description owners
|
15
|
+
starred properties spaces version webContentLink webViewLink sharingUser lastModifyingUser shared
|
16
|
+
ownedByMe originalFilename md5Checksum size quotaBytesUsed headRevisionId imageMediaMetadata videoMediaMetadata parents
|
17
|
+
exportLinks contentRestrictions contentHints trashed
|
18
|
+
'''.split()
|
19
|
+
|
20
|
+
|
21
|
+
@VisiData.cached_property
|
22
|
+
def _drivebuild(vd):
|
23
|
+
return vd.google_discovery.build("drive", "v3", credentials=vd.google_auth('drive.readonly'))
|
24
|
+
|
25
|
+
@VisiData.cached_property
|
26
|
+
def _gdrive(self):
|
27
|
+
return vd.google_discovery.build("drive", "v3", credentials=vd.google_auth('drive.readonly')).files()
|
28
|
+
|
29
|
+
@VisiData.cached_property
|
30
|
+
def _gdrive_rw(self):
|
31
|
+
return vd.google_discovery.build("drive", "v3", credentials=vd.google_auth('drive')).files()
|
32
|
+
|
33
|
+
|
34
|
+
class GDriveSheet(Sheet):
|
35
|
+
rowtype='files' # rowdef: AttrDict of result from Google Drive files.list API
|
36
|
+
defer=True
|
37
|
+
columns = [
|
38
|
+
ColumnItem('name'),
|
39
|
+
ColumnItem('size', type=int),
|
40
|
+
ColumnItem('modifiedTime', type=date),
|
41
|
+
ColumnItem('mimeType'),
|
42
|
+
ColumnItem('name'),
|
43
|
+
ColumnExpr('owner', expr='owners[0]["displayName"]')
|
44
|
+
] + [
|
45
|
+
ColumnItem(x, width=0) for x in FILES_FIELDS if x not in FILES_FIELDS_VISIBLE
|
46
|
+
]
|
47
|
+
|
48
|
+
def iterload(self):
|
49
|
+
self.results = []
|
50
|
+
page_token = None
|
51
|
+
while True:
|
52
|
+
ret = vd._gdrive.list(
|
53
|
+
pageSize=1000,
|
54
|
+
pageToken=page_token,
|
55
|
+
fields="nextPageToken, files(%s)" % ','.join(FILES_FIELDS)
|
56
|
+
).execute()
|
57
|
+
|
58
|
+
self.results.append(ret)
|
59
|
+
|
60
|
+
for r in ret.get('files', []):
|
61
|
+
yield AttrDict(r)
|
62
|
+
|
63
|
+
page_token = ret.get('nextPageToken', None)
|
64
|
+
if not page_token:
|
65
|
+
break
|
66
|
+
|
67
|
+
def openRow(self, r):
|
68
|
+
if r.mimeType == 'application/vnd.google-apps.spreadsheet':
|
69
|
+
return GSheetsIndex(r.name, source=r.id)
|
70
|
+
if r.mimeType.startswith('image'):
|
71
|
+
return vd.launchBrowser(r.webViewLink)
|
72
|
+
return vd.openSource(r.webContentLink)
|
73
|
+
|
74
|
+
@asyncthread
|
75
|
+
def deleteFile(self, **kwargs):
|
76
|
+
with Progress(total=1) as prog:
|
77
|
+
vd._gdrive_rw.delete(**kwargs).execute()
|
78
|
+
prog.addProgress(1)
|
79
|
+
|
80
|
+
@asyncthread
|
81
|
+
def putChanges(self):
|
82
|
+
adds, mods, dels = self.getDeferredChanges()
|
83
|
+
|
84
|
+
for row in Progress(dels.values()):
|
85
|
+
self.deleteFile(fileId=row.id)
|
86
|
+
|
87
|
+
vd.sync()
|
88
|
+
self.preloadHook()
|
89
|
+
self.reload()
|
@@ -0,0 +1,37 @@
|
|
1
|
+
'''
|
2
|
+
# Using VisiData with Google Sheets and Google Drive
|
3
|
+
|
4
|
+
## Setup and Authentication
|
5
|
+
|
6
|
+
Add to .visidatarc:
|
7
|
+
|
8
|
+
import visidata.experimental.google
|
9
|
+
|
10
|
+
When VisiData attempts to use the Google API, it uses the "web authentication flow", which causes a web page to open asking for permissions to read and/or write your Google Sheets.
|
11
|
+
After granting permissions, VisiData caches the auth token in the .visidata directory. Remove `.visidata/google-*.pickle` to unauthenticate.
|
12
|
+
|
13
|
+
## Load a Google Sheet into VisiData
|
14
|
+
|
15
|
+
Use VisiData to open the URL or spreadsheet id, with filetype `g` (or `gsheets`):
|
16
|
+
|
17
|
+
vd https://docs.google.com/spreadsheets/d/1WV0JI_SsGfmoocXWJILK2nhfcxU1H7roqL1HE7zBdsY/ -f g
|
18
|
+
|
19
|
+
VisiData assumes the first row is the header row with column names.
|
20
|
+
|
21
|
+
## Save one or more sheets in VisiData as a Google Sheet
|
22
|
+
|
23
|
+
Save to `<sheetname>.g` using either `Ctrl+S` (current sheet only) or `g Ctrl+S` (all sheets on the sheet stack).
|
24
|
+
<sheetname> will be the visible name of the Spreadsheet in Google Drive; each sheet tab within the Spreadsheet will be named according to the sheet name within VisiData.
|
25
|
+
|
26
|
+
## List files in Google Drive
|
27
|
+
|
28
|
+
Use the `gdrive` filetype (the path doesn't matter):
|
29
|
+
|
30
|
+
vd . -f gdrive
|
31
|
+
|
32
|
+
- Files can be marked for deletion with `d` and execute those deletions with `z Ctrl+S` (same as on the DirSheet for the local filesystem).
|
33
|
+
- Images can be viewed with `Enter` (in browser).
|
34
|
+
'''
|
35
|
+
|
36
|
+
import visidata.experimental.gdrive
|
37
|
+
import visidata.experimental.gsheets
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import re
|
2
|
+
from visidata import vd, VisiData, Sheet, IndexSheet, SequenceSheet, ColumnItem, Path, AttrDict, ColumnAttr, asyncthread
|
3
|
+
|
4
|
+
SPREADSHEET_FIELDS='properties sheets namedRanges spreadsheetUrl developerMetadata dataSources dataSourceSchedules'.split()
|
5
|
+
SHEET_FIELDS='merges conditionalFormats filterViews protectedRanges basicFilter charts bandedRanges developerMetadata rowGroups columnGroups slicers'.split()
|
6
|
+
|
7
|
+
@VisiData.api
|
8
|
+
def open_gsheets(vd, p):
|
9
|
+
m = re.search(r'([A-z0-9_]{44})', p.given)
|
10
|
+
if m:
|
11
|
+
return GSheetsIndex(p.base_stem, source=m.groups()[0])
|
12
|
+
|
13
|
+
vd.open_g = vd.open_gsheets
|
14
|
+
|
15
|
+
@VisiData.lazy_property
|
16
|
+
def google_discovery(self):
|
17
|
+
googleapiclient = vd.importExternal('googleapiclient', 'google-api-python-client')
|
18
|
+
from googleapiclient import discovery
|
19
|
+
return discovery
|
20
|
+
|
21
|
+
|
22
|
+
@VisiData.cached_property
|
23
|
+
def _gsheets(vd):
|
24
|
+
return vd.google_discovery.build("sheets", "v4", credentials=vd.google_auth('spreadsheets.readonly')).spreadsheets()
|
25
|
+
|
26
|
+
|
27
|
+
@VisiData.cached_property
|
28
|
+
def _gsheets_rw(vd):
|
29
|
+
return vd.google_discovery.build("sheets", "v4", credentials=vd.google_auth('spreadsheets')).spreadsheets()
|
30
|
+
|
31
|
+
|
32
|
+
class GSheetsIndex(Sheet):
|
33
|
+
columns = [
|
34
|
+
ColumnAttr('title', 'properties.title'),
|
35
|
+
ColumnAttr('type', 'properties.sheetType', width=0),
|
36
|
+
ColumnAttr('nRows', 'properties.gridProperties.rowCount', type=int),
|
37
|
+
ColumnAttr('nCols', 'properties.gridProperties.columnCount', type=int),
|
38
|
+
]
|
39
|
+
def iterload(self):
|
40
|
+
googlesheet = vd._gsheets.get(spreadsheetId=self.source, fields=','.join(SPREADSHEET_FIELDS)).execute()
|
41
|
+
vd.status(googlesheet['properties']['title'])
|
42
|
+
|
43
|
+
for gsheet in googlesheet['sheets']:
|
44
|
+
yield AttrDict(gsheet)
|
45
|
+
|
46
|
+
def openRow(self, r):
|
47
|
+
return GSheet(r.properties.title, source=self.source)
|
48
|
+
|
49
|
+
|
50
|
+
class GSheet(SequenceSheet):
|
51
|
+
'.source is gsheet id; .name is sheet name'
|
52
|
+
def iterload(self):
|
53
|
+
result = vd._gsheets.values().get(spreadsheetId=self.source, range=self.name).execute()
|
54
|
+
yield from result.get('values', [])
|
55
|
+
|
56
|
+
|
57
|
+
@VisiData.api
|
58
|
+
def save_gsheets(vd, p, *sheets):
|
59
|
+
gsheet = vd._gsheets_rw.create(body={
|
60
|
+
'properties': { 'title': p.base_stem },
|
61
|
+
'sheets': list({'properties': { 'title': vs.name }} for vs in sheets),
|
62
|
+
}, fields='spreadsheetId').execute()
|
63
|
+
|
64
|
+
gsheetId = gsheet.get('spreadsheetId')
|
65
|
+
vd.status(f'https://docs.google.com/spreadsheets/d/{gsheetId}/')
|
66
|
+
|
67
|
+
for vs in sheets:
|
68
|
+
rows = [list(c.name for c in vs.visibleCols)]
|
69
|
+
rows += list(list(val for col, val in row.items())
|
70
|
+
for row in vs.iterdispvals(*vs.visibleCols, format=True))
|
71
|
+
|
72
|
+
vd._gsheets_rw.values().append(
|
73
|
+
spreadsheetId=gsheetId,
|
74
|
+
valueInputOption='RAW',
|
75
|
+
range=vs.name,
|
76
|
+
body=dict(values=rows)
|
77
|
+
).execute()
|
78
|
+
|
79
|
+
vd.save_g = vd.save_gsheets
|
@@ -0,0 +1,37 @@
|
|
1
|
+
from copy import copy
|
2
|
+
|
3
|
+
from visidata import Sheet, vd, asyncsingle
|
4
|
+
|
5
|
+
@Sheet.api
|
6
|
+
def dup_search(sheet, cols='cursorCol'):
|
7
|
+
vs = copy(sheet)
|
8
|
+
vs.name += "_search"
|
9
|
+
vs.rows = sheet.rows
|
10
|
+
vs.source = sheet
|
11
|
+
vs.search = ''
|
12
|
+
|
13
|
+
@asyncsingle
|
14
|
+
def live_search_async(val, status=False):
|
15
|
+
if not val:
|
16
|
+
vs.rows = vs.source.rows
|
17
|
+
else:
|
18
|
+
vs.rows = []
|
19
|
+
for i in vd.searchRegex(vs.source, regex=val, columns=cols, printStatus=status):
|
20
|
+
vs.addRow(vs.source.rows[i])
|
21
|
+
|
22
|
+
def live_search(val):
|
23
|
+
vs.draw(vs._scr)
|
24
|
+
vd.drawRightStatus(vs._scr, vs)
|
25
|
+
val = val.rstrip('\n')
|
26
|
+
if val == vs.search:
|
27
|
+
return
|
28
|
+
vs.search = val
|
29
|
+
live_search_async(val, sheet=vs, status=False)
|
30
|
+
|
31
|
+
vd.input("search regex: ", updater=live_search)
|
32
|
+
vd.push(vs)
|
33
|
+
vs.name = vs.source.name+'_'+vs.search
|
34
|
+
|
35
|
+
|
36
|
+
Sheet.addCommand('^[s', 'dup-search', 'dup_search("cursorCol")', 'search for regex forwards in current column, creating duplicate sheet with matching rows live')
|
37
|
+
Sheet.addCommand('g^[s', 'dup-search-cols', 'dup_search("visibleCols")', 'search for regex forwards in all columns, creating duplicate sheet with matching rows live')
|