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/stored_list.py
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
from visidata import vd, VisiData, Path, AttrDict
|
4
|
+
|
5
|
+
|
6
|
+
@VisiData.api
|
7
|
+
class StoredList(list):
|
8
|
+
'Read existing persisted list from filesystem, and append new elements to .jsonl in .visidata'
|
9
|
+
def __init__(self, *args, name:str='', **kwargs):
|
10
|
+
super().__init__(*args, **kwargs)
|
11
|
+
self.name = name
|
12
|
+
|
13
|
+
@property
|
14
|
+
def path(self):
|
15
|
+
vdpath = Path(vd.options.visidata_dir)
|
16
|
+
if vdpath.exists():
|
17
|
+
return vdpath/(self.name + '.jsonl')
|
18
|
+
|
19
|
+
def reload(self):
|
20
|
+
p = self.path
|
21
|
+
if not p or not p.exists():
|
22
|
+
return
|
23
|
+
|
24
|
+
ret = []
|
25
|
+
with p.open(encoding='utf-8-sig') as fp:
|
26
|
+
for line in fp:
|
27
|
+
value = vd.callNoExceptions(json.loads, line)
|
28
|
+
if value is not None:
|
29
|
+
if isinstance(value, dict):
|
30
|
+
value = AttrDict(value)
|
31
|
+
ret.append(value)
|
32
|
+
|
33
|
+
self[:] = ret # replace without using .append
|
34
|
+
|
35
|
+
def append(self, v):
|
36
|
+
super().append(v)
|
37
|
+
|
38
|
+
p = self.path
|
39
|
+
if p is None:
|
40
|
+
return
|
41
|
+
|
42
|
+
with p.open(encoding='utf-8', mode='a') as fp:
|
43
|
+
fp.write(json.dumps(v) + '\n')
|
visidata/stored_prop.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
from functools import wraps
|
2
|
+
import json
|
3
|
+
import atexit
|
4
|
+
|
5
|
+
from visidata import vd, VisiData, Path
|
6
|
+
|
7
|
+
vd.stored_properties = {}
|
8
|
+
|
9
|
+
@VisiData.class_api
|
10
|
+
@classmethod
|
11
|
+
def stored_property(vdcls, f):
|
12
|
+
def _save_on_exit():
|
13
|
+
if vd.stored_properties.get(f, None):
|
14
|
+
p = Path(vd.options.visidata_dir)/(f.__name__ + '.json')
|
15
|
+
with p.open(mode='w', encoding='utf-8') as fp:
|
16
|
+
fp.write(json.dumps(vd.stored_properties[f]))
|
17
|
+
|
18
|
+
@property
|
19
|
+
@wraps(f)
|
20
|
+
def _decorator(*args, **kwargs):
|
21
|
+
'Read persisted value from filesystem if available; otherwise call the decorated function to create a new instance.'
|
22
|
+
value = vd.stored_properties.get(f, None)
|
23
|
+
if value is not None:
|
24
|
+
return value
|
25
|
+
|
26
|
+
p = Path(vd.options.visidata_dir)/(f.__name__ + '.json')
|
27
|
+
if p.exists():
|
28
|
+
value = json.loads(p.open(encoding='utf-8-sig').read())
|
29
|
+
|
30
|
+
if value is None:
|
31
|
+
value = f(*args, **kwargs)
|
32
|
+
|
33
|
+
vd.stored_properties[f] = value
|
34
|
+
return value
|
35
|
+
|
36
|
+
atexit.register(_save_on_exit)
|
37
|
+
setattr(vdcls, f.__name__, _decorator)
|
38
|
+
return _decorator
|
visidata/tests/conftest.py
CHANGED
@@ -10,7 +10,7 @@ def curses_setup():
|
|
10
10
|
import visidata
|
11
11
|
|
12
12
|
curses.curs_set = lambda v: None
|
13
|
-
visidata.options.
|
13
|
+
visidata.options.overwrite = 'always'
|
14
14
|
|
15
15
|
|
16
16
|
@pytest.fixture(scope="function")
|
@@ -18,8 +18,8 @@ def mock_screen():
|
|
18
18
|
"""Set up and return a mock curses screen object."""
|
19
19
|
|
20
20
|
scr = Mock()
|
21
|
-
scr.addstr =
|
22
|
-
scr.move =
|
21
|
+
scr.addstr = lambda *args, **kwargs: None
|
22
|
+
scr.move = lambda *args, **kwargs: None
|
23
23
|
scr.getmaxyx = lambda: (25, 80)
|
24
24
|
|
25
25
|
return scr
|
@@ -0,0 +1,39 @@
|
|
1
|
+
import pytest
|
2
|
+
from unittest.mock import Mock, call
|
3
|
+
|
4
|
+
import visidata
|
5
|
+
|
6
|
+
|
7
|
+
class TestClipText:
|
8
|
+
@pytest.mark.parametrize('s, dispw', [
|
9
|
+
('abcdef', 6),
|
10
|
+
('桜 高橋', 7),
|
11
|
+
('[:onclick sidebar-toggle][:reverse] b to toggle sidebar [:]', 21),
|
12
|
+
])
|
13
|
+
def test_dispwidth(self, s, dispw):
|
14
|
+
assert visidata.dispwidth(s) == dispw
|
15
|
+
|
16
|
+
@pytest.mark.parametrize('s, w, clippeds, clippedw', [
|
17
|
+
('b to', 4, 'b to', 4),
|
18
|
+
('abcde', 8, 'abcde', 5),
|
19
|
+
(' jsonl', 5, ' jso…', 5),
|
20
|
+
('abcdで', 6, 'abcdで', 6),
|
21
|
+
('abcdで', 5, 'abcd…', 5),
|
22
|
+
])
|
23
|
+
def test_clipstr(self, s, w, clippeds, clippedw):
|
24
|
+
clips, clipw = visidata.clipstr(s, w)
|
25
|
+
assert clips == clippeds
|
26
|
+
assert clipw == clippedw
|
27
|
+
|
28
|
+
def test_clipdraw_chunks(self):
|
29
|
+
prechunks = [
|
30
|
+
('', 'x'),
|
31
|
+
('', 'jsonl'),
|
32
|
+
]
|
33
|
+
scr = Mock()
|
34
|
+
scr.getmaxyx.return_value = (80,25)
|
35
|
+
visidata.clipdraw_chunks(scr, 0, 0, prechunks, visidata.ColorAttr(), w=5)
|
36
|
+
scr.addstr.assert_has_calls([
|
37
|
+
call(0, 0, 'x', 0),
|
38
|
+
call(0, 1, 'jso…', 0),
|
39
|
+
], any_order=True)
|
visidata/tests/test_commands.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
import pkg_resources
|
2
1
|
import pytest
|
3
2
|
from unittest.mock import Mock
|
4
3
|
|
5
4
|
import itertools
|
6
5
|
import visidata
|
6
|
+
from pathlib import Path
|
7
7
|
|
8
8
|
# test separately as needed
|
9
9
|
|
@@ -11,11 +11,22 @@ import visidata
|
|
11
11
|
# commands that require curses, and are not
|
12
12
|
# replayable
|
13
13
|
nonTested = (
|
14
|
+
'toggle-profile',
|
14
15
|
'syscopy',
|
15
16
|
'syspaste',
|
17
|
+
'open-syspaste',
|
16
18
|
'macro',
|
17
19
|
'mouse',
|
20
|
+
'add-subreddits',
|
21
|
+
'add-submissions',
|
22
|
+
'open-zulip',
|
18
23
|
'suspend',
|
24
|
+
'open-memstats', # TODO add testing support
|
25
|
+
'plot-column-ext',
|
26
|
+
'plot-numerics-ext',
|
27
|
+
'reload-every',
|
28
|
+
'reload-modified',
|
29
|
+
'reload-rows',
|
19
30
|
'breakpoint',
|
20
31
|
'redraw',
|
21
32
|
'menu',
|
@@ -36,13 +47,14 @@ inputLines = { 'save-sheet': 'jetsam.csv', # save to some tmp file
|
|
36
47
|
'save-col-keys': 'debris.csv',
|
37
48
|
'pyobj-expr': '2+2', # open the python object for '4'
|
38
49
|
'edit-cell': '3',
|
50
|
+
'search-keys': 'foo',
|
39
51
|
'search-col': 'foo',
|
40
52
|
'searchr-col': 'bar',
|
41
53
|
'select-col-regex': '.',
|
42
54
|
'select-cols-regex': '.',
|
43
55
|
'unselect-col-regex': '.',
|
56
|
+
'exec-python': 'import time',
|
44
57
|
'unselect-cols-regex': '.',
|
45
|
-
'edit-cell': '', # no change should not error
|
46
58
|
'go-col-regex': 'Units', # column name in sample
|
47
59
|
'go-col-number': '2',
|
48
60
|
'go-row-number': '5', # go to row 5
|
@@ -50,24 +62,54 @@ inputLines = { 'save-sheet': 'jetsam.csv', # save to some tmp file
|
|
50
62
|
'addcol-expr': 'Units', # just copy the column
|
51
63
|
'addcol-incr-step': '2',
|
52
64
|
'setcol-incr-step': '2',
|
65
|
+
'setcol-iter': 'range(1, 100)',
|
53
66
|
'setcol-format-enum': '1=cat',
|
54
|
-
'
|
67
|
+
'setcol-input': '5',
|
55
68
|
'show-expr': 'OrderDate',
|
56
69
|
'setcol-expr': 'OrderDate',
|
70
|
+
'open-ping': 'localhost',
|
57
71
|
'setcell-expr': 'OrderDate',
|
58
72
|
'setcol-range': 'range(100)',
|
59
73
|
'repeat-input-n': '1',
|
60
|
-
'
|
61
|
-
'addcol-subst': r'Units/(\w)/\1', # the first character
|
74
|
+
'addcol-regex-subst': dict(before=r'Units/(\w)', after=r'\1'), # the first character
|
62
75
|
'search-cols': 'foo',
|
63
76
|
'searchr-cols': 'bar',
|
64
77
|
'select-cols-regex': '.',
|
65
78
|
'select-expr': 'OrderDate',
|
79
|
+
'setcol-fake': 'name',
|
66
80
|
'unselect-expr': 'OrderDate',
|
67
81
|
'unselect-cols-regex': '.',
|
68
82
|
'random-rows': '3',
|
83
|
+
'select-random': '3',
|
69
84
|
'import-python': 'math',
|
70
85
|
'pyobj-expr-row': 'Units + "s"', # open the python object for '4'
|
86
|
+
'expand-col-depth': '0',
|
87
|
+
'contract-col-depth': '0',
|
88
|
+
'contract-cols-depth': '0',
|
89
|
+
'expand-cols-depth': '0',
|
90
|
+
'save-cmdlog': 'test_commands.vdj',
|
91
|
+
'aggregate-col': 'mean',
|
92
|
+
'memo-aggregate': 'mean',
|
93
|
+
'addcol-shell': '',
|
94
|
+
'theme-input': 'light',
|
95
|
+
'add-rows': '1',
|
96
|
+
'join-sheets-top2': 'append',
|
97
|
+
'join-sheets-all': 'append',
|
98
|
+
'resize-col-input': '10',
|
99
|
+
'resize-cols-input': '10',
|
100
|
+
'resize-height-input': '10',
|
101
|
+
'melt-regex': '(.*)_(.*)',
|
102
|
+
'addcol-split': '-',
|
103
|
+
'addcol-capture': '(.*)_(.*)',
|
104
|
+
'slide-left-n': '2',
|
105
|
+
'slide-right-n': '1',
|
106
|
+
'slide-down-n': '1',
|
107
|
+
'slide-up-n': '1',
|
108
|
+
'addcol-window': '0 2',
|
109
|
+
'select-around-n': '1',
|
110
|
+
'sheet': '',
|
111
|
+
'col': 'Units',
|
112
|
+
'row': '5',
|
71
113
|
}
|
72
114
|
|
73
115
|
@pytest.mark.usefixtures('curses_setup')
|
@@ -86,6 +128,9 @@ class TestCommands:
|
|
86
128
|
nerrs = 0
|
87
129
|
ntotal = 0
|
88
130
|
for longname in cmdlist.keys():
|
131
|
+
cmd = vs.getCommand(longname)
|
132
|
+
if cmd and cmd.deprecated:
|
133
|
+
continue
|
89
134
|
if not isTestableCommand(longname, cmdlist):
|
90
135
|
continue
|
91
136
|
ntotal += 1
|
@@ -102,6 +147,12 @@ class TestCommands:
|
|
102
147
|
if nerrs > 0:
|
103
148
|
assert False
|
104
149
|
|
150
|
+
# cleanup
|
151
|
+
for f in ['flotsam.csv', 'debris.csv', 'jetsam.csv', 'lagan.csv', 'test_commands.vdj']:
|
152
|
+
pf = Path(f)
|
153
|
+
if pf.exists: pf.unlink()
|
154
|
+
|
155
|
+
|
105
156
|
def runOneTest(self, mock_screen, longname):
|
106
157
|
visidata.vd.clearCaches() # we want vd to return a new VisiData object for each command
|
107
158
|
vd = visidata.vd
|
@@ -114,7 +165,7 @@ class TestCommands:
|
|
114
165
|
else:
|
115
166
|
vd.getkeystroke = Mock(side_effect=['^J'])
|
116
167
|
|
117
|
-
sample_file =
|
168
|
+
sample_file = vd.pkg_resources_files(visidata) / 'tests/sample.tsv'
|
118
169
|
vs = visidata.TsvSheet('test_commands', source=visidata.Path(sample_file))
|
119
170
|
vs.reload.__wrapped__(vs)
|
120
171
|
vs.vd = vd
|
@@ -122,4 +173,8 @@ class TestCommands:
|
|
122
173
|
vd.allSheets = [vs]
|
123
174
|
vs.mouseX, vs.mouseY = (4, 4)
|
124
175
|
vs.draw(mock_screen)
|
125
|
-
|
176
|
+
if longname in inputLines:
|
177
|
+
vd.currentReplayRow = vd.cmdlog.newRow(longname=longname, input=inputLines[longname])
|
178
|
+
else:
|
179
|
+
vd.currentReplayRow = vd.cmdlog.newRow(longname=longname)
|
180
|
+
vs.execCommand(longname, vdglobals=vd.getGlobals())
|
visidata/tests/test_edittext.py
CHANGED
@@ -48,7 +48,7 @@ class TestEditText:
|
|
48
48
|
exception = kwargs.pop('exception', None)
|
49
49
|
if exception:
|
50
50
|
with pytest.raises(exception):
|
51
|
-
visidata.vd.editline(mock_screen, 0, 0, 0, **kwargs)
|
51
|
+
visidata.vd.editline(mock_screen, 0, 0, 0, attr=visidata.ColorAttr(), **kwargs)
|
52
52
|
else:
|
53
|
-
r = visidata.vd.editline(mock_screen, 0, 0, 0, **kwargs)
|
53
|
+
r = visidata.vd.editline(mock_screen, 0, 0, 0, attr=visidata.ColorAttr(), **kwargs)
|
54
54
|
assert r == result
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import pytest
|
2
|
+
import visidata
|
3
|
+
|
4
|
+
@pytest.mark.usefixtures('curses_setup')
|
5
|
+
class TestFeatures:
|
6
|
+
def test_features(self, mock_screen):
|
7
|
+
tests = [
|
8
|
+
(mod, getattr(mod, k))
|
9
|
+
for mod in visidata.vd.importedModules
|
10
|
+
for k in dir(mod)
|
11
|
+
if k.startswith('test_')
|
12
|
+
]
|
13
|
+
for mod, testfunc in tests:
|
14
|
+
print(mod, testfunc.__name__)
|
15
|
+
visidata.vd.resetVisiData()
|
16
|
+
visidata.vd.scr = mock_screen
|
17
|
+
testfunc(visidata.vd)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
from visidata import vd, TableSheet
|
3
|
+
import pytest
|
4
|
+
|
5
|
+
|
6
|
+
class TestMenu:
|
7
|
+
def test_menuitems(self):
|
8
|
+
vd.addMenuItems('''Column > Add column > foobar > hello-world''')
|
9
|
+
|
10
|
+
m = TableSheet().getMenuItem(['Column', 'Add column', 'foobar'])
|
11
|
+
assert m
|
12
|
+
|
13
|
+
with pytest.raises(AssertionError):
|
14
|
+
vd.addMenuItems('''Column > Add column > non-command''')
|
visidata/tests/test_path.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import io
|
1
2
|
import pytest
|
2
3
|
|
3
4
|
from visidata import Path
|
@@ -16,11 +17,19 @@ class TestVisidataPath:
|
|
16
17
|
assert "https://visidata.org/hello/b.tsv" == str(url_path.with_name('b.tsv')), '{} should be https://visidata.org/hello/b.tsv'.format(url_path.with_name('b.tsv'))
|
17
18
|
assert "https://visidata.org/hello/a/b.tsv" == str(url_path.with_name('a/b.tsv')), '{} should be https://visidata.org/hello/a/b.tsv'.format(url_path.with_name('a/b.tsv'))
|
18
19
|
|
19
|
-
assert Path('foo.a.b').
|
20
|
+
assert Path('foo.a.b').base_stem == 'foo.a'
|
20
21
|
assert Path('foo.a.b').ext == 'b'
|
21
22
|
assert Path('foo').ext == ''
|
22
|
-
assert Path('foo').
|
23
|
+
assert Path('foo').base_stem == 'foo'
|
23
24
|
assert Path('foo.').ext == ''
|
24
|
-
assert Path('foo.').
|
25
|
+
assert Path('foo.').base_stem == 'foo.'
|
25
26
|
assert Path('.foo').ext == ''
|
26
|
-
assert Path('.foo').
|
27
|
+
assert Path('.foo').base_stem == '.foo'
|
28
|
+
|
29
|
+
|
30
|
+
def test_opentwice(self):
|
31
|
+
'fresh iterator for each open'
|
32
|
+
p = Path('test', fptext=io.StringIO('<html>'))
|
33
|
+
a = next(p.open())
|
34
|
+
b = next(p.open())
|
35
|
+
assert a == b
|
visidata/text_source.py
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
from visidata import vd, BaseSheet
|
4
|
+
|
5
|
+
vd.option('regex_skip', '', 'regex of lines to skip in text sources', help='regex')
|
6
|
+
vd.option('regex_flags', 'I', 'flags to pass to re.compile() [AILMSUX]', replay=True)
|
7
|
+
|
8
|
+
@BaseSheet.api
|
9
|
+
def regex_flags(sheet):
|
10
|
+
'Return flags to pass to regex functions from options'
|
11
|
+
return sum(getattr(re, f.upper()) for f in sheet.options.regex_flags)
|
12
|
+
|
13
|
+
|
14
|
+
class FilterFile:
|
15
|
+
def __init__(self, fp, regex:str, regex_flags:int=0):
|
16
|
+
import re
|
17
|
+
self._fp = fp
|
18
|
+
self._regex_skip = re.compile(regex, regex_flags)
|
19
|
+
|
20
|
+
def readline(self) -> str:
|
21
|
+
while True:
|
22
|
+
line = self._fp.readline()
|
23
|
+
if self._regex_skip.match(line):
|
24
|
+
continue
|
25
|
+
return line
|
26
|
+
|
27
|
+
def __getattr__(self, k):
|
28
|
+
return getattr(self._fp, k)
|
29
|
+
|
30
|
+
def __iter__(self):
|
31
|
+
return self
|
32
|
+
|
33
|
+
def __next__(self):
|
34
|
+
line = self.readline()
|
35
|
+
if not line:
|
36
|
+
raise StopIteration
|
37
|
+
return line
|
38
|
+
|
39
|
+
def __enter__(self):
|
40
|
+
return self
|
41
|
+
|
42
|
+
def __exit__(self, *args, **kwargs):
|
43
|
+
return self._fp.__exit__(*args, **kwargs)
|
44
|
+
|
45
|
+
|
46
|
+
@BaseSheet.api
|
47
|
+
def open_text_source(sheet):
|
48
|
+
'Open sheet source as text, using sheet options for encoding and regex_skip.'
|
49
|
+
fp = sheet.source.open(encoding=sheet.options.encoding, encoding_errors=sheet.options.encoding_errors)
|
50
|
+
regex_skip = sheet.options.regex_skip
|
51
|
+
if regex_skip:
|
52
|
+
return FilterFile(fp, regex_skip, sheet.regex_flags())
|
53
|
+
return fp
|
visidata/textsheet.py
CHANGED
@@ -6,7 +6,7 @@ from visidata import globalCommand, VisiData
|
|
6
6
|
import visidata
|
7
7
|
|
8
8
|
|
9
|
-
vd.option('wrap', False, 'wrap text to fit window width on TextSheet')
|
9
|
+
vd.option('wrap', False, 'wrap text to fit window width on TextSheet', max_help=0)
|
10
10
|
vd.option('save_filetype', 'tsv', 'specify default file type to save as', replay=True)
|
11
11
|
|
12
12
|
|
@@ -32,7 +32,7 @@ class TextSheet(Sheet):
|
|
32
32
|
for i, L in enumerate(textwrap.wrap(str(text), width=winWidth)):
|
33
33
|
yield [startingLine+i+1, L]
|
34
34
|
else:
|
35
|
-
yield [startingLine+1, text]
|
35
|
+
yield [startingLine+1, text.strip()]
|
36
36
|
|
37
37
|
def sysopen(sheet, linenum=0):
|
38
38
|
@asyncthread
|
@@ -44,6 +44,7 @@ class TextSheet(Sheet):
|
|
44
44
|
|
45
45
|
import tempfile
|
46
46
|
with tempfile.NamedTemporaryFile() as temp:
|
47
|
+
temp.close() #2118
|
47
48
|
writelines(sheet, temp.name)
|
48
49
|
vd.launchEditor(temp.name, '+%s' % linenum)
|
49
50
|
sheet.rows = []
|
@@ -82,7 +83,7 @@ def recentErrorsSheet(self):
|
|
82
83
|
BaseSheet.addCommand('^E', 'error-recent', 'vd.lastErrors and vd.push(recentErrorsSheet) or status("no error")', 'view traceback for most recent error')
|
83
84
|
BaseSheet.addCommand('g^E', 'errors-all', 'vd.push(vd.allErrorsSheet)', 'view traceback for most recent errors')
|
84
85
|
|
85
|
-
Sheet.addCommand(None, 'view-cell', 'vd.push(ErrorSheet("%s[%s].%s" % (name, cursorRowIndex, cursorCol.name), sourceSheet=sheet, source=cursorDisplay.splitlines()))', 'view contents of current cell in a new sheet')
|
86
|
+
Sheet.addCommand(None, 'view-cell', 'vd.push(ErrorSheet("%s[%s].%s" % (name, cursorRowIndex, cursorCol.name), sourceSheet=sheet, source=cursorDisplay.splitlines()))', 'view contents of current cell in a new sheet')
|
86
87
|
Sheet.addCommand('z^E', 'error-cell', 'vd.push(ErrorSheet(sheet.name+"_cell_error", sourceSheet=sheet, source=getattr(cursorCell, "error", None) or fail("no error this cell")))', 'view traceback for error in current cell')
|
87
88
|
|
88
89
|
TextSheet.addCommand('^O', 'sysopen-sheet', 'sheet.sysopen(sheet.cursorRowIndex)', 'open copy of text sheet in $EDITOR and reload on exit')
|
@@ -91,3 +92,9 @@ TextSheet.addCommand('^O', 'sysopen-sheet', 'sheet.sysopen(sheet.cursorRowIndex)
|
|
91
92
|
TextSheet.options.save_filetype = 'txt'
|
92
93
|
|
93
94
|
vd.addGlobals({'TextSheet': TextSheet, 'ErrorSheet': ErrorSheet})
|
95
|
+
|
96
|
+
vd.addMenuItems('''
|
97
|
+
View > Errors > recent > error-recent
|
98
|
+
View > Errors > all > errors-all
|
99
|
+
View > Errors > in cell > error-cell
|
100
|
+
''')
|
visidata/theme.py
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
'Switch between packaged themes (colors and display characters)'
|
2
|
+
|
3
|
+
from visidata import vd, VisiData, Sheet, BaseSheet
|
4
|
+
|
5
|
+
|
6
|
+
vd.option('theme', '', 'display/color theme to use')
|
7
|
+
|
8
|
+
vd.themes = {}
|
9
|
+
|
10
|
+
|
11
|
+
@VisiData.before
|
12
|
+
def run(vd, *args, **kwargs):
|
13
|
+
t = vd.options.theme
|
14
|
+
if t:
|
15
|
+
vd.set_theme(t)
|
16
|
+
|
17
|
+
|
18
|
+
@Sheet.api
|
19
|
+
@VisiData.api
|
20
|
+
def set_theme(obj, theme=''):
|
21
|
+
if theme and theme not in vd.themes:
|
22
|
+
vd.warning(f'no "{theme}" theme available')
|
23
|
+
return
|
24
|
+
|
25
|
+
# unset everything first
|
26
|
+
for k in vd.options.keys():
|
27
|
+
if k.startswith(tuple('color_ disp_ note_'.split())):
|
28
|
+
obj.options.unset(k)
|
29
|
+
|
30
|
+
if not theme:
|
31
|
+
return
|
32
|
+
|
33
|
+
if isinstance(theme, str):
|
34
|
+
theme = vd.themes[theme]
|
35
|
+
|
36
|
+
for k, v in theme.items():
|
37
|
+
obj.options[k] = v
|
38
|
+
|
39
|
+
|
40
|
+
BaseSheet.addCommand('', 'theme-input', 'vd.set_theme(chooseOne([dict(key=k) for k in themes.keys()], type="theme"))', 'choose from available themes')
|
41
|
+
BaseSheet.addCommand('', 'theme-default', 'vd.set_theme()', 'reset theme to VisiData defaults')
|
42
|
+
|
43
|
+
vd.addMenuItem('View', 'Set theme', 'choose', 'theme-input')
|
44
|
+
vd.addMenuItem('View', 'Set theme', 'default', 'theme-default')
|
File without changes
|
@@ -0,0 +1,84 @@
|
|
1
|
+
'ASCII theme using the first 8 colors'
|
2
|
+
|
3
|
+
from visidata import vd
|
4
|
+
|
5
|
+
|
6
|
+
vd.themes['ascii8'] = dict(
|
7
|
+
disp_note_none='',
|
8
|
+
disp_truncator='>',
|
9
|
+
disp_oddspace='.',
|
10
|
+
disp_more_left='<',
|
11
|
+
disp_more_right='>',
|
12
|
+
disp_error_val='',
|
13
|
+
disp_ambig_width=1,
|
14
|
+
|
15
|
+
disp_pending='',
|
16
|
+
note_pending=':',
|
17
|
+
note_format_exc='?',
|
18
|
+
note_getter_exc='!',
|
19
|
+
note_type_exc='!',
|
20
|
+
|
21
|
+
color_note_pending='bold magenta',
|
22
|
+
color_note_type='yellow',
|
23
|
+
color_note_row='yellow',
|
24
|
+
|
25
|
+
disp_column_sep='|',
|
26
|
+
disp_keycol_sep='|',
|
27
|
+
disp_rowtop_sep='|',
|
28
|
+
disp_rowmid_sep='|',
|
29
|
+
disp_rowbot_sep='|',
|
30
|
+
disp_rowend_sep='|',
|
31
|
+
disp_keytop_sep='|',
|
32
|
+
disp_keymid_sep='|',
|
33
|
+
disp_keybot_sep='|',
|
34
|
+
disp_endtop_sep='|',
|
35
|
+
disp_endmid_sep='|',
|
36
|
+
disp_endbot_sep='|',
|
37
|
+
disp_selected_note='+',
|
38
|
+
disp_sort_asc='^^^^^^',
|
39
|
+
disp_sort_desc='vvvvvv',
|
40
|
+
color_default='white on black',
|
41
|
+
color_default_hdr='bold',
|
42
|
+
color_bottom_hdr='underline',
|
43
|
+
color_current_row='reverse',
|
44
|
+
color_current_col='bold',
|
45
|
+
color_current_hdr='bold reverse',
|
46
|
+
color_column_sep='blue',
|
47
|
+
color_key_col='cyan',
|
48
|
+
color_hidden_col='8',
|
49
|
+
color_selected_row='yellow',
|
50
|
+
color_edit_cell='white',
|
51
|
+
color_graph_hidden='blue',
|
52
|
+
color_graph_selected='bold',
|
53
|
+
color_status_replay='green',
|
54
|
+
|
55
|
+
color_graph_axis='bold',
|
56
|
+
color_sidebar='black on blue',
|
57
|
+
color_add_pending='green',
|
58
|
+
color_change_pending='reverse yellow',
|
59
|
+
color_delete_pending='red',
|
60
|
+
disp_rstatus_fmt=' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus} {sheet.options.disp_selected_note}{sheet.nSelectedRows}',
|
61
|
+
disp_status_fmt='{sheet.shortcut}> {sheet.name}| ',
|
62
|
+
disp_lstatus_max=0,
|
63
|
+
disp_status_sep=' | ',
|
64
|
+
color_keystrokes='bold black on cyan',
|
65
|
+
color_status='bold black on cyan',
|
66
|
+
color_error='red',
|
67
|
+
color_warning='yellow',
|
68
|
+
color_top_status='underline',
|
69
|
+
color_active_status='black on cyan',
|
70
|
+
color_inactive_status='8 on black',
|
71
|
+
color_working='green',
|
72
|
+
|
73
|
+
color_menu='black on cyan',
|
74
|
+
color_menu_active='yellow on black',
|
75
|
+
color_menu_spec='black on green',
|
76
|
+
color_menu_help='black on cyan',
|
77
|
+
disp_menu_boxchars='||-- ||',
|
78
|
+
disp_menu_more='>',
|
79
|
+
disp_menu_push='+',
|
80
|
+
disp_menu_input='_',
|
81
|
+
disp_menu_fmt='7-bit ASCII 3-bit color',
|
82
|
+
plot_colors = 'white',
|
83
|
+
disp_histogram='*'
|
84
|
+
)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
'ASCII monochrome theme; default colors only'
|
2
|
+
|
3
|
+
from visidata import vd
|
4
|
+
|
5
|
+
|
6
|
+
vd.themes['asciimono'] = dict(
|
7
|
+
disp_note_none='',
|
8
|
+
disp_truncator='>',
|
9
|
+
disp_oddspace='.',
|
10
|
+
disp_more_left='<',
|
11
|
+
disp_more_right='>',
|
12
|
+
disp_error_val='',
|
13
|
+
disp_ambig_width=1,
|
14
|
+
|
15
|
+
disp_pending='',
|
16
|
+
note_pending=':',
|
17
|
+
note_format_exc='?',
|
18
|
+
note_getter_exc='!',
|
19
|
+
note_type_exc='!',
|
20
|
+
|
21
|
+
color_note_pending='bold',
|
22
|
+
color_note_type='',
|
23
|
+
color_note_row='',
|
24
|
+
|
25
|
+
disp_column_sep='|',
|
26
|
+
disp_keycol_sep='|',
|
27
|
+
disp_rowtop_sep='|',
|
28
|
+
disp_rowmid_sep='|',
|
29
|
+
disp_rowbot_sep='|',
|
30
|
+
disp_rowend_sep='|',
|
31
|
+
disp_keytop_sep='|',
|
32
|
+
disp_keymid_sep='|',
|
33
|
+
disp_keybot_sep='|',
|
34
|
+
disp_endtop_sep='|',
|
35
|
+
disp_endmid_sep='|',
|
36
|
+
disp_endbot_sep='|',
|
37
|
+
disp_selected_note='+',
|
38
|
+
disp_sort_asc='^^^^^^',
|
39
|
+
disp_sort_desc='vvvvvv',
|
40
|
+
color_default='',
|
41
|
+
color_default_hdr='bold',
|
42
|
+
color_bottom_hdr='underline',
|
43
|
+
color_current_row='reverse',
|
44
|
+
color_current_col='bold',
|
45
|
+
color_current_hdr='bold reverse',
|
46
|
+
color_column_sep='',
|
47
|
+
color_key_col='bold',
|
48
|
+
color_hidden_col='8',
|
49
|
+
color_selected_row='',
|
50
|
+
color_edit_cell='',
|
51
|
+
color_graph_hidden='',
|
52
|
+
color_graph_selected='bold',
|
53
|
+
color_status_replay='',
|
54
|
+
|
55
|
+
color_graph_axis='bold',
|
56
|
+
color_sidebar='reverse',
|
57
|
+
color_change_pending='reverse',
|
58
|
+
color_delete_pending='underline',
|
59
|
+
|
60
|
+
disp_rstatus_fmt=' {sheet.longname} {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus} {sheet.options.disp_selected_note}{sheet.nSelectedRows}',
|
61
|
+
disp_status_fmt='{sheet.shortcut}> {sheet.name}| ',
|
62
|
+
disp_lstatus_max=0,
|
63
|
+
disp_status_sep=' | ',
|
64
|
+
color_keystrokes='bold reverse',
|
65
|
+
color_status='bold reverse',
|
66
|
+
color_error='bold',
|
67
|
+
color_warning='bold',
|
68
|
+
color_top_status='underline',
|
69
|
+
color_active_status='reverse',
|
70
|
+
color_inactive_status='8',
|
71
|
+
color_working='',
|
72
|
+
|
73
|
+
color_menu='reverse',
|
74
|
+
color_menu_active='',
|
75
|
+
color_menu_spec='reverse',
|
76
|
+
color_menu_help='reverse',
|
77
|
+
color_add_pending='',
|
78
|
+
disp_menu_boxchars='||-- ||',
|
79
|
+
disp_menu_more='>',
|
80
|
+
disp_menu_push='+',
|
81
|
+
disp_menu_input='_',
|
82
|
+
disp_menu_fmt='_.;"`\\ ASCII mono /\':._',
|
83
|
+
plot_colors = 'white',
|
84
|
+
)
|