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/loaders/ttf.py
CHANGED
@@ -3,7 +3,7 @@ from visidata import VisiData, vd, Sheet, Column, Progress, ColumnAttr, ColumnIt
|
|
3
3
|
|
4
4
|
@VisiData.api
|
5
5
|
def open_ttf(vd, p):
|
6
|
-
return TTFTablesSheet(p.
|
6
|
+
return TTFTablesSheet(p.base_stem, source=p)
|
7
7
|
|
8
8
|
vd.open_otf = vd.open_ttf
|
9
9
|
|
@@ -24,9 +24,9 @@ class TTFTablesSheet(Sheet):
|
|
24
24
|
return TTFGlyphsSheet(self.name+'_glyphs', source=self, sourceRows=[row], ttf=self.ttf)
|
25
25
|
|
26
26
|
def iterload(self):
|
27
|
-
|
27
|
+
fontTools = vd.importExternal('fontTools.ttLib', 'fonttools')
|
28
28
|
|
29
|
-
self.ttf = fontTools.
|
29
|
+
self.ttf = fontTools.TTFont(str(self.source), 0, allowVID=0, ignoreDecompileErrors=True, fontNumber=-1)
|
30
30
|
for cmap in self.ttf["cmap"].tables:
|
31
31
|
yield cmap
|
32
32
|
|
@@ -53,20 +53,21 @@ class TTFGlyphsSheet(Sheet):
|
|
53
53
|
|
54
54
|
|
55
55
|
def makePen(*args, **kwargs):
|
56
|
-
|
57
|
-
|
58
|
-
except ImportError as e:
|
59
|
-
vd.error('fonttools not installed')
|
56
|
+
fontTools = vd.importExternal('fontTools', 'fonttools')
|
57
|
+
from fontTools.pens.basePen import BasePen
|
60
58
|
|
61
59
|
class GlyphPen(InvertedCanvas, BasePen):
|
62
60
|
aspectRatio = 1.0
|
63
61
|
def __init__(self, name, **kwargs):
|
64
62
|
super().__init__(name, **kwargs)
|
63
|
+
self.path_firstxy = None
|
65
64
|
self.lastxy = None
|
66
65
|
self.attr = self.plotColor(('glyph',))
|
67
66
|
|
68
67
|
def _moveTo(self, xy):
|
69
68
|
self.lastxy = xy
|
69
|
+
if self.path_firstxy is None:
|
70
|
+
self.path_firstxy = xy
|
70
71
|
|
71
72
|
def _lineTo(self, xy):
|
72
73
|
x1, y1 = self.lastxy
|
@@ -74,6 +75,17 @@ def makePen(*args, **kwargs):
|
|
74
75
|
self.line(x1, y1, x2, y2, self.attr)
|
75
76
|
self._moveTo(xy)
|
76
77
|
|
78
|
+
def _closePath(self):
|
79
|
+
if self.path_firstxy:
|
80
|
+
if (self.path_firstxy != self.lastxy):
|
81
|
+
self._lineTo(self.path_firstxy)
|
82
|
+
self.path_firstxy = None
|
83
|
+
self.lastxy = None
|
84
|
+
|
85
|
+
def _endPath(self):
|
86
|
+
self.path_firstxy = None
|
87
|
+
self.lastxy = None
|
88
|
+
|
77
89
|
def _curveToOne(self, xy1, xy2, xy3):
|
78
90
|
vd.error('NotImplemented')
|
79
91
|
|
visidata/loaders/unzip_http.py
CHANGED
@@ -26,6 +26,7 @@ import struct
|
|
26
26
|
import fnmatch
|
27
27
|
import pathlib
|
28
28
|
import urllib.parse
|
29
|
+
from visidata import vd
|
29
30
|
|
30
31
|
|
31
32
|
__version__ = '0.5.1'
|
@@ -98,7 +99,7 @@ class RemoteZipFile:
|
|
98
99
|
magic_eocd = b'\x50\x4b\x05\x06'
|
99
100
|
|
100
101
|
def __init__(self, url):
|
101
|
-
|
102
|
+
urllib3 = vd.importExternal('urllib3')
|
102
103
|
self.url = url
|
103
104
|
self.http = urllib3.PoolManager()
|
104
105
|
self.zip_size = 0
|
@@ -185,7 +186,7 @@ class RemoteZipFile:
|
|
185
186
|
|
186
187
|
outpath = path/member
|
187
188
|
os.makedirs(outpath.parent, exist_ok=True)
|
188
|
-
with self.
|
189
|
+
with self._open(member) as fpin:
|
189
190
|
with open(path/member, mode='wb') as fpout:
|
190
191
|
while True:
|
191
192
|
r = fpin.read(65536)
|
@@ -205,7 +206,7 @@ class RemoteZipFile:
|
|
205
206
|
if any(fnmatch.fnmatch(f.filename, g) for g in globs):
|
206
207
|
yield f
|
207
208
|
|
208
|
-
def
|
209
|
+
def _open(self, fn):
|
209
210
|
if isinstance(fn, str):
|
210
211
|
f = list(self.matching_files(fn))
|
211
212
|
if not f:
|
@@ -226,8 +227,8 @@ class RemoteZipFile:
|
|
226
227
|
else:
|
227
228
|
error(f'unknown compression method {method}')
|
228
229
|
|
229
|
-
def
|
230
|
-
return io.TextIOWrapper(self.
|
230
|
+
def open(self, fn):
|
231
|
+
return io.TextIOWrapper(self._open(fn))
|
231
232
|
|
232
233
|
|
233
234
|
class RemoteZipStream(io.RawIOBase):
|
visidata/loaders/usv.py
CHANGED
@@ -4,7 +4,7 @@ from visidata import Sheet, TsvSheet, options, vd, VisiData
|
|
4
4
|
|
5
5
|
@VisiData.api
|
6
6
|
def open_usv(vd, p):
|
7
|
-
return TsvSheet(p.
|
7
|
+
return TsvSheet(p.base_stem, source=p, delimiter='\u241f', row_delimiter='\u241e')
|
8
8
|
|
9
9
|
@VisiData.api
|
10
10
|
def save_usv(vd, p, vs):
|
visidata/loaders/vcf.py
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
from visidata import VisiData, Column, getitemdef, PythonSheet, asyncthread
|
1
|
+
from visidata import VisiData, Column, getitemdef, PythonSheet, asyncthread, vd
|
2
2
|
|
3
3
|
|
4
4
|
# requires (deb): libbz2-dev libcurl4-openssl-dev liblzma-dev
|
5
5
|
|
6
6
|
@VisiData.api
|
7
7
|
def open_vcf(vd, p):
|
8
|
-
return VcfSheet(p.
|
8
|
+
return VcfSheet(p.base_stem, source=p)
|
9
9
|
|
10
10
|
def unbox(col, row):
|
11
11
|
v = getitemdef(row, col.expr)
|
@@ -20,22 +20,22 @@ class VcfSheet(PythonSheet):
|
|
20
20
|
rowtype = 'cards'
|
21
21
|
@asyncthread
|
22
22
|
def reload(self):
|
23
|
-
|
23
|
+
vobject = vd.importExternal('vobject')
|
24
24
|
self.rows = []
|
25
25
|
self.columns = []
|
26
26
|
|
27
27
|
addedCols = set()
|
28
28
|
lines = []
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
addedCols
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
29
|
+
with self.open_text_source() as fp:
|
30
|
+
for line in fp:
|
31
|
+
lines.append(line)
|
32
|
+
if line.startswith('END:'):
|
33
|
+
row = vobject.readOne('\n'.join(lines))
|
34
|
+
for k, v in row.contents.items():
|
35
|
+
if v and str(v[0].value).startswith('(None)'):
|
36
|
+
continue
|
37
|
+
if not k in addedCols:
|
38
|
+
addedCols.add(k)
|
39
|
+
self.addColumn(Column(k, expr=k, getter=unbox))
|
40
|
+
self.addRow(row.contents)
|
41
|
+
lines = []
|
visidata/loaders/vds.py
CHANGED
@@ -2,37 +2,40 @@
|
|
2
2
|
|
3
3
|
import json
|
4
4
|
|
5
|
-
from visidata import VisiData, JsonSheet, Progress, IndexSheet, SettableColumn, ItemColumn
|
5
|
+
from visidata import VisiData, JsonSheet, Progress, IndexSheet, SettableColumn, ItemColumn, ExprColumn
|
6
6
|
|
7
7
|
|
8
8
|
NL='\n'
|
9
9
|
|
10
10
|
@VisiData.api
|
11
11
|
def open_vds(vd, p):
|
12
|
-
return VdsIndexSheet(p.
|
12
|
+
return VdsIndexSheet(p.base_stem, source=p)
|
13
13
|
|
14
14
|
|
15
15
|
@VisiData.api
|
16
16
|
def save_vds(vd, p, *sheets):
|
17
17
|
'Save in custom VisiData format, preserving columns and their attributes.'
|
18
18
|
|
19
|
-
with p.
|
19
|
+
with p.open(mode='w', encoding='utf-8') as fp:
|
20
20
|
for vs in sheets:
|
21
21
|
# class and attrs for vs
|
22
22
|
d = { 'name': vs.name, }
|
23
23
|
fp.write('#'+json.dumps(d)+NL)
|
24
24
|
|
25
25
|
# class and attrs for each column in vs
|
26
|
-
for col in vs.
|
26
|
+
for col in vs.columns:
|
27
27
|
d = col.__getstate__()
|
28
28
|
if isinstance(col, SettableColumn):
|
29
29
|
d['col'] = 'Column'
|
30
|
+
elif isinstance(col, ItemColumn):
|
31
|
+
d['col'] = 'Column'
|
32
|
+
d['expr'] = col.name #2037 override expr
|
30
33
|
else:
|
31
34
|
d['col'] = type(col).__name__
|
32
35
|
fp.write('#'+json.dumps(d)+NL)
|
33
36
|
|
34
37
|
with Progress(gerund='saving'):
|
35
|
-
for row in vs.iterdispvals(*vs.
|
38
|
+
for row in vs.iterdispvals(*vs.columns, format=False):
|
36
39
|
d = {col.name:val for col, val in row.items()}
|
37
40
|
fp.write(json.dumps(d, default=str)+NL)
|
38
41
|
|
@@ -40,7 +43,7 @@ def save_vds(vd, p, *sheets):
|
|
40
43
|
class VdsIndexSheet(IndexSheet):
|
41
44
|
def iterload(self):
|
42
45
|
vs = None
|
43
|
-
with self.source.
|
46
|
+
with self.source.open(encoding='utf-8') as fp:
|
44
47
|
line = fp.readline()
|
45
48
|
while line:
|
46
49
|
if line.startswith('#{'):
|
@@ -59,7 +62,7 @@ class VdsSheet(JsonSheet):
|
|
59
62
|
self.colnames = {}
|
60
63
|
self.columns = []
|
61
64
|
|
62
|
-
with self.source.
|
65
|
+
with self.source.open(encoding='utf-8') as fp:
|
63
66
|
fp.seek(self.source_fpos)
|
64
67
|
|
65
68
|
# consume all metadata, create columns
|
visidata/loaders/vdx.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
import re
|
2
|
+
|
1
3
|
import visidata
|
2
|
-
from visidata import VisiData, CommandLogBase, BaseSheet, Sheet, AttrDict
|
4
|
+
from visidata import VisiData, CommandLogBase, BaseSheet, Sheet, AttrDict, Progress
|
3
5
|
|
4
6
|
|
5
7
|
@VisiData.api
|
6
8
|
def open_vdx(vd, p):
|
7
|
-
return CommandLogSimple(p.
|
9
|
+
return CommandLogSimple(p.base_stem, source=p, precious=True)
|
8
10
|
|
9
11
|
|
10
12
|
class CommandLogSimple(CommandLogBase, Sheet):
|
@@ -20,16 +22,16 @@ class CommandLogSimple(CommandLogBase, Sheet):
|
|
20
22
|
|
21
23
|
@VisiData.api
|
22
24
|
def save_vdx(vd, p, *vsheets):
|
23
|
-
with p.
|
25
|
+
with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:
|
24
26
|
fp.write(f"# {visidata.__version_info__}\n")
|
25
27
|
for vs in vsheets:
|
26
28
|
prevrow = None
|
27
29
|
for r in vs.rows:
|
28
30
|
if prevrow is not None and r.sheet and prevrow.sheet != r.sheet:
|
29
31
|
fp.write(f'sheet {r.sheet}\n')
|
30
|
-
if r.col and (prevrow.col != r.col
|
32
|
+
if r.col and (prevrow is None or prevrow.col != r.col):
|
31
33
|
fp.write(f'col {r.col}\n')
|
32
|
-
if r.row and (prevrow.row != r.row
|
34
|
+
if r.row and (prevrow is None or prevrow.row != r.row):
|
33
35
|
fp.write(f'row {r.row}\n')
|
34
36
|
|
35
37
|
line = r.longname
|
@@ -40,6 +42,29 @@ def save_vdx(vd, p, *vsheets):
|
|
40
42
|
prevrow = r
|
41
43
|
|
42
44
|
|
45
|
+
@VisiData.api
|
46
|
+
def runvdx(vd, vdx:str):
|
47
|
+
for line in Progress(vdx.splitlines()):
|
48
|
+
vs = vd.sheet or Sheet()
|
49
|
+
vs.ensureLoaded()
|
50
|
+
line = line.strip()
|
51
|
+
if not line or line[0] == '#':
|
52
|
+
continue
|
53
|
+
|
54
|
+
m = re.match(r'^(\+(\S+) )?(\S+)(.*)$', line)
|
55
|
+
if not m:
|
56
|
+
print('bad:', line)
|
57
|
+
continue
|
58
|
+
|
59
|
+
_, pos, longname, rest = m.groups()
|
60
|
+
vd.currentReplayRow = AttrDict(longname=longname, input=rest)
|
61
|
+
if pos:
|
62
|
+
vd.moveToPos(vd.sheets, *vd.parsePos(pos))
|
63
|
+
print(vs.name, longname)
|
64
|
+
vs.execCommand(longname)
|
65
|
+
vd.sync()
|
66
|
+
|
67
|
+
|
43
68
|
BaseSheet.addCommand('', 'sheet', 'n=input("sheet to jump to: "); vd.push(vd.getSheet(n) or fail(f"no such sheet {n}"))', 'jump to named sheet')
|
44
69
|
BaseSheet.addCommand('', 'col', 'n=input("column to go to: "); moveToCol(n) or fail(f"no such column {n}")', 'move to named/numbered col')
|
45
70
|
BaseSheet.addCommand('', 'row', 'n=input("row to go to: "); moveToRow(n) or fail(f"no such row {n}")', 'move to named/numbered row')
|
visidata/loaders/xlsb.py
CHANGED
@@ -2,13 +2,20 @@ from visidata import vd, IndexSheet, VisiData
|
|
2
2
|
|
3
3
|
'Requires visidata/deps/pyxlsb fork'
|
4
4
|
|
5
|
+
@VisiData.api
|
6
|
+
def guess_xls(vd, p):
|
7
|
+
if p.open_bytes().read(16).startswith(b'\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1'):
|
8
|
+
return dict(filetype='xlsb', _likelihood=10)
|
9
|
+
|
5
10
|
|
6
11
|
@VisiData.api
|
7
12
|
def open_xlsb(vd, p):
|
8
|
-
return XlsbIndex(p.
|
13
|
+
return XlsbIndex(p.base_stem, source=p)
|
14
|
+
|
9
15
|
|
10
16
|
class XlsbIndex(IndexSheet):
|
11
17
|
def iterload(self):
|
18
|
+
vd.importExternal('pyxlsb', '-e git+https://github.com/saulpw/pyxlsb.git@visidata#egg=pyxlsb')
|
12
19
|
from pyxlsb import open_workbook
|
13
20
|
|
14
21
|
wb = open_workbook(str(self.source))
|
visidata/loaders/xlsx.py
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
import itertools
|
2
2
|
import copy
|
3
3
|
import datetime
|
4
|
+
import re
|
5
|
+
from colorsys import rgb_to_hls, hls_to_rgb
|
4
6
|
|
5
7
|
from visidata import VisiData, vd, Sheet, Column, Progress, IndexSheet, ColumnAttr, SequenceSheet, AttrDict, AttrColumn
|
8
|
+
from visidata import CellColorizer, getattrdeep, rgb_to_attr
|
6
9
|
from visidata.type_date import date
|
7
10
|
|
8
11
|
|
@@ -10,11 +13,11 @@ vd.option('xlsx_meta_columns', False, 'include columns for cell objects, font co
|
|
10
13
|
|
11
14
|
@VisiData.api
|
12
15
|
def open_xls(vd, p):
|
13
|
-
return XlsIndexSheet(p.
|
16
|
+
return XlsIndexSheet(p.base_stem, source=p)
|
14
17
|
|
15
18
|
@VisiData.api
|
16
19
|
def open_xlsx(vd, p):
|
17
|
-
return XlsxIndexSheet(p.
|
20
|
+
return XlsxIndexSheet(p.base_stem, source=p)
|
18
21
|
|
19
22
|
class XlsxIndexSheet(IndexSheet):
|
20
23
|
'Load XLSX file (in Excel Open XML format).'
|
@@ -29,16 +32,20 @@ class XlsxIndexSheet(IndexSheet):
|
|
29
32
|
nKeys = 1
|
30
33
|
|
31
34
|
def iterload(self):
|
32
|
-
|
35
|
+
openpyxl = vd.importExternal('openpyxl')
|
33
36
|
self.workbook = openpyxl.load_workbook(str(self.source), data_only=True, read_only=True)
|
34
37
|
for sheetname in self.workbook.sheetnames:
|
35
38
|
src = self.workbook[sheetname]
|
36
|
-
yield XlsxSheet(self.name, sheetname, source=src)
|
39
|
+
yield XlsxSheet(self.name, sheetname, source=src, workbook=self.workbook)
|
37
40
|
|
38
41
|
|
39
42
|
class XlsxSheet(SequenceSheet):
|
40
43
|
# rowdef: AttrDict of column_letter to cell
|
44
|
+
colorizers = [
|
45
|
+
CellColorizer(5, None, lambda s,c,r,v: c and r and s.colorize_xlsx_cell(c,r))
|
46
|
+
]
|
41
47
|
def setCols(self, headerrows):
|
48
|
+
vd.importExternal('openpyxl')
|
42
49
|
from openpyxl.utils.cell import get_column_letter
|
43
50
|
self.columns = []
|
44
51
|
self._rowtype = AttrDict
|
@@ -55,7 +62,7 @@ class XlsxSheet(SequenceSheet):
|
|
55
62
|
for i, colnamelines in enumerate(itertools.zip_longest(*headers, fillvalue='')):
|
56
63
|
colnamelines = ['' if c is None else c for c in colnamelines]
|
57
64
|
column_name = ''.join(map(str, colnamelines))
|
58
|
-
self.addColumn(AttrColumn(column_name, column_letters[i] + '.value'))
|
65
|
+
self.addColumn(AttrColumn(column_name, column_letters[i] + '.value', column_letter=column_letters[i]))
|
59
66
|
self.addXlsxMetaColumns(column_letters[i], column_name)
|
60
67
|
|
61
68
|
def addRow(self, row, index=None):
|
@@ -65,6 +72,7 @@ class XlsxSheet(SequenceSheet):
|
|
65
72
|
self.addXlsxMetaColumns(column_letter, column_letter)
|
66
73
|
|
67
74
|
def iterload(self):
|
75
|
+
vd.importExternal('openpyxl')
|
68
76
|
from openpyxl.utils.cell import get_column_letter
|
69
77
|
worksheet = self.source
|
70
78
|
for row in Progress(worksheet.iter_rows(), total=worksheet.max_row or 0):
|
@@ -73,13 +81,16 @@ class XlsxSheet(SequenceSheet):
|
|
73
81
|
def addXlsxMetaColumns(self, column_letter, column_name):
|
74
82
|
if self.options.xlsx_meta_columns:
|
75
83
|
self.addColumn(
|
76
|
-
AttrColumn(column_name + '_cellPyObj', column_letter))
|
84
|
+
AttrColumn(column_name + '_cellPyObj', column_letter, column_letter=column_letter))
|
77
85
|
self.addColumn(
|
78
86
|
AttrColumn(column_name + '_fontColor',
|
79
|
-
column_letter + '.font.color.value'))
|
87
|
+
column_letter + '.font.color.value', column_letter=column_letter))
|
80
88
|
self.addColumn(
|
81
89
|
AttrColumn(column_name + '_fillColor', column_letter +
|
82
|
-
'.fill.start_color.value'))
|
90
|
+
'.fill.start_color.value', column_letter=column_letter))
|
91
|
+
self.addColumn(Column(column_name + '_colorizer', width=0,
|
92
|
+
column_letter=column_letter,
|
93
|
+
getter=lambda c,r: c.sheet.colorize_xlsx_cell(c,r)))
|
83
94
|
|
84
95
|
def paste_after(self, rowidx):
|
85
96
|
to_paste = list(copy.copy(r) for r in reversed(vd.memory.cliprows))
|
@@ -97,7 +108,7 @@ class XlsIndexSheet(IndexSheet):
|
|
97
108
|
]
|
98
109
|
nKeys = 1
|
99
110
|
def iterload(self):
|
100
|
-
|
111
|
+
xlrd = vd.importExternal('xlrd')
|
101
112
|
self.workbook = xlrd.open_workbook(str(self.source))
|
102
113
|
for sheetname in self.workbook.sheet_names():
|
103
114
|
yield XlsSheet(self.name, sheetname, source=self.workbook.sheet_by_name(sheetname))
|
@@ -123,11 +134,26 @@ def xls_name(vs):
|
|
123
134
|
|
124
135
|
@VisiData.api
|
125
136
|
def save_xlsx(vd, p, *sheets):
|
126
|
-
|
137
|
+
openpyxl = vd.importExternal('openpyxl')
|
127
138
|
|
128
139
|
wb = openpyxl.Workbook()
|
129
140
|
wb.remove_sheet(wb['Sheet'])
|
130
141
|
|
142
|
+
def _convert_save_row(dispvals:dict, replace_illegal=False) -> list:
|
143
|
+
row = []
|
144
|
+
for col, v in dispvals.items():
|
145
|
+
if v is None:
|
146
|
+
v = ""
|
147
|
+
elif col.type == date:
|
148
|
+
v = datetime.datetime.fromtimestamp(int(v.timestamp()))
|
149
|
+
elif not vd.isNumeric(col):
|
150
|
+
v = str(v)
|
151
|
+
if replace_illegal:
|
152
|
+
v = re.sub(openpyxl.cell.cell.ILLEGAL_CHARACTERS_RE, ' ', v)
|
153
|
+
|
154
|
+
row.append(v)
|
155
|
+
return row
|
156
|
+
|
131
157
|
for vs in sheets:
|
132
158
|
if vs.xls_name != vs.names[-1]:
|
133
159
|
vd.warning(f'saving {vs.name} as {vs.xls_name}')
|
@@ -137,28 +163,21 @@ def save_xlsx(vd, p, *sheets):
|
|
137
163
|
ws.append(headers)
|
138
164
|
|
139
165
|
for dispvals in vs.iterdispvals(format=False):
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
v = datetime.datetime.fromtimestamp(int(v.timestamp()))
|
147
|
-
elif not vd.isNumeric(col):
|
148
|
-
v = str(v)
|
149
|
-
row.append(v)
|
150
|
-
|
151
|
-
ws.append(row)
|
166
|
+
row = _convert_save_row(dispvals)
|
167
|
+
try:
|
168
|
+
ws.append(row)
|
169
|
+
except openpyxl.utils.exceptions.IllegalCharacterError as e:
|
170
|
+
row = _convert_save_row(dispvals, replace_illegal=True) #1402
|
171
|
+
ws.append(row)
|
152
172
|
|
153
173
|
wb.active = ws
|
154
174
|
|
155
175
|
wb.save(filename=p)
|
156
|
-
vd.status(f'{p} save finished')
|
157
176
|
|
158
177
|
|
159
178
|
@VisiData.api
|
160
179
|
def save_xls(vd, p, *sheets):
|
161
|
-
|
180
|
+
xlwt = vd.importExternal('xlwt')
|
162
181
|
|
163
182
|
wb = xlwt.Workbook()
|
164
183
|
|
@@ -175,4 +194,105 @@ def save_xls(vd, p, *sheets):
|
|
175
194
|
ws1.write(r_i, c_i, v)
|
176
195
|
|
177
196
|
wb.save(p)
|
178
|
-
|
197
|
+
|
198
|
+
|
199
|
+
# from https://stackoverflow.com/a/65426130
|
200
|
+
|
201
|
+
RGBMAX = 255
|
202
|
+
HLSMAX = 240
|
203
|
+
|
204
|
+
@XlsxSheet.api
|
205
|
+
def colorize_xlsx_cell(sheet, col, row):
|
206
|
+
fg = getattrdeep(row, col.column_letter+'.font.color', None)
|
207
|
+
bg = getattrdeep(row, col.column_letter+'.fill.start_color', None)
|
208
|
+
fg = sheet.xlsx_color_to_xterm256(fg)
|
209
|
+
bg = sheet.xlsx_color_to_xterm256(bg)
|
210
|
+
|
211
|
+
if bg == '-1' or fg == '-1':
|
212
|
+
fg, bg = '-1', '-1'
|
213
|
+
|
214
|
+
return f'{fg} on {bg}'
|
215
|
+
|
216
|
+
@XlsxSheet.api
|
217
|
+
def xlsx_color_to_xterm256(sheet, color) -> str:
|
218
|
+
if not color:
|
219
|
+
return ''
|
220
|
+
|
221
|
+
if color.type == 'rgb':
|
222
|
+
s = color.value
|
223
|
+
if isinstance(s, int):
|
224
|
+
return str(s)
|
225
|
+
|
226
|
+
a,r,g,b = s[0:2], s[2:4], s[4:6], s[6:8]
|
227
|
+
return rgb_to_attr(int(r, 16), int(g, 16), int(b, 16), int(a, 16))
|
228
|
+
|
229
|
+
if color.type == 'theme':
|
230
|
+
return sheet.theme_and_tint_to_rgb(color.value, color.tint)
|
231
|
+
else:
|
232
|
+
return str(color.value)
|
233
|
+
|
234
|
+
@XlsxSheet.api
|
235
|
+
def theme_and_tint_to_rgb(sheet, theme, tint) -> str:
|
236
|
+
"""Given a workbook, a theme number and a tint return a xterm256 color number"""
|
237
|
+
rgb = sheet.theme_colors[theme]
|
238
|
+
h, l, s = rgb_to_ms_hls(rgb)
|
239
|
+
r, g, b = ms_hls_to_rgb(h, tint_luminance(tint, l), s)
|
240
|
+
|
241
|
+
return rgb_to_attr(r*256, g*256, b*256)
|
242
|
+
|
243
|
+
@XlsxSheet.lazy_property
|
244
|
+
def theme_colors(sheet):
|
245
|
+
"""Gets theme colors from the workbook"""
|
246
|
+
# see: https://groups.google.com/forum/#!topic/openpyxl-users/I0k3TfqNLrc
|
247
|
+
from openpyxl.xml.functions import QName, fromstring
|
248
|
+
xlmns = 'http://schemas.openxmlformats.org/drawingml/2006/main'
|
249
|
+
root = fromstring(sheet.workbook.loaded_theme)
|
250
|
+
themeEl = root.find(QName(xlmns, 'themeElements').text)
|
251
|
+
colorSchemes = themeEl.findall(QName(xlmns, 'clrScheme').text)
|
252
|
+
firstColorScheme = colorSchemes[0]
|
253
|
+
|
254
|
+
theme_colors = []
|
255
|
+
|
256
|
+
for c in ['lt1', 'dk1', 'lt2', 'dk2', 'accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6']:
|
257
|
+
accent = firstColorScheme.find(QName(xlmns, c).text)
|
258
|
+
for i in list(accent): # walk all child nodes, rather than assuming [0]
|
259
|
+
if 'window' in i.attrib['val']:
|
260
|
+
theme_colors.append(i.attrib['lastClr'])
|
261
|
+
else:
|
262
|
+
theme_colors.append(i.attrib['val'])
|
263
|
+
|
264
|
+
return theme_colors
|
265
|
+
|
266
|
+
def rgb_to_ms_hls(red, green=None, blue=None):
|
267
|
+
"""Converts rgb values in range (0,1) or a hex string of the form '[#aa]rrggbb' to HLSMAX based HLS, (alpha values are ignored)"""
|
268
|
+
if green is None:
|
269
|
+
if isinstance(red, str):
|
270
|
+
if len(red) > 6:
|
271
|
+
red = red[-6:] # Ignore preceding '#' and alpha values
|
272
|
+
blue = int(red[4:], 16) / RGBMAX
|
273
|
+
green = int(red[2:4], 16) / RGBMAX
|
274
|
+
red = int(red[0:2], 16) / RGBMAX
|
275
|
+
else:
|
276
|
+
red, green, blue = red
|
277
|
+
h, l, s = rgb_to_hls(red, green, blue)
|
278
|
+
return (int(round(h * HLSMAX)), int(round(l * HLSMAX)), int(round(s * HLSMAX)))
|
279
|
+
|
280
|
+
def ms_hls_to_rgb(hue, lightness=None, saturation=None):
|
281
|
+
"""Converts HLSMAX based HLS values to rgb values in the range (0,1)"""
|
282
|
+
if lightness is None:
|
283
|
+
hue, lightness, saturation = hue
|
284
|
+
return hls_to_rgb(hue / HLSMAX, lightness / HLSMAX, saturation / HLSMAX)
|
285
|
+
|
286
|
+
def rgb_to_hex(red, green=None, blue=None):
|
287
|
+
"""Converts (0,1) based RGB values to a hex string 'rrggbb'"""
|
288
|
+
if green is None:
|
289
|
+
red, green, blue = red
|
290
|
+
return ('%02x%02x%02x' % (int(round(red * RGBMAX)), int(round(green * RGBMAX)), int(round(blue * RGBMAX)))).upper()
|
291
|
+
|
292
|
+
def tint_luminance(tint, lum):
|
293
|
+
"""Tints a HLSMAX based luminance"""
|
294
|
+
# See: http://ciintelligence.blogspot.co.uk/2012/02/converting-excel-theme-color-and-tint.html
|
295
|
+
if tint < 0:
|
296
|
+
return int(round(lum * (1.0 + tint)))
|
297
|
+
else:
|
298
|
+
return int(round(lum * (1.0 - tint) + (HLSMAX - HLSMAX * (1.0 - tint))))
|
visidata/loaders/xml.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
from
|
1
|
+
from copy import copy
|
2
|
+
from visidata import VisiData, vd, Sheet, options, Column, Progress, setitem, ColumnAttr, vlen, RowColorizer, Path
|
2
3
|
|
3
4
|
vd.option('xml_parser_huge_tree', True, 'allow very deep trees and very long text content')
|
4
5
|
|
5
6
|
|
6
7
|
@VisiData.api
|
7
8
|
def open_xml(vd, p):
|
8
|
-
return XmlSheet(p.
|
9
|
+
return XmlSheet(p.base_stem, source=p)
|
9
10
|
|
10
11
|
VisiData.open_svg = VisiData.open_xml
|
11
12
|
|
@@ -47,9 +48,11 @@ class XmlSheet(Sheet):
|
|
47
48
|
|
48
49
|
def iterload(self):
|
49
50
|
if isinstance(self.source, Path):
|
51
|
+
vd.importExternal('lxml')
|
50
52
|
from lxml import etree, objectify
|
51
53
|
p = etree.XMLParser(**self.options.getall('xml_parser_'))
|
52
|
-
|
54
|
+
with self.open_text_source() as fp:
|
55
|
+
self.root = etree.parse(fp, parser=p)
|
53
56
|
objectify.deannotate(self.root, cleanup_namespaces=True)
|
54
57
|
else: # elif isinstance(self.source, XmlElement):
|
55
58
|
self.root = self.source
|
visidata/loaders/xword.py
CHANGED
@@ -8,13 +8,13 @@ vd.option('color_xword_active', 'green', 'color of active clue')
|
|
8
8
|
|
9
9
|
@VisiData.api
|
10
10
|
def open_puz(vd, p):
|
11
|
-
return PuzSheet(p.
|
11
|
+
return PuzSheet(p.base_stem, source=p)
|
12
12
|
|
13
13
|
@VisiData.api
|
14
14
|
def open_xd(vd, p):
|
15
15
|
if p.is_dir():
|
16
|
-
return CrosswordsSheet(p.
|
17
|
-
return CrosswordSheet(p.
|
16
|
+
return CrosswordsSheet(p.base_stem, source=p)
|
17
|
+
return CrosswordSheet(p.base_stem, source=p)
|
18
18
|
|
19
19
|
|
20
20
|
@VisiData.api
|
@@ -96,7 +96,7 @@ class PuzSheet(CrosswordSheet):
|
|
96
96
|
|
97
97
|
@VisiData.api
|
98
98
|
def save_xd(vd, p, vs):
|
99
|
-
with p.
|
99
|
+
with p.open(mode='w', encoding='utf-8') as fp:
|
100
100
|
fp.write(vs.xd.to_unicode())
|
101
101
|
|
102
102
|
|
visidata/loaders/yaml.py
CHANGED
@@ -1,19 +1,29 @@
|
|
1
1
|
from itertools import chain
|
2
2
|
|
3
|
-
from visidata import VisiData, Progress, JsonSheet
|
3
|
+
from visidata import VisiData, Progress, JsonSheet, vd
|
4
4
|
|
5
5
|
|
6
6
|
@VisiData.api
|
7
7
|
def open_yml(vd, p):
|
8
|
-
return YamlSheet(p.
|
8
|
+
return YamlSheet(p.base_stem, source=p)
|
9
9
|
|
10
10
|
VisiData.open_yaml = VisiData.open_yml
|
11
11
|
|
12
12
|
class YamlSheet(JsonSheet):
|
13
13
|
def iterload(self):
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
yaml = vd.importExternal('yaml', 'PyYAML')
|
15
|
+
|
16
|
+
class PrettySafeLoader(yaml.SafeLoader):
|
17
|
+
def construct_python_tuple(self, node):
|
18
|
+
return tuple(self.construct_sequence(node))
|
19
|
+
|
20
|
+
PrettySafeLoader.add_constructor(
|
21
|
+
u'tag:yaml.org,2002:python/tuple',
|
22
|
+
PrettySafeLoader.construct_python_tuple
|
23
|
+
)
|
24
|
+
|
25
|
+
with self.source.open() as fp:
|
26
|
+
documents = yaml.load_all(fp, PrettySafeLoader)
|
17
27
|
|
18
28
|
self.columns = []
|
19
29
|
self._knownKeys.clear()
|