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.
Files changed (253) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +263 -44
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +22 -4
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +65 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. visidata/apps/vgit/__main__.py +3 -0
  18. visidata/apps/vgit/abort.py +23 -0
  19. visidata/apps/vgit/blame.py +76 -0
  20. visidata/apps/vgit/branch.py +153 -0
  21. visidata/apps/vgit/config.py +95 -0
  22. visidata/apps/vgit/diff.py +169 -0
  23. visidata/apps/vgit/gitsheet.py +161 -0
  24. visidata/apps/vgit/grep.py +37 -0
  25. visidata/apps/vgit/log.py +81 -0
  26. visidata/apps/vgit/main.py +55 -0
  27. visidata/apps/vgit/remote.py +57 -0
  28. visidata/apps/vgit/repos.py +71 -0
  29. visidata/apps/vgit/setup.py +37 -0
  30. visidata/apps/vgit/stash.py +69 -0
  31. visidata/apps/vgit/status.py +204 -0
  32. visidata/apps/vgit/statusbar.py +34 -0
  33. visidata/basesheet.py +59 -50
  34. visidata/canvas.py +251 -99
  35. visidata/choose.py +15 -11
  36. visidata/clean_names.py +29 -0
  37. visidata/clipboard.py +84 -18
  38. visidata/cliptext.py +220 -46
  39. visidata/cmdlog.py +89 -114
  40. visidata/color.py +142 -56
  41. visidata/column.py +134 -131
  42. visidata/ddw/input.ddw +74 -79
  43. visidata/ddw/regex.ddw +57 -0
  44. visidata/ddwplay.py +33 -14
  45. visidata/deprecated.py +77 -3
  46. visidata/desktop/visidata.desktop +7 -0
  47. visidata/editor.py +12 -6
  48. visidata/errors.py +5 -1
  49. visidata/experimental/__init__.py +0 -0
  50. visidata/experimental/diff_sheet.py +29 -0
  51. visidata/experimental/digit_autoedit.py +6 -0
  52. visidata/experimental/gdrive.py +89 -0
  53. visidata/experimental/google.py +37 -0
  54. visidata/experimental/gsheets.py +79 -0
  55. visidata/experimental/live_search.py +37 -0
  56. visidata/experimental/liveupdate.py +45 -0
  57. visidata/experimental/mark.py +133 -0
  58. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  59. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  60. visidata/experimental/rownum.py +73 -0
  61. visidata/experimental/slide_cells.py +26 -0
  62. visidata/expr.py +8 -4
  63. visidata/extensible.py +32 -6
  64. visidata/features/__init__.py +0 -0
  65. visidata/features/addcol_audiometadata.py +42 -0
  66. visidata/features/addcol_histogram.py +34 -0
  67. visidata/features/canvas_save_svg.py +69 -0
  68. visidata/features/change_precision.py +46 -0
  69. visidata/features/cmdpalette.py +163 -0
  70. visidata/features/colorbrewer.py +363 -0
  71. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  72. visidata/features/command_server.py +105 -0
  73. visidata/features/currency_to_usd.py +70 -0
  74. visidata/{customdate.py → features/customdate.py} +2 -0
  75. visidata/features/dedupe.py +132 -0
  76. visidata/{describe.py → features/describe.py} +17 -15
  77. visidata/features/errors_guide.py +26 -0
  78. visidata/features/expand_cols.py +202 -0
  79. visidata/{fill.py → features/fill.py} +4 -2
  80. visidata/{freeze.py → features/freeze.py} +11 -6
  81. visidata/features/graph_seaborn.py +79 -0
  82. visidata/features/helloworld.py +10 -0
  83. visidata/features/hint_types.py +17 -0
  84. visidata/{incr.py → features/incr.py} +5 -0
  85. visidata/{join.py → features/join.py} +107 -53
  86. visidata/features/known_cols.py +21 -0
  87. visidata/features/layout.py +62 -0
  88. visidata/{melt.py → features/melt.py} +33 -21
  89. visidata/features/normcol.py +118 -0
  90. visidata/features/open_config.py +7 -0
  91. visidata/features/open_syspaste.py +18 -0
  92. visidata/features/ping.py +157 -0
  93. visidata/features/procmgr.py +208 -0
  94. visidata/features/random_sample.py +6 -0
  95. visidata/{regex.py → features/regex.py} +47 -31
  96. visidata/features/reload_every.py +55 -0
  97. visidata/features/rename_col_cascade.py +30 -0
  98. visidata/features/scroll_context.py +60 -0
  99. visidata/features/select_equal_selected.py +11 -0
  100. visidata/features/setcol_fake.py +65 -0
  101. visidata/{slide.py → features/slide.py} +75 -21
  102. visidata/features/sparkline.py +48 -0
  103. visidata/features/status_source.py +20 -0
  104. visidata/{sysedit.py → features/sysedit.py} +2 -1
  105. visidata/features/sysopen_mailcap.py +46 -0
  106. visidata/features/term_extras.py +13 -0
  107. visidata/{transpose.py → features/transpose.py} +5 -4
  108. visidata/features/type_ipaddr.py +73 -0
  109. visidata/features/type_url.py +11 -0
  110. visidata/{unfurl.py → features/unfurl.py} +9 -9
  111. visidata/{window.py → features/window.py} +2 -2
  112. visidata/form.py +50 -21
  113. visidata/freqtbl.py +81 -33
  114. visidata/fuzzymatch.py +414 -0
  115. visidata/graph.py +105 -33
  116. visidata/guide.py +180 -0
  117. visidata/help.py +75 -44
  118. visidata/hint.py +39 -0
  119. visidata/indexsheet.py +109 -0
  120. visidata/input_history.py +55 -0
  121. visidata/interface.py +58 -0
  122. visidata/keys.py +17 -16
  123. visidata/loaders/__init__.py +9 -0
  124. visidata/loaders/_pandas.py +61 -21
  125. visidata/loaders/api_airtable.py +70 -0
  126. visidata/loaders/api_bitio.py +102 -0
  127. visidata/loaders/api_matrix.py +148 -0
  128. visidata/loaders/api_reddit.py +306 -0
  129. visidata/loaders/api_zulip.py +249 -0
  130. visidata/loaders/archive.py +41 -7
  131. visidata/loaders/arrow.py +7 -7
  132. visidata/loaders/conll.py +49 -0
  133. visidata/loaders/csv.py +25 -7
  134. visidata/loaders/eml.py +3 -4
  135. visidata/loaders/f5log.py +1204 -0
  136. visidata/loaders/fec.py +325 -0
  137. visidata/loaders/fixed_width.py +3 -5
  138. visidata/loaders/frictionless.py +3 -3
  139. visidata/loaders/geojson.py +8 -5
  140. visidata/loaders/google.py +48 -0
  141. visidata/loaders/graphviz.py +4 -4
  142. visidata/loaders/hdf5.py +4 -4
  143. visidata/loaders/html.py +48 -10
  144. visidata/loaders/http.py +84 -30
  145. visidata/loaders/imap.py +20 -10
  146. visidata/loaders/jrnl.py +52 -0
  147. visidata/loaders/json.py +83 -29
  148. visidata/loaders/jsonla.py +74 -0
  149. visidata/loaders/lsv.py +15 -11
  150. visidata/loaders/mailbox.py +40 -0
  151. visidata/loaders/markdown.py +1 -3
  152. visidata/loaders/mbtiles.py +4 -5
  153. visidata/loaders/mysql.py +11 -13
  154. visidata/loaders/npy.py +7 -7
  155. visidata/loaders/odf.py +4 -1
  156. visidata/loaders/orgmode.py +428 -0
  157. visidata/loaders/pandas_freqtbl.py +14 -20
  158. visidata/loaders/parquet.py +62 -6
  159. visidata/loaders/pcap.py +3 -3
  160. visidata/loaders/pdf.py +4 -3
  161. visidata/loaders/png.py +19 -13
  162. visidata/loaders/postgres.py +9 -8
  163. visidata/loaders/rec.py +7 -3
  164. visidata/loaders/s3.py +342 -0
  165. visidata/loaders/sas.py +5 -5
  166. visidata/loaders/scrape.py +186 -0
  167. visidata/loaders/shp.py +6 -5
  168. visidata/loaders/spss.py +5 -6
  169. visidata/loaders/sqlite.py +68 -28
  170. visidata/loaders/texttables.py +1 -1
  171. visidata/loaders/toml.py +60 -0
  172. visidata/loaders/tsv.py +61 -19
  173. visidata/loaders/ttf.py +19 -7
  174. visidata/loaders/unzip_http.py +6 -5
  175. visidata/loaders/usv.py +1 -1
  176. visidata/loaders/vcf.py +16 -16
  177. visidata/loaders/vds.py +10 -7
  178. visidata/loaders/vdx.py +30 -5
  179. visidata/loaders/xlsb.py +8 -1
  180. visidata/loaders/xlsx.py +145 -25
  181. visidata/loaders/xml.py +6 -3
  182. visidata/loaders/xword.py +4 -4
  183. visidata/loaders/yaml.py +15 -5
  184. visidata/macos.py +1 -1
  185. visidata/macros.py +130 -41
  186. visidata/main.py +119 -94
  187. visidata/mainloop.py +101 -154
  188. visidata/man/parse_options.py +2 -2
  189. visidata/man/vd.1 +302 -147
  190. visidata/man/vd.txt +291 -151
  191. visidata/memory.py +3 -3
  192. visidata/menu.py +104 -423
  193. visidata/metasheets.py +59 -141
  194. visidata/modify.py +79 -23
  195. visidata/motd.py +3 -3
  196. visidata/mouse.py +137 -0
  197. visidata/movement.py +43 -35
  198. visidata/optionssheet.py +99 -0
  199. visidata/path.py +131 -43
  200. visidata/pivot.py +74 -47
  201. visidata/plugins.py +65 -192
  202. visidata/pyobj.py +50 -201
  203. visidata/rename_col.py +20 -0
  204. visidata/save.py +42 -20
  205. visidata/search.py +54 -10
  206. visidata/selection.py +84 -5
  207. visidata/settings.py +162 -24
  208. visidata/sheets.py +229 -257
  209. visidata/shell.py +51 -21
  210. visidata/sidebar.py +162 -0
  211. visidata/sort.py +11 -4
  212. visidata/statusbar.py +113 -104
  213. visidata/stored_list.py +43 -0
  214. visidata/stored_prop.py +38 -0
  215. visidata/tests/conftest.py +3 -3
  216. visidata/tests/test_cliptext.py +39 -0
  217. visidata/tests/test_commands.py +62 -7
  218. visidata/tests/test_edittext.py +2 -2
  219. visidata/tests/test_features.py +17 -0
  220. visidata/tests/test_menu.py +14 -0
  221. visidata/tests/test_path.py +13 -4
  222. visidata/text_source.py +53 -0
  223. visidata/textsheet.py +10 -3
  224. visidata/theme.py +44 -0
  225. visidata/themes/__init__.py +0 -0
  226. visidata/themes/ascii8.py +84 -0
  227. visidata/themes/asciimono.py +84 -0
  228. visidata/themes/light.py +17 -0
  229. visidata/threads.py +87 -39
  230. visidata/tuiwin.py +22 -0
  231. visidata/type_currency.py +22 -3
  232. visidata/type_date.py +31 -9
  233. visidata/type_floatsi.py +5 -1
  234. visidata/undo.py +18 -6
  235. visidata/utils.py +106 -23
  236. visidata/vdobj.py +28 -17
  237. visidata/windows.py +10 -0
  238. visidata/wrappers.py +9 -3
  239. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  240. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
  241. {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
  242. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  243. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
  244. visidata-3.0.dist-info/RECORD +257 -0
  245. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  246. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
  247. visidata/layout.py +0 -44
  248. visidata/misc.py +0 -5
  249. visidata-2.11.dev0.dist-info/RECORD +0 -142
  250. /visidata/{repeat.py → features/repeat.py} +0 -0
  251. {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
  252. {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  253. {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
+ ''')
@@ -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.name, source=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.name, source=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 fp.open(*args, **kwargs)
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 fp.open(*args, **kwargs, pwd=pwd.encode('utf-8'))
49
- vd.error(err)
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
- if (path/r.filename).exists():
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.name, source=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.name, source=p)
16
+ return ArrowSheet(p.base_stem, source=p)
17
17
 
18
18
 
19
19
  def arrow_to_vdtype(t):
20
- import pyarrow as pa
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
- pa.lib.Type_LARGE_STRING: vlen,
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
- import pyarrow as pa
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
- import pyarrow as pa
85
- import numpy as np
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
- options_num_first_rows = 10
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.name, source=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
- with self.source.open_text(encoding=self.options.encoding) as fp:
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
- with p.open_text(mode='w', encoding=sheet.options.encoding, newline='') as fp:
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.name, source=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.open_text(encoding='utf-8') as fp:
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
- if givenpath.exists() and sheet.options.confirm_overwrite:
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