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
@@ -0,0 +1,249 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from visidata import vd, VisiData, BaseSheet, Sheet, TextSheet, PyobjSheet
|
4
|
+
from visidata import ItemColumn, Column, vlen, date, asyncsingle, ENTER, AttrDict
|
5
|
+
|
6
|
+
vd.option('zulip_batch_size', -100, 'number of messages to fetch per call (<0 to fetch before anchor)')
|
7
|
+
vd.option('zulip_anchor', 1000000000, 'message id to start fetching from')
|
8
|
+
vd.option('zulip_delay_s', 0.00001, 'seconds to wait between calls (0 to stop after first)')
|
9
|
+
vd.option('zulip_api_key', '', 'Zulip API key')
|
10
|
+
vd.option('zulip_email', '', 'Email for use with Zulip API key')
|
11
|
+
|
12
|
+
|
13
|
+
@VisiData.api
|
14
|
+
def open_zulip(vd, p):
|
15
|
+
vd.importExternal('zulip')
|
16
|
+
import zulip
|
17
|
+
|
18
|
+
if not vd.options.zulip_api_key:
|
19
|
+
vd.warning('zulip_api_key must be set first')
|
20
|
+
vd.status('Enter your login email and Zulip API key (see _https://zulip.com/api/api-keys_).')
|
21
|
+
email = vd.input(f'Login email for {p.given}: ', record=False)
|
22
|
+
api_key = vd.input(f'Zulip API key: ', record=False)
|
23
|
+
|
24
|
+
vd.setPersistentOptions(zulip_email=email, zulip_api_key=api_key)
|
25
|
+
|
26
|
+
vd.z_client = zulip.Client(site=p.given, api_key=vd.options.zulip_api_key, email=vd.options.zulip_email)
|
27
|
+
|
28
|
+
return vd.subscribedStreams
|
29
|
+
|
30
|
+
|
31
|
+
VisiData.openhttp_zulip = VisiData.open_zulip
|
32
|
+
|
33
|
+
|
34
|
+
@VisiData.api
|
35
|
+
def z_rpc(vd, r, result_field_name=None):
|
36
|
+
if r['result'] != 'success':
|
37
|
+
return PyobjSheet(result_field_name+'_error', source=r)
|
38
|
+
elif result_field_name:
|
39
|
+
return PyobjSheet(result_field_name, source=r[result_field_name])
|
40
|
+
|
41
|
+
|
42
|
+
@VisiData.lazy_property
|
43
|
+
def allStreams(vd):
|
44
|
+
return ZulipStreamsSheet("all_streams", zulip_func='get_streams', zulip_result_key="streams", zulip_kwargs=dict(include_public=True, include_subscribed=True))
|
45
|
+
|
46
|
+
|
47
|
+
@VisiData.lazy_property
|
48
|
+
def subscribedStreams(vd):
|
49
|
+
return ZulipStreamsSheet("subscriptions", zulip_func='get_subscriptions', zulip_result_key="subscriptions")
|
50
|
+
|
51
|
+
|
52
|
+
@VisiData.lazy_property
|
53
|
+
def allMessages(vd):
|
54
|
+
return ZulipMessagesSheet("all_messages")
|
55
|
+
|
56
|
+
|
57
|
+
@VisiData.api
|
58
|
+
def parseColumns(vd, fieldlist):
|
59
|
+
for cname in fieldlist:
|
60
|
+
kwargs = {}
|
61
|
+
while not cname[0].isalpha():
|
62
|
+
if cname[0] == '#': kwargs['type'] = int
|
63
|
+
elif cname[0] == '@': kwargs['type'] = date
|
64
|
+
elif cname[0] == '-': kwargs['width'] = 0
|
65
|
+
else: break
|
66
|
+
cname = cname[1:]
|
67
|
+
yield ItemColumn(cname, **kwargs)
|
68
|
+
|
69
|
+
|
70
|
+
class ZulipAPISheet(Sheet):
|
71
|
+
zulip_func = None
|
72
|
+
zulip_result_key = ''
|
73
|
+
zulip_args = []
|
74
|
+
zulip_kwargs = {}
|
75
|
+
fields = ''
|
76
|
+
|
77
|
+
def iterload(self):
|
78
|
+
self.columns = []
|
79
|
+
for c in vd.parseColumns(self.fields.split()):
|
80
|
+
self.addColumn(c)
|
81
|
+
|
82
|
+
zulip_func = self.zulip_func
|
83
|
+
if isinstance(zulip_func, str): # allow later binding for startup perf
|
84
|
+
zulip_func = getattr(vd.z_client, zulip_func)
|
85
|
+
r = zulip_func(*self.zulip_args, **self.zulip_kwargs)
|
86
|
+
if r['result'] != 'success':
|
87
|
+
vd.push(PyobjSheet(self.zulip_result_key+'_error', source=r))
|
88
|
+
return
|
89
|
+
yield from r[self.zulip_result_key]
|
90
|
+
|
91
|
+
def addRow(self, r, **kwargs):
|
92
|
+
return super().addRow(AttrDict(r), **kwargs)
|
93
|
+
|
94
|
+
|
95
|
+
class ZulipStreamsSheet(ZulipAPISheet):
|
96
|
+
guide = '''# Zulip Streams
|
97
|
+
|
98
|
+
- `Enter` to open recent messages from the stream
|
99
|
+
- `z Enter` to open list of topics from the stream
|
100
|
+
'''
|
101
|
+
rowtype = 'streams' # rowdef: dict of stream from server
|
102
|
+
fields = '-#stream_id name @date_created description -rendered_description -invite_only -is_web_public -stream_post_policy -history_public_to_subscribers -#first_message_id -#message_retention_days -is_announcement_only'
|
103
|
+
|
104
|
+
def openRow(self, r):
|
105
|
+
return ZulipMessagesSheet(r.name, filters=dict(stream=r.name))
|
106
|
+
|
107
|
+
def openCell(self, c, r):
|
108
|
+
return ZulipTopicsSheet(r.name+'_topics',
|
109
|
+
zulip_func=vd.z_client.get_stream_topics,
|
110
|
+
zulip_args=[r.stream_id],
|
111
|
+
zulip_result_key='topics')
|
112
|
+
|
113
|
+
|
114
|
+
class ZulipTopicsSheet(ZulipAPISheet):
|
115
|
+
rowtype = 'topics' # rowdef: dict of topic from server
|
116
|
+
fields='name #max_id'
|
117
|
+
def openRow(self, r):
|
118
|
+
return ZulipMessagesSheet(f'{r.name}:{r.subject}', filters=dict(stream=r.name, topic=r.subject))
|
119
|
+
|
120
|
+
|
121
|
+
class ZulipMembersSheet(ZulipAPISheet):
|
122
|
+
guide = '''# Zulip Members
|
123
|
+
- `Enter` to open list of messages from this member
|
124
|
+
'''
|
125
|
+
rowtype = 'members' # rowdef: dict of member from server
|
126
|
+
fields = '''-#user_id full_name email timezone @date_joined -#avatar_version -is_admin -is_owner -is_guest -is_bot -#role -is_active -avatar_url -bot_type -#bot_owner_id'''
|
127
|
+
def openRow(self, r):
|
128
|
+
return ZulipMessagesSheet(r.display_recipient, filters=dict(stream=r.display_recipient))
|
129
|
+
|
130
|
+
|
131
|
+
class ZulipMessagesSheet(Sheet):
|
132
|
+
guide = '''# Zulip Messages Sheet
|
133
|
+
Loads continuously starting with most recent, until all messages have been read.
|
134
|
+
|
135
|
+
- `Ctrl+C` to cancel loading.
|
136
|
+
- `Enter` to open message in word-wrapped text sheet
|
137
|
+
'''
|
138
|
+
rowtype = 'messages' # rowdef: dict of message from server
|
139
|
+
# fields = ''
|
140
|
+
columns = [
|
141
|
+
ItemColumn('timestamp', type=date, fmtstr='%Y-%m-%d %H:%M'),
|
142
|
+
ItemColumn('sender', 'sender_full_name'),
|
143
|
+
ItemColumn('sender_email', width=0),
|
144
|
+
ItemColumn('recipient', 'display_recipient'),
|
145
|
+
ItemColumn('subject'),
|
146
|
+
ItemColumn('content'),
|
147
|
+
ItemColumn('client', width=0),
|
148
|
+
ItemColumn('reactions', type=vlen),
|
149
|
+
ItemColumn('submessages', type=vlen),
|
150
|
+
ItemColumn('flags', width=0),
|
151
|
+
]
|
152
|
+
filters={}
|
153
|
+
|
154
|
+
@asyncsingle # kill previous thread
|
155
|
+
def reload(self):
|
156
|
+
self.rows = []
|
157
|
+
narrow = list(self.filters.items())
|
158
|
+
n = self.options.zulip_batch_size
|
159
|
+
req = AttrDict(
|
160
|
+
num_before = -n if n < 0 else 0,
|
161
|
+
num_after = n if n > 0 else 0,
|
162
|
+
anchor = self.options.zulip_anchor,
|
163
|
+
apply_markdown = False,
|
164
|
+
narrow = narrow)
|
165
|
+
|
166
|
+
while True:
|
167
|
+
r = vd.z_client.call_endpoint(url='messages', method='GET', request=req)
|
168
|
+
if r['result'] == 'success':
|
169
|
+
if not r['messages']: break
|
170
|
+
for i, msg in enumerate(r['messages']):
|
171
|
+
self.addRow(msg, index=i)
|
172
|
+
req['anchor'] = min(msg['id'] for msg in r['messages'])-1
|
173
|
+
s = self.options.zulip_delay_s
|
174
|
+
if s <= 0:
|
175
|
+
break
|
176
|
+
time.sleep(s)
|
177
|
+
|
178
|
+
# vd.status('finished loading, waiting for new messages')
|
179
|
+
# vd.z_client.call_on_each_event(self.received_event, ['message'], narrow=narrow)
|
180
|
+
|
181
|
+
def get_channel_name(self, r):
|
182
|
+
recp = r['display_recipient']
|
183
|
+
if isinstance(recp, list): # private message
|
184
|
+
return '[%s]' % recp[0]['full_name']
|
185
|
+
else:
|
186
|
+
return '%s:%s' % (recp, r['subject'])
|
187
|
+
|
188
|
+
def update_message(self, msgid, content):
|
189
|
+
req = {
|
190
|
+
"message_id": msgid,
|
191
|
+
"content": content
|
192
|
+
}
|
193
|
+
vd.z_rpc(vd.z_client.update_message(req))
|
194
|
+
|
195
|
+
def openRow(self, r):
|
196
|
+
vs = TextSheet(self.get_channel_name(r), source=[r["content"]])
|
197
|
+
vs.options.wrap = True
|
198
|
+
return vs
|
199
|
+
|
200
|
+
def received_event(self, event):
|
201
|
+
if event['type'] == 'message':
|
202
|
+
self.addRow(event['message'])
|
203
|
+
|
204
|
+
def reply_message(self, msg, row):
|
205
|
+
recp = row['display_recipient']
|
206
|
+
if isinstance(recp, list):
|
207
|
+
for dest in recp:
|
208
|
+
self.send_message(msg, row['subject'], dest['email'], 'private')
|
209
|
+
else:
|
210
|
+
self.send_message(msg, row['subject'], dest, 'stream')
|
211
|
+
|
212
|
+
def send_message(self, msg, subject, dest, msgtype='stream'):
|
213
|
+
req = {
|
214
|
+
'type': msgtype,
|
215
|
+
'content': msg,
|
216
|
+
'subject': subject,
|
217
|
+
'to': dest,
|
218
|
+
}
|
219
|
+
r = vd.z_client.send_message(req)
|
220
|
+
|
221
|
+
if r['result'] != 'success':
|
222
|
+
vd.push(PyobjSheet('send_message_result', source=r))
|
223
|
+
|
224
|
+
|
225
|
+
vd.addGlobals({
|
226
|
+
'ZulipMembersSheet': ZulipMembersSheet,
|
227
|
+
'ZulipStreamsSheet': ZulipStreamsSheet,
|
228
|
+
'ZulipAPISheet': ZulipAPISheet,
|
229
|
+
'ZulipMessagesSheet': ZulipMessagesSheet,
|
230
|
+
})
|
231
|
+
|
232
|
+
ZulipAPISheet.addCommand('', 'open-zulip-profile', 'vd.push(PyobjSheet("profile", source=z_client.get_profile()))', 'open connected user\'s profile')
|
233
|
+
ZulipAPISheet.addCommand('', 'open-zulip-members', 'vd.push(ZulipMembersSheet("members", zulip_func=z_client.get_users, zulip_result_key="members"))', 'open list of all members')
|
234
|
+
ZulipAPISheet.addCommand('', 'open-zulip-streams', 'vd.push(vd.allStreams)', 'open list of all streams')
|
235
|
+
ZulipAPISheet.addCommand('', 'open-zulip-subs', 'vd.push(vd.subscribedStreams)', 'open list of subscribed streams')
|
236
|
+
ZulipAPISheet.addCommand('', 'open-zulip-msgs', 'vd.push(vd.allMessages)', 'open list of all messages')
|
237
|
+
|
238
|
+
ZulipMessagesSheet.addCommand('', 'reply-zulip-msg', 'reply_message(input(cursorRow["display_recipient"][1]["short_name"]+"> ", "message"), cursorRow)', 'reply to current topic')
|
239
|
+
ZulipMessagesSheet.addCommand('', 'edit-zulip-msg', 'update_message(cursorRow["id"], editCell(3, cursorRowIndex))', 'edit message content')
|
240
|
+
|
241
|
+
vd.addMenuItems('''
|
242
|
+
File > Zulip > profile > open-zulip-profile
|
243
|
+
File > Zulip > member list > open-zulip-members
|
244
|
+
File > Zulip > streams > open-zulip-streams
|
245
|
+
File > Zulip > subscriptions > open-zulip-subs
|
246
|
+
File > Zulip > messages > open-zulip-subs
|
247
|
+
File > Zulip > reply > reply-zulip-msg
|
248
|
+
File > Zulip > edit message > edit-zulip-msg
|
249
|
+
''')
|
visidata/loaders/archive.py
CHANGED
@@ -8,13 +8,23 @@ from visidata import vd, VisiData, asyncthread, Sheet, Progress, Menu, options
|
|
8
8
|
from visidata import ColumnAttr, Column, Path
|
9
9
|
from visidata.type_date import date
|
10
10
|
|
11
|
+
@VisiData.api
|
12
|
+
def guess_zip(vd, p):
|
13
|
+
if not p.is_url() and zipfile.is_zipfile(p.open_bytes()):
|
14
|
+
return dict(filetype='zip')
|
15
|
+
|
16
|
+
@VisiData.api
|
17
|
+
def guess_tar(vd, p):
|
18
|
+
if tarfile.is_tarfile(p.fp):
|
19
|
+
return dict(filetype='tar')
|
20
|
+
|
11
21
|
@VisiData.api
|
12
22
|
def open_zip(vd, p):
|
13
|
-
return vd.ZipSheet(p.
|
23
|
+
return vd.ZipSheet(p.base_stem, source=p)
|
14
24
|
|
15
25
|
@VisiData.api
|
16
26
|
def open_tar(vd, p):
|
17
|
-
return TarSheet(p.
|
27
|
+
return TarSheet(p.base_stem, source=p)
|
18
28
|
|
19
29
|
VisiData.open_tgz = VisiData.open_tar
|
20
30
|
VisiData.open_txz = VisiData.open_tar
|
@@ -37,16 +47,33 @@ class ZipSheet(Sheet):
|
|
37
47
|
getter=lambda col, row: datetime.datetime(*row[0].date_time)),
|
38
48
|
]
|
39
49
|
nKeys = 2
|
50
|
+
guide = '''# Zip Sheet
|
51
|
+
This is a list of files contained in the zipfile {sheet.displaySource}.
|
52
|
+
|
53
|
+
Once extracted, files can be loaded with `ENTER`.
|
54
|
+
|
55
|
+
Commands:
|
56
|
+
|
57
|
+
- `x` to extract current file to current directory
|
58
|
+
- `gx` to extract selected files to current directory
|
59
|
+
- `zx` to extract current file to a given pathname
|
60
|
+
- `gzx` to extract selected files to given directory
|
61
|
+
|
62
|
+
'''
|
40
63
|
|
41
64
|
def openZipFile(self, fp, *args, **kwargs):
|
42
65
|
'''Use VisiData input to handle password-protected zip files.'''
|
66
|
+
if isinstance(fp, zipfile.ZipFile):
|
67
|
+
zip_open = fp.open
|
68
|
+
elif isinstance(fp, unzip_http.RemoteZipFile):
|
69
|
+
zip_open = fp._open
|
43
70
|
try:
|
44
|
-
return
|
71
|
+
return zip_open(*args, **kwargs)
|
45
72
|
except RuntimeError as err:
|
46
73
|
if 'password required' in err.args[0]:
|
47
74
|
pwd = vd.input(f'{args[0].filename} is encrypted, enter password: ', display=False)
|
48
|
-
return
|
49
|
-
vd.
|
75
|
+
return zip_open(*args, **kwargs, pwd=pwd.encode('utf-8'))
|
76
|
+
vd.exceptionCaught(err)
|
50
77
|
|
51
78
|
def openRow(self, row):
|
52
79
|
fi, zpath = row
|
@@ -59,10 +86,16 @@ class ZipSheet(Sheet):
|
|
59
86
|
files = []
|
60
87
|
for row in rows:
|
61
88
|
r, _ = row
|
62
|
-
|
63
|
-
vd.confirm(f'{r.filename} exists, overwrite? ') #1452
|
89
|
+
vd.confirmOverwrite(path/r.filename) #1452
|
64
90
|
self.extract_async(row)
|
65
91
|
|
92
|
+
def sysopen_row(self, row):
|
93
|
+
'Extract file in row to tempdir and launch $EDITOR. Modifications will be discarded.'
|
94
|
+
import tempfile
|
95
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
96
|
+
self.zfp.extract(member=row[0], path=tempdir)
|
97
|
+
vd.launchExternalEditorPath(Path(tempdir)/row[0].filename)
|
98
|
+
|
66
99
|
@asyncthread
|
67
100
|
def extract_async(self, *rows, path=None):
|
68
101
|
'Extract rows to *path*, without confirmation.'
|
@@ -115,6 +148,7 @@ ZipSheet.addCommand('x', 'extract-file', 'extract(cursorRow)', 'extract current
|
|
115
148
|
ZipSheet.addCommand('gx', 'extract-selected', 'extract(*onlySelectedRows)', 'extract selected files to current directory')
|
116
149
|
ZipSheet.addCommand('zx', 'extract-file-to', 'extract(cursorRow, path=inputPath("extract to: "))', 'extract current file to given pathname')
|
117
150
|
ZipSheet.addCommand('gzx', 'extract-selected-to', 'extract(*onlySelectedRows, path=inputPath("extract %d files to: " % nSelectedRows))', 'extract selected files to given directory')
|
151
|
+
ZipSheet.addCommand('Ctrl+O', 'sysopen-row', 'sysopen_row(cursorRow)', 'open $EDITOR with current file (modifications will be discarded)')
|
118
152
|
|
119
153
|
vd.addMenu(Menu('File', Menu('Extract',
|
120
154
|
Menu('current file', 'extract-file'),
|
visidata/loaders/arrow.py
CHANGED
@@ -7,17 +7,17 @@ from visidata import Sheet, VisiData, TypedWrapper, anytype, date, vlen, Column,
|
|
7
7
|
@VisiData.api
|
8
8
|
def open_arrow(vd, p):
|
9
9
|
'Apache Arrow IPC file format'
|
10
|
-
return ArrowSheet(p.
|
10
|
+
return ArrowSheet(p.base_stem, source=p)
|
11
11
|
|
12
12
|
|
13
13
|
@VisiData.api
|
14
14
|
def open_arrows(vd, p):
|
15
15
|
'Apache Arrow IPC streaming format'
|
16
|
-
return ArrowSheet(p.
|
16
|
+
return ArrowSheet(p.base_stem, source=p)
|
17
17
|
|
18
18
|
|
19
19
|
def arrow_to_vdtype(t):
|
20
|
-
|
20
|
+
pa = vd.importExternal('pyarrow')
|
21
21
|
|
22
22
|
arrow_to_vd_typemap = {
|
23
23
|
pa.lib.Type_BOOL: bool,
|
@@ -44,7 +44,7 @@ def arrow_to_vdtype(t):
|
|
44
44
|
pa.lib.Type_LARGE_BINARY: vlen,
|
45
45
|
# pa.lib.Type_FIXED_SIZE_BINARY: bytes,
|
46
46
|
# pa.lib.Type_STRING: str,
|
47
|
-
|
47
|
+
# pa.lib.Type_LARGE_STRING: vlen, #2003
|
48
48
|
# pa.lib.Type_LIST: list,
|
49
49
|
# pa.lib.Type_LARGE_LIST: list,
|
50
50
|
# pa.lib.Type_FIXED_SIZE_LIST: list,
|
@@ -58,7 +58,7 @@ def arrow_to_vdtype(t):
|
|
58
58
|
|
59
59
|
class ArrowSheet(Sheet):
|
60
60
|
def iterload(self):
|
61
|
-
|
61
|
+
pa = vd.importExternal('pyarrow')
|
62
62
|
|
63
63
|
try:
|
64
64
|
with pa.OSFile(str(self.source), 'rb') as fp:
|
@@ -81,8 +81,8 @@ class ArrowSheet(Sheet):
|
|
81
81
|
|
82
82
|
@VisiData.api
|
83
83
|
def save_arrow(vd, p, sheet, streaming=False):
|
84
|
-
|
85
|
-
|
84
|
+
pa = vd.importExternal('pyarrow')
|
85
|
+
np = vd.importExternal('numpy')
|
86
86
|
|
87
87
|
typemap = {
|
88
88
|
anytype: pa.string(),
|
@@ -0,0 +1,49 @@
|
|
1
|
+
__author__ = "Paul McCann <polm@dampfkraft.com>"
|
2
|
+
|
3
|
+
from visidata import vd, VisiData, TableSheet, ItemColumn
|
4
|
+
|
5
|
+
@VisiData.api
|
6
|
+
def open_conll(vd, p):
|
7
|
+
return ConllSheet(p.base_stem, source=p)
|
8
|
+
|
9
|
+
|
10
|
+
@VisiData.api
|
11
|
+
def open_conllu(vd, p):
|
12
|
+
return ConllSheet(p.base_stem, source=p)
|
13
|
+
|
14
|
+
|
15
|
+
class ConllSheet(TableSheet):
|
16
|
+
rowtype='tokens'
|
17
|
+
# see here for reference:
|
18
|
+
# https://universaldependencies.org/format.html
|
19
|
+
columns=[
|
20
|
+
# Usually an integer, but can be prefixed like "dev-s1"
|
21
|
+
ItemColumn('sent_id', 0, type=str),
|
22
|
+
# token ID is almost always an integer, but can be technically be a decimal between 0 and 1.
|
23
|
+
# starts from 1 for each sentence.
|
24
|
+
ItemColumn('token_id', 1, type=int),
|
25
|
+
# form from the raw input, aka surface
|
26
|
+
ItemColumn('form', 2, type=str),
|
27
|
+
ItemColumn('lemma', 3, type=str),
|
28
|
+
ItemColumn('upos', 4, type=str),
|
29
|
+
ItemColumn('xpos', 5, type=str),
|
30
|
+
ItemColumn('feats', 6, type=dict),
|
31
|
+
ItemColumn('head', 7, type=int),
|
32
|
+
ItemColumn('deprel', 8, type=str),
|
33
|
+
# possibly list of pairs, but often? unused
|
34
|
+
ItemColumn('deps', 9),
|
35
|
+
# empty or a dictionary
|
36
|
+
ItemColumn('misc', 10, type=dict),
|
37
|
+
]
|
38
|
+
def iterload(self):
|
39
|
+
pyconll = vd.importExternal('pyconll')
|
40
|
+
|
41
|
+
# sent_id + token_id will be unique
|
42
|
+
self.setKeys([self.columns[0], self.columns[1]])
|
43
|
+
|
44
|
+
with self.source.open(encoding='utf-8') as fp:
|
45
|
+
for sent in pyconll.load.iter_sentences(fp):
|
46
|
+
sent_id = sent.id
|
47
|
+
for token in sent:
|
48
|
+
yield [sent_id, token.id, token._form, token.lemma, token.upos,
|
49
|
+
token.xpos, token.feats, token.head, token.deprel, token.deps, token.misc]
|
visidata/loaders/csv.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
import csv
|
2
|
-
|
3
1
|
from visidata import vd, VisiData, SequenceSheet, options, stacktrace
|
4
2
|
from visidata import TypedExceptionWrapper, Progress
|
5
3
|
|
@@ -11,13 +9,25 @@ vd.option('csv_escapechar', None, 'escapechar passed to csv.reader', replay=True
|
|
11
9
|
vd.option('csv_lineterminator', '\r\n', 'lineterminator passed to csv.writer', replay=True)
|
12
10
|
vd.option('safety_first', False, 'sanitize input/output to handle edge cases, with a performance cost', replay=True)
|
13
11
|
|
14
|
-
csv.field_size_limit(2**31-1) # Windows has max 32-bit
|
15
12
|
|
16
|
-
|
13
|
+
@VisiData.api
|
14
|
+
def guess_csv(vd, p):
|
15
|
+
import csv
|
16
|
+
csv.field_size_limit(2**31-1) #288 Windows has max 32-bit
|
17
|
+
line = next(p.open())
|
18
|
+
if ',' in line:
|
19
|
+
dialect = csv.Sniffer().sniff(line)
|
20
|
+
r = dict(filetype='csv', _likelihood=0)
|
21
|
+
|
22
|
+
for csvopt in dir(dialect):
|
23
|
+
if not csvopt.startswith('_'):
|
24
|
+
r['csv_'+csvopt] = getattr(dialect, csvopt)
|
25
|
+
|
26
|
+
return r
|
17
27
|
|
18
28
|
@VisiData.api
|
19
29
|
def open_csv(vd, p):
|
20
|
-
return CsvSheet(p.
|
30
|
+
return CsvSheet(p.base_stem, source=p)
|
21
31
|
|
22
32
|
def removeNulls(fp):
|
23
33
|
for line in fp:
|
@@ -28,7 +38,10 @@ class CsvSheet(SequenceSheet):
|
|
28
38
|
|
29
39
|
def iterload(self):
|
30
40
|
'Convert from CSV, first handling header row specially.'
|
31
|
-
|
41
|
+
import csv
|
42
|
+
csv.field_size_limit(2**31-1) #288 Windows has max 32-bit
|
43
|
+
|
44
|
+
with self.open_text_source() as fp:
|
32
45
|
if options.safety_first:
|
33
46
|
rdr = csv.reader(removeNulls(fp), **options.getall('csv_'))
|
34
47
|
else:
|
@@ -47,7 +60,10 @@ class CsvSheet(SequenceSheet):
|
|
47
60
|
@VisiData.api
|
48
61
|
def save_csv(vd, p, sheet):
|
49
62
|
'Save as single CSV file, handling column names as first line.'
|
50
|
-
|
63
|
+
import csv
|
64
|
+
csv.field_size_limit(2**31-1) #288 Windows has max 32-bit
|
65
|
+
|
66
|
+
with p.open(mode='w', encoding=sheet.options.save_encoding, newline='') as fp:
|
51
67
|
cw = csv.writer(fp, **options.getall('csv_'))
|
52
68
|
colnames = [col.name for col in sheet.visibleCols]
|
53
69
|
if ''.join(colnames):
|
@@ -57,6 +73,8 @@ def save_csv(vd, p, sheet):
|
|
57
73
|
for dispvals in sheet.iterdispvals(format=True):
|
58
74
|
cw.writerow(dispvals.values())
|
59
75
|
|
76
|
+
CsvSheet.options.regex_skip = '^#.*'
|
77
|
+
|
60
78
|
vd.addGlobals({
|
61
79
|
'CsvSheet': CsvSheet
|
62
80
|
})
|
visidata/loaders/eml.py
CHANGED
@@ -4,7 +4,7 @@ from visidata import VisiData, vd, Column, TableSheet, vlen
|
|
4
4
|
|
5
5
|
@VisiData.api
|
6
6
|
def open_eml(vd, p):
|
7
|
-
return EmailSheet(p.
|
7
|
+
return EmailSheet(p.base_stem, source=p)
|
8
8
|
|
9
9
|
class EmailSheet(TableSheet):
|
10
10
|
rowtype = 'parts' # rowdef: sub-Messages
|
@@ -16,7 +16,7 @@ class EmailSheet(TableSheet):
|
|
16
16
|
def iterload(self):
|
17
17
|
import email
|
18
18
|
parser = email.parser.Parser()
|
19
|
-
with self.source.
|
19
|
+
with self.source.open(encoding='utf-8') as fp:
|
20
20
|
yield from parser.parse(fp).walk()
|
21
21
|
|
22
22
|
@EmailSheet.api
|
@@ -27,8 +27,7 @@ def extract_part(sheet, givenpath, part):
|
|
27
27
|
@EmailSheet.api
|
28
28
|
def extract_parts(sheet, givenpath, *parts):
|
29
29
|
'Save all *parts* to Path *givenpath*.'
|
30
|
-
|
31
|
-
vd.confirm("%s already exists. overwrite? " % givenpath.given)
|
30
|
+
vd.confirmOverwrite(givenpath, f'{givenpath} already exists, extract anyway?')
|
32
31
|
|
33
32
|
vd.status('saving %s parts to %s' % (len(parts), givenpath.given))
|
34
33
|
|