visidata 2.11.1__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 +259 -42
- visidata/_open.py +84 -29
- visidata/_types.py +21 -3
- 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
- {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 +59 -50
- 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 +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 +30 -5
- 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} +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} +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 +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 +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/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 +301 -148
- visidata/man/vd.txt +290 -153
- 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 +50 -201
- 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 +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 +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.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/vd.1 +301 -148
- {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +301 -148
- visidata-3.0.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/METADATA +12 -8
- visidata-3.0.dist-info/RECORD +257 -0
- {visidata-2.11.1.dist-info → visidata-3.0.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.data}/scripts/vd +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
import os
|
2
|
+
import time
|
3
|
+
|
4
|
+
from visidata import vd, BaseSheet, Sheet, asyncthread, Path, ScopedSetattr
|
5
|
+
|
6
|
+
|
7
|
+
@BaseSheet.api
|
8
|
+
@asyncthread
|
9
|
+
def reload_every(sheet, seconds:int):
|
10
|
+
while True:
|
11
|
+
sheet.reload()
|
12
|
+
time.sleep(seconds)
|
13
|
+
|
14
|
+
|
15
|
+
@BaseSheet.api
|
16
|
+
@asyncthread
|
17
|
+
def reload_modified(sheet):
|
18
|
+
'Spawn thread to call sheet.reload_rows when sheet.source mtime has changed.'
|
19
|
+
p = sheet.source
|
20
|
+
assert isinstance(p, Path)
|
21
|
+
assert not p.is_url()
|
22
|
+
|
23
|
+
mtime = os.stat(p).st_mtime
|
24
|
+
while True:
|
25
|
+
time.sleep(1)
|
26
|
+
t = os.stat(p).st_mtime
|
27
|
+
if t != mtime:
|
28
|
+
mtime = t
|
29
|
+
sheet.reload_rows()
|
30
|
+
|
31
|
+
|
32
|
+
@Sheet.api
|
33
|
+
@asyncthread
|
34
|
+
def reload_rows(self):
|
35
|
+
'Reload rows from ``self.source``, keeping current columns intact. Async.'
|
36
|
+
with (ScopedSetattr(self, 'loading', True),
|
37
|
+
ScopedSetattr(self, 'checkCursor', lambda: True),
|
38
|
+
ScopedSetattr(self, 'cursorRowIndex', self.cursorRowIndex)):
|
39
|
+
self.beforeLoad()
|
40
|
+
try:
|
41
|
+
self.loader()
|
42
|
+
vd.status("finished loading rows")
|
43
|
+
finally:
|
44
|
+
self.afterLoad()
|
45
|
+
|
46
|
+
|
47
|
+
BaseSheet.addCommand('', 'reload-every', 'sheet.reload_every(input("reload interval (sec): ", value=1))', 'schedule sheet reload every N seconds') #683
|
48
|
+
BaseSheet.addCommand('', 'reload-modified', 'sheet.reload_modified()', 'reload sheet when source file modified (tail-like behavior)') #1686
|
49
|
+
BaseSheet.addCommand('z^R', 'reload-rows', 'preloadHook(); reload_rows(); status("reloaded")', 'Reload current sheet')
|
50
|
+
|
51
|
+
vd.addMenuItems('''
|
52
|
+
File > Reload > rows only > reload-rows
|
53
|
+
File > Reload > every N seconds > reload-every
|
54
|
+
File > Reload > when source modified > reload-modified
|
55
|
+
''')
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import ast
|
2
|
+
|
3
|
+
from visidata import vd, Column, ExprColumn, Sheet
|
4
|
+
|
5
|
+
vd.option('rename_cascade', False, 'cascade column renames into expressions') #2088
|
6
|
+
|
7
|
+
|
8
|
+
class Renamer(ast.NodeTransformer):
|
9
|
+
def __init__(self, find, replace):
|
10
|
+
self.find = find
|
11
|
+
self.replace = replace
|
12
|
+
|
13
|
+
def visit_Name(self, node):
|
14
|
+
if node.id == self.find:
|
15
|
+
node.id = self.replace
|
16
|
+
|
17
|
+
return node
|
18
|
+
|
19
|
+
|
20
|
+
@Column.before
|
21
|
+
def setName(col, newname):
|
22
|
+
if col.sheet and col.sheet.options.rename_cascade:
|
23
|
+
for c in col.sheet.columns:
|
24
|
+
if isinstance(c, ExprColumn):
|
25
|
+
parsed_expr = ast.parse(c.expr)
|
26
|
+
canon_expr = ast.unparse(parsed_expr)
|
27
|
+
new_expr = ast.unparse(Renamer(col.name, newname).visit(parsed_expr))
|
28
|
+
if new_expr != canon_expr:
|
29
|
+
vd.addUndo(setattr, c, 'expr', c.expr)
|
30
|
+
c.expr = new_expr
|
@@ -0,0 +1,60 @@
|
|
1
|
+
'''
|
2
|
+
Enables two functions:
|
3
|
+
1. options.disp_scroll_context: the minimum number of lines to keep visible above and below the cursor when scrolling
|
4
|
+
2. toggle-scrollfix: Lock the cursor to a particular row number in the view
|
5
|
+
|
6
|
+
Allows the user to fix the position on the page where they would like the
|
7
|
+
cursor to "stick". Helps to provide context about surrounding rows when
|
8
|
+
near the top and bottom of the page.
|
9
|
+
|
10
|
+
Usage: scroll a few lines down, `toggle-scrollfix' and scroll again!
|
11
|
+
|
12
|
+
NOTE:
|
13
|
+
- disables scroll-middle command (zz)
|
14
|
+
'''
|
15
|
+
|
16
|
+
__author__ = 'Geekscrapy'
|
17
|
+
__version__ = '1.2'
|
18
|
+
|
19
|
+
from visidata import vd, Sheet
|
20
|
+
|
21
|
+
vd.option('disp_scroll_context', 0, 'minimum number of lines to keep visible above/below cursor when scrolling')
|
22
|
+
vd.optalias('disp_scrolloff', 'disp_scroll_context')
|
23
|
+
|
24
|
+
@Sheet.after
|
25
|
+
def checkCursor(sheet):
|
26
|
+
nctx = sheet.options.disp_scroll_context
|
27
|
+
if nctx:
|
28
|
+
if nctx >= int(sheet.nScreenRows/2):
|
29
|
+
if sheet.cursorRowIndex+1 > int(sheet.nScreenRows/2):
|
30
|
+
sheet.topRowIndex = sheet.cursorRowIndex - int(sheet.nScreenRows/2)
|
31
|
+
else:
|
32
|
+
if sheet.cursorRowIndex-sheet.topRowIndex < nctx:
|
33
|
+
sheet.topRowIndex = max(0, sheet.cursorRowIndex-nctx)
|
34
|
+
|
35
|
+
if sheet.bottomRowIndex-sheet.cursorRowIndex < nctx:
|
36
|
+
sheet.bottomRowIndex = sheet.cursorRowIndex+nctx
|
37
|
+
|
38
|
+
nfix = getattr(sheet, 'disp_scrollfix', 0)
|
39
|
+
if nfix > 0:
|
40
|
+
cursorViewIndex = sheet.cursorRowIndex - sheet.topRowIndex
|
41
|
+
if cursorViewIndex > nfix:
|
42
|
+
sheet.topRowIndex += abs(nfix - cursorViewIndex)
|
43
|
+
if cursorViewIndex < nfix and sheet.topRowIndex > 0:
|
44
|
+
sheet.topRowIndex -= abs(nfix - cursorViewIndex)
|
45
|
+
|
46
|
+
|
47
|
+
@Sheet.api
|
48
|
+
def toggle_scrollfix(sheet):
|
49
|
+
if getattr(sheet, 'disp_scrollfix', -1) >= 0:
|
50
|
+
sheet.disp_scrollfix = -1
|
51
|
+
vd.status("cursor unlocked")
|
52
|
+
else:
|
53
|
+
sheet.disp_scrollfix = sheet.cursorRowIndex - sheet.topRowIndex
|
54
|
+
vd.status(f"cursor locked to screen row {sheet.disp_scrollfix}")
|
55
|
+
|
56
|
+
|
57
|
+
Sheet.addCommand('', 'toggle-scrollfix', 'toggle_scrollfix()', helpstr='toggle cursor lock to current screen row')
|
58
|
+
|
59
|
+
|
60
|
+
vd.addMenuItems('''View > Toggle display > lock cursor to screen row > toggle-scrollfix''')
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from visidata import Sheet, asyncthread, Progress
|
2
|
+
|
3
|
+
|
4
|
+
@Sheet.api
|
5
|
+
@asyncthread
|
6
|
+
def select_equal_selected(sheet, col):
|
7
|
+
selectedVals = set(col.getDisplayValue(row) for row in Progress(sheet.selectedRows))
|
8
|
+
sheet.select(sheet.gatherBy(lambda r,c=col,vals=selectedVals: c.getDisplayValue(r) in vals), progress=False)
|
9
|
+
|
10
|
+
|
11
|
+
Sheet.addCommand('', 'select-equal-selected', 'select_equal_selected(cursorCol)', 'select rows with values in current column in already selected rows')
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# to anonymize a column in vd: do "setcol-fake" with e.g. 'name' 'isbn10' or any of the functions on Faker()
|
2
|
+
|
3
|
+
import json
|
4
|
+
|
5
|
+
from visidata import vd, Column, Sheet, asyncthread, Progress, VisiData
|
6
|
+
|
7
|
+
vd.option('faker_locale', 'en_US', 'default locale to use for Faker', replay=True)
|
8
|
+
vd.option('faker_extra_providers', None, 'list of additional Provider classes to load via add_provider()', replay=True)
|
9
|
+
vd.option('faker_salt', '', 'Use a non-empty string to enable deterministic fakes')
|
10
|
+
|
11
|
+
def addFakerProviders(fake, providers):
|
12
|
+
'''
|
13
|
+
Add custom providers to Faker. Provider classes typically derive from
|
14
|
+
faker.providers.BaseProvider, so check for that here. This helps to
|
15
|
+
highlight likely misconfigurations instead of hiding them.
|
16
|
+
|
17
|
+
See also: https://faker.readthedocs.io/en/master/communityproviders.html
|
18
|
+
|
19
|
+
fake: Faker object
|
20
|
+
providers: List of provider classes to add
|
21
|
+
'''
|
22
|
+
faker = vd.importExternal('faker', 'Faker')
|
23
|
+
if isinstance(providers, str):
|
24
|
+
providers = [ getattr(faker.providers, p) for p in providers.split() ]
|
25
|
+
|
26
|
+
if not isinstance(providers, list):
|
27
|
+
vd.fail('options.faker_extra_providers must be a list')
|
28
|
+
|
29
|
+
for provider in providers:
|
30
|
+
if not issubclass(provider, faker.providers.BaseProvider):
|
31
|
+
vd.warning('"{}" not a Faker Provider'.format(provider.__name__))
|
32
|
+
continue
|
33
|
+
fake.add_provider(provider)
|
34
|
+
|
35
|
+
@Column.api
|
36
|
+
@asyncthread
|
37
|
+
def setValuesFromFaker(col, faketype, rows):
|
38
|
+
faker = vd.importExternal('faker', 'Faker')
|
39
|
+
fake = faker.Faker(col.sheet.options.faker_locale)
|
40
|
+
if col.sheet.options.faker_extra_providers:
|
41
|
+
addFakerProviders(fake, col.sheet.options.faker_extra_providers)
|
42
|
+
fakefunc = getattr(fake, faketype, None) or vd.fail(f'no such faker "{faketype}"')
|
43
|
+
|
44
|
+
fakeMap = {}
|
45
|
+
fakeMap[None] = None
|
46
|
+
fakeMap[col.sheet.options.null_value] = col.sheet.options.null_value
|
47
|
+
|
48
|
+
vd.addUndoSetValues([col], rows)
|
49
|
+
salt = col.sheet.options.faker_salt
|
50
|
+
|
51
|
+
for r in Progress(rows):
|
52
|
+
v = col.getValue(r)
|
53
|
+
if v in fakeMap:
|
54
|
+
newv = fakeMap[v]
|
55
|
+
else:
|
56
|
+
if salt:
|
57
|
+
# Reset the Faker seed for each value. For a given salt string,
|
58
|
+
# the same cell value will always generate the same fake value.
|
59
|
+
fake.seed_instance(json.dumps(v) + salt)
|
60
|
+
newv = fakefunc()
|
61
|
+
fakeMap[v] = newv
|
62
|
+
col.setValue(r, newv)
|
63
|
+
|
64
|
+
|
65
|
+
Sheet.addCommand(None, 'setcol-fake', 'cursorCol.setValuesFromFaker(input("faketype: ", type="faketype"), selectedRows)', 'replace values in current column for selected rows with fake values')
|
@@ -1,6 +1,7 @@
|
|
1
1
|
'''slide rows/columns around'''
|
2
2
|
|
3
|
-
|
3
|
+
import visidata
|
4
|
+
from visidata import Sheet, moveListItem, vd
|
4
5
|
|
5
6
|
@Sheet.api
|
6
7
|
def slide_col(sheet, colidx, newcolidx):
|
@@ -19,26 +20,6 @@ def slide_row(sheet, rowidx, newcolidx):
|
|
19
20
|
return moveListItem(sheet.rows, rowidx, newcolidx)
|
20
21
|
|
21
22
|
|
22
|
-
@Sheet.api
|
23
|
-
def onClick(sheet, vcolidx, rowidx):
|
24
|
-
pass
|
25
|
-
|
26
|
-
@Sheet.api
|
27
|
-
def onRelease(sheet, vcolidx, rowidx, destx, desty):
|
28
|
-
newvcolidx = sheet.visibleColAtX(destx)
|
29
|
-
newrowidx = sheet.visibleRowAtY(desty)
|
30
|
-
|
31
|
-
if newvcolidx is not None and newvcolidx != vcolidx:
|
32
|
-
sheet.cursorVisibleColIndex = sheet.slide_col(vcolidx, newvcolidx)
|
33
|
-
|
34
|
-
# else: only move row if within same column (if column not moved above)
|
35
|
-
elif newrowidx is not None and newrowidx != rowidx:
|
36
|
-
sheet.cursorRowIndex = sheet.slide_row(rowidx, newrowidx)
|
37
|
-
|
38
|
-
else:
|
39
|
-
sheet.onClick(vcolidx, rowidx)
|
40
|
-
|
41
|
-
|
42
23
|
def moveKeyCol(sheet, fromKeyColIdx, toKeyColIdx):
|
43
24
|
'Move key column to another key column position in sheet.'
|
44
25
|
if not (1 <= toKeyColIdx <= len(sheet.keyCols)):
|
@@ -101,3 +82,76 @@ Sheet.bindkey('gKEY_SLEFT', 'slide-leftmost')
|
|
101
82
|
Sheet.bindkey('gkDN', 'slide-bottom')
|
102
83
|
Sheet.bindkey('gkUP', 'slide-top')
|
103
84
|
Sheet.bindkey('gKEY_SRIGHT', 'slide-rightmost')
|
85
|
+
|
86
|
+
vd.addMenuItems('''
|
87
|
+
Edit > Slide > Row > up > slide-up
|
88
|
+
Edit > Slide > Row > up N > slide-up-n
|
89
|
+
Edit > Slide > Row > down > slide-down
|
90
|
+
Edit > Slide > Row > down N > slide-down-n
|
91
|
+
Edit > Slide > Row > to top > slide-top
|
92
|
+
Edit > Slide > Row > to bottom > slide-bottom
|
93
|
+
Edit > Slide > Column > left > slide-left
|
94
|
+
Edit > Slide > Column > left N > slide-left-n
|
95
|
+
Edit > Slide > Column > leftmost > slide-leftmost
|
96
|
+
Edit > Slide > Column > right > slide-right
|
97
|
+
Edit > Slide > Column > right N > slide-right-n
|
98
|
+
Edit > Slide > Column > rightmost > slide-rightmost
|
99
|
+
''')
|
100
|
+
|
101
|
+
## tests
|
102
|
+
|
103
|
+
|
104
|
+
def make_tester(setup_vdx):
|
105
|
+
def t(vdx, golden):
|
106
|
+
global vd
|
107
|
+
vd = visidata.vd.resetVisiData()
|
108
|
+
vd.runvdx(setup_vdx)
|
109
|
+
|
110
|
+
vd.runvdx(vdx)
|
111
|
+
colnames = [c.name for c in vd.sheet.visibleCols]
|
112
|
+
assert colnames == golden.split(), ' '.join(colnames)
|
113
|
+
|
114
|
+
return t
|
115
|
+
|
116
|
+
def test_slide_keycol_1(vd):
|
117
|
+
t = make_tester('''
|
118
|
+
open-file sample_data/sample.tsv
|
119
|
+
+::OrderDate key-col
|
120
|
+
+::Region key-col
|
121
|
+
+::Rep key-col
|
122
|
+
''')
|
123
|
+
|
124
|
+
t('', 'OrderDate Region Rep Item Units Unit_Cost Total')
|
125
|
+
t('+::Rep slide-leftmost', 'Rep OrderDate Region Item Units Unit_Cost Total')
|
126
|
+
t('+::OrderDate slide-rightmost', 'Region Rep OrderDate Item Units Unit_Cost Total')
|
127
|
+
t('+::Rep slide-left', 'OrderDate Rep Region Item Units Unit_Cost Total')
|
128
|
+
t('+::OrderDate slide-right', 'Region OrderDate Rep Item Units Unit_Cost Total')
|
129
|
+
|
130
|
+
t('''
|
131
|
+
+::Item key-col
|
132
|
+
+::Item slide-left
|
133
|
+
slide-left
|
134
|
+
slide-right
|
135
|
+
slide-right
|
136
|
+
slide-left
|
137
|
+
slide-left
|
138
|
+
''', 'OrderDate Item Region Rep Units Unit_Cost Total')
|
139
|
+
|
140
|
+
|
141
|
+
def test_slide_leftmost(vd):
|
142
|
+
t = make_tester('''open-file sample_data/benchmark.csv''')
|
143
|
+
|
144
|
+
t('+::Paid slide-leftmost', 'Paid Date Customer SKU Item Quantity Unit')
|
145
|
+
|
146
|
+
t = make_tester('''
|
147
|
+
open-file sample_data/benchmark.csv
|
148
|
+
+::Date key-col
|
149
|
+
''')
|
150
|
+
|
151
|
+
t('', 'Date Customer SKU Item Quantity Unit Paid')
|
152
|
+
t('''+::Item slide-leftmost''', 'Date Item Customer SKU Quantity Unit Paid')
|
153
|
+
t('''+::SKU key-col
|
154
|
+
+::Quantity slide-leftmost''', 'Date SKU Quantity Customer Item Unit Paid')
|
155
|
+
t('''+::Date slide-leftmost''', 'Date Customer SKU Item Quantity Unit Paid')
|
156
|
+
t('''+::Item slide-leftmost
|
157
|
+
+::SKU slide-leftmost''', 'Date SKU Item Customer Quantity Unit Paid')
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"""
|
2
|
+
Generate sparkline column for numeric columns
|
3
|
+
"""
|
4
|
+
|
5
|
+
from visidata import vd, Column, Sheet
|
6
|
+
|
7
|
+
__author__ = 'Lucas Messenger @layertwo'
|
8
|
+
|
9
|
+
vd.theme_option('disp_sparkline', '▁▂▃▄▅▆▇', 'characters to display sparkline')
|
10
|
+
|
11
|
+
|
12
|
+
def sparkline(*values):
|
13
|
+
"""
|
14
|
+
From *values generate string sparkline
|
15
|
+
"""
|
16
|
+
lines = vd.options.disp_sparkline
|
17
|
+
values = [v for v in values if isinstance(v, (int, float))]
|
18
|
+
mx = max(values)
|
19
|
+
mn = min(values)
|
20
|
+
w = (mx - mn) / len(lines)
|
21
|
+
bounds = [(mn + w * i) for i in range(len(lines))]
|
22
|
+
|
23
|
+
output = ''
|
24
|
+
for val in values:
|
25
|
+
for b in bounds:
|
26
|
+
if mn == 0 and val == 0:
|
27
|
+
output += ' '
|
28
|
+
break
|
29
|
+
if val < b:
|
30
|
+
output += lines[bounds.index(b) - 1]
|
31
|
+
break
|
32
|
+
else:
|
33
|
+
output += max(lines)
|
34
|
+
return output
|
35
|
+
|
36
|
+
|
37
|
+
@Sheet.api
|
38
|
+
def addcol_sparkline(sheet, sourceCols):
|
39
|
+
"""
|
40
|
+
Add sparkline column
|
41
|
+
"""
|
42
|
+
c = Column('sparkline',
|
43
|
+
sourceCols=sourceCols,
|
44
|
+
getter=lambda c,r: sparkline(*tuple(c.getTypedValue(r) for c in c.sheet.sourceCols)))
|
45
|
+
sheet.addColumn(c)
|
46
|
+
|
47
|
+
|
48
|
+
Sheet.addCommand(None, 'addcol-sparkline', 'addcol_sparkline(numericCols(nonKeyVisibleCols))', 'add sparkline of all numeric columns')
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import inspect
|
2
|
+
|
3
|
+
import visidata
|
4
|
+
from visidata import VisiData
|
5
|
+
|
6
|
+
@VisiData.api
|
7
|
+
def getStatusSource(vd):
|
8
|
+
stack = inspect.stack()
|
9
|
+
for i, sf in enumerate(stack):
|
10
|
+
if sf.function in 'status aside'.split():
|
11
|
+
if stack[i+1].function in 'error fail warning debug'.split():
|
12
|
+
sf = stack[i+2]
|
13
|
+
else:
|
14
|
+
sf = stack[i+1]
|
15
|
+
break
|
16
|
+
|
17
|
+
fn = sf.filename
|
18
|
+
if fn.startswith(visidata.__path__[0]):
|
19
|
+
fn = visidata.__package__ + fn[len(visidata.__path__[0]):]
|
20
|
+
return f'{fn}:{sf.lineno}:{sf.function}'
|
@@ -17,7 +17,8 @@ def syseditCells_async(sheet, cols, rows, filetype=None):
|
|
17
17
|
|
18
18
|
import tempfile
|
19
19
|
with tempfile.NamedTemporaryFile() as temp:
|
20
|
-
|
20
|
+
temp.close() #2118
|
21
|
+
p = Path(temp.name)
|
21
22
|
|
22
23
|
vd.status(f'copying {vs.nRows} {vs.rowtype} to {p} as {filetype}')
|
23
24
|
vd.sync(vd.saveSheets(p, vs))
|
@@ -0,0 +1,46 @@
|
|
1
|
+
''' Plugin for viewing files with appropriate mailcap-specified application.
|
2
|
+
Add mailcap-view and mailcap-view-selected commands to DirSheet.
|
3
|
+
|
4
|
+
mimetype can be given explicitly with `mimetype` option; will be guessed by filename otherwise.
|
5
|
+
|
6
|
+
Usage:
|
7
|
+
- add `import experimental.mailcap_view` to .visidatarc
|
8
|
+
- on the DirSheet, `Ctrl+V` or `gCtrl+V` to view file(s) using mailcap entry for the guessed (or given via options) mimetype
|
9
|
+
'''
|
10
|
+
|
11
|
+
import os
|
12
|
+
from visidata import vd, DirSheet, SuspendCurses
|
13
|
+
|
14
|
+
vd.option('mailcap_mimetype', '', 'force mimetype for sysopen-mailcap')
|
15
|
+
vd.optalias('mimetype', 'mailcap_mimetype')
|
16
|
+
|
17
|
+
|
18
|
+
@DirSheet.api
|
19
|
+
def run_mailcap(sheet, p, key='view'):
|
20
|
+
import mailcap
|
21
|
+
import mimetypes
|
22
|
+
|
23
|
+
mimetype = sheet.options.mailcap_mimetype
|
24
|
+
if not mimetype:
|
25
|
+
mimetype, encoding = mimetypes.guess_type(str(p))
|
26
|
+
|
27
|
+
if not mimetype:
|
28
|
+
vd.fail('no mimetype given and no guess')
|
29
|
+
|
30
|
+
caps = mailcap.getcaps()
|
31
|
+
|
32
|
+
plist = [f'{k}={v}' for k, v in sheet.options.getall('mailcap_').items() if k != 'mailcap_mimetype']
|
33
|
+
cmdline, mcap_entry = mailcap.findmatch(caps, mimetype, key=key, filename=str(p), plist=plist)
|
34
|
+
|
35
|
+
with SuspendCurses():
|
36
|
+
os.system(cmdline)
|
37
|
+
|
38
|
+
|
39
|
+
DirSheet.addCommand('', 'sysopen-mailcap', 'run_mailcap(cursorRow)', 'open using mailcap entry for current row, guessing mimetype')
|
40
|
+
DirSheet.addCommand('', 'sysopen-mailcap-selected', 'for r in selectedRows: run_mailcap(r)', 'open selected files in succession, using mailcap')
|
41
|
+
|
42
|
+
|
43
|
+
vd.addMenuItems('''
|
44
|
+
File > Open > using mailcap > file at cursor > sysopen-mailcap
|
45
|
+
File > Open > using mailcap > selected files > sysopen-mailcap-selected
|
46
|
+
''')
|
@@ -1,10 +1,9 @@
|
|
1
|
-
from visidata import VisiData, Sheet, asyncthread, Progress, Column
|
1
|
+
from visidata import vd, VisiData, Sheet, asyncthread, Progress, Column
|
2
2
|
|
3
3
|
# rowdef: Column
|
4
4
|
@VisiData.api
|
5
5
|
class TransposeSheet(Sheet):
|
6
|
-
|
7
|
-
def reload(self):
|
6
|
+
def beforeLoad(self):
|
8
7
|
# key rows become column names
|
9
8
|
col = Column('_'.join(c.name for c in self.source.keyCols),
|
10
9
|
getter=lambda c,origcol: origcol.name)
|
@@ -13,7 +12,7 @@ class TransposeSheet(Sheet):
|
|
13
12
|
self.columns = [col]
|
14
13
|
self.setKeys(self.columns)
|
15
14
|
|
16
|
-
|
15
|
+
def loader(self):
|
17
16
|
# rows become columns
|
18
17
|
for row in Progress(self.source.rows, 'transposing'):
|
19
18
|
self.addColumn(Column('_'.join(map(str, self.source.rowkey(row))),
|
@@ -23,3 +22,5 @@ class TransposeSheet(Sheet):
|
|
23
22
|
self.rows = list(self.source.nonKeyVisibleCols)
|
24
23
|
|
25
24
|
Sheet.addCommand('T', 'transpose', 'vd.push(TransposeSheet(name+"_T", source=sheet))', 'open new sheet with rows and columns transposed')
|
25
|
+
|
26
|
+
vd.addMenuItems('Data > Transpose > transpose')
|
@@ -0,0 +1,73 @@
|
|
1
|
+
"""
|
2
|
+
Column types and utility commands related to IP addresses.
|
3
|
+
"""
|
4
|
+
from ipaddress import ip_address, ip_network, _BaseNetwork
|
5
|
+
|
6
|
+
from visidata import vd
|
7
|
+
from visidata.sheets import Column, TableSheet
|
8
|
+
|
9
|
+
|
10
|
+
vd.addType(ip_address, icon=":", formatter=lambda fmt, ip: str(ip))
|
11
|
+
vd.addType(ip_network, icon="/", formatter=lambda fmt, ip: str(ip))
|
12
|
+
|
13
|
+
|
14
|
+
def isSupernet(cell, network, isNull):
|
15
|
+
"""Is `cell` a supernet of `network`?
|
16
|
+
|
17
|
+
Treat nulls as false, and perform conversions to IP network objects only
|
18
|
+
if necessary.
|
19
|
+
"""
|
20
|
+
if isNull(cell):
|
21
|
+
return False
|
22
|
+
if not isinstance(cell, _BaseNetwork):
|
23
|
+
try:
|
24
|
+
cell = ip_network(str(cell).strip())
|
25
|
+
except ValueError:
|
26
|
+
return False
|
27
|
+
return cell.supernet_of(network)
|
28
|
+
|
29
|
+
|
30
|
+
@Column.api
|
31
|
+
def selectSupernets(col, ip):
|
32
|
+
"""Select rows based on network containment
|
33
|
+
|
34
|
+
Given an IP address (e.g. 10.0.0.0) or network (e.g. 10.0.0.0/8) as input,
|
35
|
+
select rows whose network address space completely contains the input network.
|
36
|
+
"""
|
37
|
+
if not ip:
|
38
|
+
return
|
39
|
+
|
40
|
+
sheet = col.sheet
|
41
|
+
network = ip_network(ip.strip())
|
42
|
+
isNull = sheet.isNullFunc()
|
43
|
+
|
44
|
+
vd.status(f'selecting rows where {col.name} is a supernet of "{str(network)}"')
|
45
|
+
sheet.select(
|
46
|
+
[
|
47
|
+
row
|
48
|
+
for row in sheet.rows
|
49
|
+
if isSupernet(col.getTypedValue(row), network, isNull)
|
50
|
+
]
|
51
|
+
)
|
52
|
+
|
53
|
+
|
54
|
+
TableSheet.addCommand(
|
55
|
+
None,
|
56
|
+
"type-ipaddr",
|
57
|
+
"cursorCol.type=ip_address",
|
58
|
+
"set type of current column to IP address",
|
59
|
+
)
|
60
|
+
TableSheet.addCommand(
|
61
|
+
None,
|
62
|
+
"type-ipnet",
|
63
|
+
"cursorCol.type=ip_network",
|
64
|
+
"set type of current column to IP network",
|
65
|
+
)
|
66
|
+
TableSheet.addCommand(
|
67
|
+
None,
|
68
|
+
"select-supernets",
|
69
|
+
'cursorCol.selectSupernets(input("ip or cidr block: "))',
|
70
|
+
"select rows where the CIDR block value includes the input address space",
|
71
|
+
)
|
72
|
+
|
73
|
+
vd.addGlobals(ip_address=ip_address, ip_network=ip_network)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from visidata import Sheet, Column, DisplayWrapper
|
2
|
+
|
3
|
+
|
4
|
+
@Column.api
|
5
|
+
def displayer_url(self, dw:DisplayWrapper, width=None):
|
6
|
+
'Display cell text as clickable url'
|
7
|
+
yield ('onclick '+dw.text, dw.text)
|
8
|
+
|
9
|
+
|
10
|
+
Sheet.addCommand("", "type-url", "sheet.cursorCol.displayer = 'url'", "set column to open URLs in $BROWSER on mouse click")
|
11
|
+
Sheet.addCommand("", "open-url", "vd.launchBrowser(sheet.cursorValue)", "open current cursor value in $BROWSER")
|
@@ -7,7 +7,7 @@ Credit to Jeremy Singer-Vine for the idea and original implementation.
|
|
7
7
|
'''
|
8
8
|
|
9
9
|
from collections.abc import Iterable, Mapping
|
10
|
-
from visidata import vd, Progress, Sheet, Column, ColumnItem, SettableColumn, SubColumnFunc, asyncthread
|
10
|
+
from visidata import vd, Progress, Sheet, Column, ColumnItem, SettableColumn, SubColumnFunc, asyncthread
|
11
11
|
from visidata import stacktrace, TypedExceptionWrapper
|
12
12
|
|
13
13
|
|
@@ -16,8 +16,7 @@ vd.option('unfurl_empty', False, 'if unfurl includes rows for empty containers',
|
|
16
16
|
|
17
17
|
class UnfurledSheet(Sheet):
|
18
18
|
# rowdef: [row, key, sub_value]
|
19
|
-
|
20
|
-
def reload(self):
|
19
|
+
def resetCols(self):
|
21
20
|
# Copy over base sheet, using SubColumnFunc
|
22
21
|
self.columns = []
|
23
22
|
for col in self.source.columns:
|
@@ -29,7 +28,7 @@ class UnfurledSheet(Sheet):
|
|
29
28
|
else:
|
30
29
|
self.addColumn(SubColumnFunc(col.name, col, 0, keycol=col.keycol))
|
31
30
|
|
32
|
-
|
31
|
+
def iterload(self):
|
33
32
|
unfurl_empty = self.options.unfurl_empty
|
34
33
|
for row in Progress(self.source.rows):
|
35
34
|
try:
|
@@ -39,7 +38,7 @@ class UnfurledSheet(Sheet):
|
|
39
38
|
if unfurl_empty:
|
40
39
|
# TypedExceptionWrapper allows the use of z^E to see the stacktrace
|
41
40
|
# the exception on its own lacks clarity
|
42
|
-
|
41
|
+
yield [row, TypedExceptionWrapper(None, exception=e), TypedExceptionWrapper(None, exception=e)]
|
43
42
|
else:
|
44
43
|
vd.exceptionCaught(e)
|
45
44
|
continue
|
@@ -54,17 +53,18 @@ class UnfurledSheet(Sheet):
|
|
54
53
|
|
55
54
|
nadded = 0
|
56
55
|
for key, sub_value in gen:
|
57
|
-
|
58
|
-
self.addRow(new_row)
|
56
|
+
yield [ row, key, sub_value ]
|
59
57
|
nadded += 1
|
60
58
|
|
61
59
|
if unfurl_empty and not nadded:
|
62
|
-
|
60
|
+
yield [row, None, None]
|
63
61
|
|
64
62
|
|
65
63
|
@Sheet.api
|
66
64
|
def unfurl_col(sheet, col):
|
67
|
-
return UnfurledSheet(sheet.name,
|
65
|
+
return UnfurledSheet(sheet.name, vd.cleanName(col.name), 'unfurled', source=sheet, source_col=col)
|
68
66
|
|
69
67
|
|
70
68
|
Sheet.addCommand("zM", "unfurl-col", "vd.push(unfurl_col(cursorCol))", "row-wise expand current column of lists (e.g. [2]) or dicts (e.g. {3}) within that column")
|
69
|
+
|
70
|
+
vd.addMenuItems('Data > Unfurl column > unfurl-col')
|
@@ -47,10 +47,10 @@ def addcol_window(sheet, curcol):
|
|
47
47
|
|
48
48
|
@Sheet.api
|
49
49
|
def select_around(sheet, n):
|
50
|
-
sheet.select(list(itertools.chain(*(winrows for row, winrows in sheet.window(n, n) if sheet.isSelected(row)))))
|
50
|
+
sheet.select(list(itertools.chain(*(winrows for row, winrows in sheet.window(int(n), int(n)) if sheet.isSelected(row)))))
|
51
51
|
|
52
52
|
|
53
53
|
Sheet.addCommand('w', 'addcol-window', 'addcol_window(cursorCol)', 'add column where each row contains a list of that row, nBefore rows, and nAfter rows')
|
54
|
-
Sheet.addCommand('', 'select-around-n', 'select_around(input("select rows around selected: ", value=1))')
|
54
|
+
Sheet.addCommand('', 'select-around-n', 'select_around(input("select rows around selected: ", value=1))', 'select additional N rows before/after each selected row')
|
55
55
|
|
56
56
|
vd.addMenuItem('Row', 'Select', 'N rows around each selected row', 'select-around-n')
|