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.
Files changed (256) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +78 -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. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +63 -51
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +6 -2
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +22 -4
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +197 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +77 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +200 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +20 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +54 -12
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +302 -149
  187. visidata/man/vd.txt +291 -154
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +55 -205
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +239 -260
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +114 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/benchmark.csv +52 -0
  213. visidata/tests/conftest.py +3 -3
  214. visidata/tests/test_cliptext.py +39 -0
  215. visidata/tests/test_commands.py +65 -7
  216. visidata/tests/test_edittext.py +2 -2
  217. visidata/tests/test_features.py +28 -0
  218. visidata/tests/test_menu.py +14 -0
  219. visidata/tests/test_path.py +13 -4
  220. visidata/text_source.py +53 -0
  221. visidata/textsheet.py +10 -3
  222. visidata/theme.py +44 -0
  223. visidata/themes/__init__.py +0 -0
  224. visidata/themes/ascii8.py +84 -0
  225. visidata/themes/asciimono.py +84 -0
  226. visidata/themes/light.py +17 -0
  227. visidata/threads.py +89 -40
  228. visidata/tuiwin.py +22 -0
  229. visidata/type_currency.py +22 -3
  230. visidata/type_date.py +31 -9
  231. visidata/type_floatsi.py +5 -1
  232. visidata/undo.py +17 -5
  233. visidata/utils.py +106 -23
  234. visidata/vdobj.py +28 -17
  235. visidata/windows.py +10 -0
  236. visidata/wrappers.py +9 -3
  237. visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
  238. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
  239. {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
  240. visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
  241. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
  242. visidata-3.0.1.dist-info/RECORD +258 -0
  243. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
  244. vgit/__init__.py +0 -1
  245. vgit/gitsheet.py +0 -164
  246. visidata/layout.py +0 -44
  247. visidata/misc.py +0 -5
  248. visidata-2.11.1.data/scripts/vgit +0 -9
  249. visidata-2.11.1.dist-info/RECORD +0 -155
  250. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  251. {vgit → visidata/apps/vgit}/abort.py +0 -0
  252. /visidata/{repeat.py → features/repeat.py} +0 -0
  253. {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
  256. {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,325 @@
1
+ """
2
+ Filename: vdfec.py
3
+ Last updated: 2019-04-21
4
+ Home: https://github.com/jsvine/visidata-plugins
5
+ Author: Jeremy Singer-Vine
6
+
7
+ # Installation
8
+
9
+ - Install fecfile: `pip install fecfile`
10
+ - Add vdfec.py to your ~/.visidata directory
11
+ - Add "import vdfec" to your ~/.visidatarc file
12
+
13
+ # Usage
14
+
15
+ vdfec.py enables VisiData to load .fec files from the Federal Election Commission.
16
+
17
+ Once saved to your ~/.visidata directory, and imported via your ~/.visidatarc file,
18
+ you should be able to interactively explore .fec files as you would any other
19
+ filetype in VisiData.
20
+
21
+ From the command line:
22
+
23
+ vd path/to/my/file.fec
24
+
25
+ # Thanks
26
+
27
+ vdfec.py depends heavily on Evan Sonderegger's `fecfile` Python library: https://esonderegger.github.io/fecfile/
28
+
29
+ ... which in turn is based on Derek Willis's `Fech` Ruby library: https://github.com/dwillis/Fech
30
+
31
+ ... and Chris Zubak-Skees' transformation of `Fech`'s form-and-field mappings: https://github.com/PublicI/fec-parse/blob/master/lib/renderedmaps.js
32
+
33
+ Thanks to all who have contributed to those projects.
34
+
35
+ """
36
+
37
+ from copy import copy
38
+ from visidata import (
39
+ vd,
40
+ Path,
41
+ Sheet,
42
+ TextSheet,
43
+ Column,
44
+ ColumnAttr,
45
+ ColumnItem,
46
+ ENTER,
47
+ asyncthread,
48
+ Progress,
49
+ addGlobals,
50
+ )
51
+
52
+ class DiveSheet(Sheet):
53
+ "A deeply-diveable, quick-diving sheet."
54
+
55
+ def reload(self):
56
+ mapping = self.source
57
+
58
+ self.columns = []
59
+ self.rows = []
60
+
61
+ self.key_type = str
62
+ self.size = len(mapping)
63
+
64
+ if self.size == 0:
65
+ return
66
+
67
+ if isinstance(mapping, list):
68
+ first = mapping[0]
69
+ if isinstance(first, dict):
70
+ colgetter = lambda x: x.keys()
71
+ elif isinstance(first, list):
72
+ colgetter = lambda x: list(range(len(x)))
73
+ else:
74
+ mapping = dict(enumerate(mapping))
75
+ self.key_type = int
76
+ self.size = len(mapping)
77
+
78
+ if isinstance(mapping, dict):
79
+ self.is_keyvalue = True
80
+ if self.size:
81
+ max_key_len = max(map(len, map(str, mapping.keys())))
82
+ key_width = min(50, max(max_key_len + 2, 6))
83
+ else:
84
+ key_width = None
85
+
86
+ self.addColumn(ColumnItem(
87
+ "key",
88
+ width = key_width,
89
+ type = self.key_type
90
+ ))
91
+ self.addColumn(ColumnItem("value"))
92
+ self.setKeys(self.columns[:1])
93
+
94
+ for k, v in mapping.items():
95
+ self.addRow({ "key": k, "value": v })
96
+
97
+ elif isinstance(mapping, list):
98
+ self.is_keyvalue = False
99
+ indices = []
100
+ for item in mapping:
101
+ try:
102
+ cols = colgetter(item)
103
+ for col in cols:
104
+ if col not in indices:
105
+ self.addColumn(ColumnItem(col))
106
+ indices.append(col)
107
+
108
+ self.addRow(item)
109
+
110
+ except Exception as e:
111
+ vd.warning("Can't dive on lists with heterogenous item types.")
112
+ return False
113
+
114
+ def dive(self):
115
+ if self.is_keyvalue:
116
+ cell = self.cursorRow["value"]
117
+ name = vd.joinSheetnames(self.name, self.cursorRow["key"])
118
+
119
+ if isinstance(cell, (list, dict)):
120
+ vs = self.__class__(name, source = cell)
121
+ else:
122
+ vd.warning("Nothing to dive into.")
123
+ return
124
+ else:
125
+ name = vd.joinSheetnames(self.name, "row")
126
+ vs = self.__class__(name, source = self.cursorRow)
127
+
128
+ success = vs.reload()
129
+ if success == False:
130
+ return
131
+
132
+ vd.push(vs)
133
+
134
+ DiveSheet.addCommand(
135
+ ENTER,
136
+ 'dive-row',
137
+ 'vd.sheet.dive()'
138
+ )
139
+
140
+ class FECItemizationSheet(Sheet):
141
+ "A sheet to display a list of FEC itemizations from a given form/schedule."
142
+
143
+ rowtype = "itemizations"
144
+
145
+ @asyncthread
146
+ def reload(self):
147
+ self.rows = []
148
+ self.columns = []
149
+
150
+ if len(self.source) == 0:
151
+ return
152
+
153
+ for i, row in enumerate(Progress(self.source, total = len(self.source))):
154
+ if i == 0:
155
+ self.set_columns_from_row(row)
156
+ self.addRow(row)
157
+
158
+ def set_columns_from_row(self, row):
159
+ self.columns.clear()
160
+ for i, name in enumerate(row.keys()):
161
+ self.addColumn(ColumnItem(name))
162
+ def dive(self):
163
+ vs = DiveSheet(
164
+ vd.joinSheetnames(self.name, "detail"),
165
+ source = self.cursorRow
166
+ )
167
+ vs.reload()
168
+ vd.push(vs)
169
+
170
+ FECItemizationSheet.addCommand(
171
+ ENTER,
172
+ 'dive-row',
173
+ 'vd.sheet.dive()'
174
+ )
175
+
176
+ class FECScheduleSheet(Sheet):
177
+ "A sheet to display the list of itemized schedules in a filing."
178
+
179
+ rowtype = "schedules"
180
+
181
+ columns = [
182
+ ColumnAttr("schedule", "schedule_name", width = 14),
183
+ ColumnAttr("name", width = 0),
184
+ ColumnAttr("size", type = int),
185
+ ]
186
+
187
+ nKeys = 1
188
+
189
+ @asyncthread
190
+ def reload(self):
191
+ self.rows = []
192
+
193
+ for schedule_name in self.source.keys():
194
+ vs = FECItemizationSheet(
195
+ vd.joinSheetnames(self.name, schedule_name),
196
+ schedule_name = schedule_name,
197
+ source = self.source[schedule_name],
198
+ size = len(self.source[schedule_name]),
199
+ )
200
+ self.addRow(vs)
201
+
202
+ FECScheduleSheet.addCommand(
203
+ ENTER,
204
+ 'dive-row',
205
+ 'vd.push(cursorRow)'
206
+ )
207
+
208
+ COMPONENT_SHEET_CLASSES = {
209
+ "header": DiveSheet,
210
+ "summary": DiveSheet,
211
+ "itemization": FECScheduleSheet,
212
+ "text": FECItemizationSheet,
213
+ "F99_text": TextSheet,
214
+ }
215
+
216
+ class FECFiling(Sheet):
217
+ "A sheet representing an entire .fec file."
218
+
219
+ rowtype = "components"
220
+ filing = None
221
+
222
+ columns = [
223
+ ColumnAttr("component", "component_name", width = 14),
224
+ ColumnAttr("name", width = 0),
225
+ ColumnAttr("size", type = int),
226
+ ]
227
+
228
+ nKeys = 1
229
+
230
+ @asyncthread
231
+ def reload(self):
232
+ from fecfile import fecparser
233
+ self.rows = []
234
+
235
+ row_dict = { }
236
+ itemization_subsheets = {}
237
+
238
+ def addSheetRow(component_name):
239
+ "On first encountering a component, add a row to the filing sheet"
240
+
241
+ cls = COMPONENT_SHEET_CLASSES[component_name]
242
+
243
+ source_cls = list if cls in [
244
+ FECItemizationSheet,
245
+ TextSheet
246
+ ] else dict
247
+
248
+ vs = cls(
249
+ vd.joinSheetnames(self.name, component_name),
250
+ component_name = component_name,
251
+ source = source_cls(),
252
+ size = 0,
253
+ )
254
+
255
+ vs.reload()
256
+ row_dict[component_name] = vs
257
+ self.addRow(vs)
258
+
259
+ src = Path(self.source.resolve())
260
+
261
+ item_iter = fecparser.iter_lines(src, { "as_strings": True })
262
+
263
+ for item in item_iter:
264
+ dtype = item.data_type
265
+ if dtype not in row_dict.keys():
266
+ addSheetRow(dtype)
267
+
268
+ sheet_row = row_dict[dtype]
269
+
270
+ if dtype in [ "header", "summary" ]:
271
+ sheet_row.source = item.data
272
+ sheet_row.reload()
273
+
274
+ elif dtype == "text":
275
+ if len(sheet_row.source) == 0:
276
+ sheet_row.set_columns_from_row(item.data)
277
+ sheet_row.source.append(item.data)
278
+ sheet_row.addRow(item.data)
279
+ sheet_row.size += 1
280
+
281
+ elif dtype == "F99_text":
282
+ sheet_row.source = item.data.split("\n")
283
+ sheet_row.size = len(sheet_row.source)
284
+
285
+ elif dtype == "itemization":
286
+ form_type = item.data["form_type"]
287
+
288
+ if form_type[0] == "S":
289
+ form_type = "Schedule " + item.data["form_type"][1]
290
+
291
+ if form_type not in sheet_row.source:
292
+ sheet_row.source[form_type] = [ ]
293
+ subsheet = FECItemizationSheet(
294
+ vd.joinSheetnames(sheet_row.name, form_type),
295
+ schedule_name = form_type,
296
+ source = [ ],
297
+ size = 0,
298
+ )
299
+ subsheet.reload()
300
+ subsheet.set_columns_from_row(item.data)
301
+ sheet_row.addRow(subsheet)
302
+ itemization_subsheets[form_type] = subsheet
303
+ else:
304
+ subsheet = itemization_subsheets[form_type]
305
+
306
+ subsheet.addRow(item.data)
307
+ subsheet.source.append(item.data)
308
+ subsheet.size += 1
309
+
310
+ sheet_row.source[form_type].append(item.data)
311
+ sheet_row.size += 1
312
+
313
+ FECFiling.addCommand(
314
+ ENTER,
315
+ 'dive-row',
316
+ 'vd.push(cursorRow)'
317
+ )
318
+
319
+ def open_fec(p):
320
+ return FECFiling(p.base_stem, source=p)
321
+
322
+ addGlobals({
323
+ "open_fec": open_fec,
324
+ "DiveSheet": DiveSheet
325
+ })
@@ -7,7 +7,7 @@ vd.option('fixed_maxcols', 0, 'max number of fixed-width columns to create (0 is
7
7
 
8
8
  @VisiData.api
9
9
  def open_fixed(vd, p):
10
- return FixedWidthColumnsSheet(p.name, source=p, headerlines=[])
10
+ return FixedWidthColumnsSheet(p.base_stem, source=p, headerlines=[])
11
11
 
12
12
  class FixedWidthColumn(Column):
13
13
  def __init__(self, name, i, j, **kwargs):
@@ -76,7 +76,7 @@ class FixedWidthColumnsSheet(SequenceSheet):
76
76
 
77
77
  @VisiData.api
78
78
  def save_fixed(vd, p, *vsheets):
79
- with p.open_text(mode='w', encoding=vsheets[0].options.encoding) as fp:
79
+ with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:
80
80
  for sheet in vsheets:
81
81
  if len(vsheets) > 1:
82
82
  fp.write('%s\n\n' % sheet.name)
@@ -94,5 +94,3 @@ def save_fixed(vd, p, *vsheets):
94
94
  for col, val in dispvals.items():
95
95
  fp.write(('{0:%s%s.%s} ' % ('>' if vd.isNumeric(col) else '<', widths[col], widths[col])).format(val))
96
96
  fp.write('\n')
97
-
98
- vd.status('%s save finished' % p)
@@ -2,11 +2,11 @@ from visidata import VisiData, vd, Progress, IndexSheet
2
2
 
3
3
  @VisiData.api
4
4
  def open_frictionless(vd, p):
5
- return FrictionlessIndexSheet(p.name, source=p)
5
+ return FrictionlessIndexSheet(p.base_stem, source=p)
6
6
 
7
7
  class FrictionlessIndexSheet(IndexSheet):
8
8
  def iterload(self):
9
- import datapackage
10
- self.dp = datapackage.Package(self.source.open_text(encoding='utf-8'))
9
+ datapackage = vd.importExternal('datapackage')
10
+ self.dp = datapackage.Package(self.source.open(encoding='utf-8'))
11
11
  for r in Progress(self.dp.resources):
12
12
  yield vd.openSource(self.source.with_name(r.descriptor['path']), filetype=r.descriptor.get('format', 'json'))
@@ -1,12 +1,15 @@
1
1
  from functools import reduce
2
+ from copy import deepcopy
3
+
2
4
  import json
3
5
 
4
- from visidata import VisiData, vd, Column, asyncthread, Progress, PythonSheet, InvertedCanvas, deepcopy, date, wrapply, TypedExceptionWrapper, TypedWrapper
6
+ from visidata import VisiData, vd, Column, asyncthread, Progress, PythonSheet, InvertedCanvas, date, wrapply, TypedExceptionWrapper, TypedWrapper
7
+
5
8
 
6
9
 
7
10
  @VisiData.api
8
11
  def open_geojson(vd, p):
9
- return GeoJSONSheet(p.name, source=p)
12
+ return GeoJSONSheet(p.base_stem, source=p)
10
13
 
11
14
  class GeoJSONColumn(Column):
12
15
  def calcValue(self, row):
@@ -24,7 +27,7 @@ class GeoJSONSheet(PythonSheet):
24
27
  def iterload(self):
25
28
  self.colnames = {}
26
29
 
27
- with self.source.open_text(encoding='utf-8') as fp:
30
+ with self.source.open(encoding='utf-8') as fp:
28
31
  ret = json.load(fp)
29
32
 
30
33
  if ret['type'] == 'FeatureCollection':
@@ -146,14 +149,14 @@ def save_geojson(vd, p, vs):
146
149
  except Exception:
147
150
  indent = vs.options.json_indent
148
151
 
149
- with p.open_text(mode='w', encoding='utf-8') as fp:
152
+ with p.open(mode='w', encoding='utf-8') as fp:
150
153
  encoder = json.JSONEncoder(indent=indent, sort_keys=vs.options.json_sort_keys)
151
154
  for chunk in encoder.iterencode(featcoll):
152
155
  fp.write(chunk)
153
156
 
154
157
  GeoJSONSheet.addCommand('.', 'plot-row', 'vd.push(GeoJSONMap(name+"_map", sourceRows=[cursorRow], textCol=cursorCol, source=sheet))', 'plot geospatial vector in current row')
155
158
  GeoJSONSheet.addCommand('g.', 'plot-rows', 'vd.push(GeoJSONMap(name+"_map", sourceRows=rows, textCol=cursorCol, source=sheet))', 'plot all geospatial vectors in current sheet')
156
- GeoJSONMap.addCommand('^S', 'save-sheet', 'vd.saveSheets(inputPath("save to: ", value=getDefaultSaveName(sheet)), sheet, confirm_overwrite=options.confirm_overwrite)', 'save current sheet to filename in format determined by extension (default .geojson)')
159
+ GeoJSONMap.addCommand('^S', 'save-sheet', 'vd.saveSheets(inputPath("save to: ", value=getDefaultSaveName(sheet)), sheet)', 'save current sheet to filename in format determined by extension (default .geojson)')
157
160
 
158
161
  vd.addGlobals({
159
162
  'GeoJSONMap': GeoJSONMap,
@@ -0,0 +1,48 @@
1
+
2
+
3
+ from visidata import vd, VisiData, Sheet, IndexSheet, SequenceSheet, ColumnItem, Path, AttrDict, ColumnAttr, asyncthread
4
+
5
+
6
+ def _google_creds_fn():
7
+
8
+ filename = 'google_creds.json'
9
+ google_creds_path = vd.pkg_resources_files('vdplus.api.google') / filename
10
+ import os
11
+ if not os.path.exists(google_creds_path):
12
+ vd.error(f'{filename} file does not exist in {google_creds_path.parent}\n'
13
+ 'Create it by following this guide: https://github.com/saulpw/visidata/blob/develop/docs/gmail.md')
14
+ else:
15
+ return str(google_creds_path)
16
+
17
+
18
+ @VisiData.api
19
+ def google_auth(vd, scopes=None):
20
+ import pickle
21
+ import os.path
22
+ import urllib.parse
23
+
24
+ SCOPES = []
25
+ for scope in scopes.split():
26
+ if not scope.startswith('https://'):
27
+ scope = 'https://www.googleapis.com/auth/' + scope
28
+ SCOPES.append(scope)
29
+
30
+ GOOGLE_TOKEN_FILE = Path(vd.options.visidata_dir)/f'google-{urllib.parse.quote_plus(str(scopes))}.pickle'
31
+ creds = None
32
+ if os.path.exists(GOOGLE_TOKEN_FILE):
33
+ with open(GOOGLE_TOKEN_FILE, 'rb') as fp:
34
+ creds = pickle.load(fp)
35
+
36
+ if not creds or not creds.valid:
37
+ if creds and creds.expired and creds.refresh_token:
38
+ from google.auth.transport.requests import Request
39
+ creds.refresh(Request())
40
+ else:
41
+ from google_auth_oauthlib.flow import InstalledAppFlow
42
+ flow = InstalledAppFlow.from_client_secrets_file(_google_creds_fn(), SCOPES)
43
+ creds = flow.run_local_server(port=0)
44
+
45
+ with open(GOOGLE_TOKEN_FILE, 'wb') as fp:
46
+ pickle.dump(creds, fp)
47
+
48
+ return creds
@@ -1,5 +1,5 @@
1
1
  from visidata import vd, options, TypedWrapper, asyncthread, Progress
2
- from visidata import wrapply, clean_to_id, VisiData
2
+ from visidata import wrapply, VisiData
3
3
 
4
4
  vd.option('graphviz_edge_labels', True, 'whether to include edge labels on graphviz diagrams')
5
5
 
@@ -19,7 +19,7 @@ def save_dot(vd, p, vs):
19
19
 
20
20
  srccol = vs.keyCols[0]
21
21
  dstcol = vs.keyCols[1]
22
- with p.open_text(mode='w', encoding='utf-8') as fp:
22
+ with p.open(mode='w', encoding='utf-8') as fp:
23
23
  pfp = lambda *args: print(*args, file=fp)
24
24
  pfp('graph { concentrate=true;')
25
25
  for row in Progress(vs.rows, 'saving'):
@@ -28,8 +28,8 @@ def save_dot(vd, p, vs):
28
28
  if not is_valid(src) or not is_valid(dst):
29
29
  continue
30
30
 
31
- downsrc = clean_to_id(str(src)) or src
32
- downdst = clean_to_id(str(dst)) or dst
31
+ downsrc = vd.cleanName(str(src)) or src
32
+ downdst = vd.cleanName(str(dst)) or dst
33
33
  edgenotes = [c.getTypedValue(row) for c in vs.nonKeyVisibleCols if not vd.isNumeric(c)]
34
34
  edgetype = '-'.join(str(x) for x in edgenotes if is_valid(x))
35
35
  color = assignedColors.get(edgetype, None)
visidata/loaders/hdf5.py CHANGED
@@ -2,14 +2,14 @@ from visidata import VisiData, vd, Sheet, Path, Column, ItemColumn, BaseSheet
2
2
 
3
3
  @VisiData.api
4
4
  def open_h5(vd, p):
5
- return Hdf5ObjSheet(p.name, source=p)
5
+ return Hdf5ObjSheet(p.base_stem, source=p)
6
6
 
7
7
  VisiData.open_hdf5 = VisiData.open_h5
8
8
 
9
9
  class Hdf5ObjSheet(Sheet):
10
10
  'Support sheets in HDF5 format.'
11
11
  def iterload(self):
12
- import h5py
12
+ h5py = vd.importExternal('h5py')
13
13
  source = self.source
14
14
  if isinstance(self.source, Path):
15
15
  source = h5py.File(str(self.source), 'r')
@@ -48,13 +48,13 @@ class Hdf5ObjSheet(Sheet):
48
48
 
49
49
 
50
50
  def openRow(self, row):
51
- import h5py
51
+ h5py = vd.importExternal('h5py')
52
52
  if isinstance(row, BaseSheet):
53
53
  return row
54
54
  if isinstance(row, h5py.HLObject):
55
55
  return Hdf5ObjSheet(row)
56
56
 
57
- import numpy
57
+ numpy = vd.importExternal('numpy')
58
58
  from .npy import NpySheet
59
59
  if isinstance(row, numpy.ndarray):
60
60
  return NpySheet(None, npy=row)
visidata/loaders/html.py CHANGED
@@ -3,13 +3,26 @@ import urllib.parse
3
3
  import copy
4
4
  import itertools
5
5
 
6
- from visidata import VisiData, vd, Sheet, options, Column, Progress, IndexSheet, ItemColumn, InferColumnsSheet
6
+ from visidata import VisiData, vd, Sheet, options, Column, Progress, IndexSheet, ItemColumn
7
7
 
8
8
  vd.option('html_title', '<h2>{sheet.name}</h2>', 'table header when saving to html')
9
9
 
10
+
11
+ @VisiData.api
12
+ def guess_html(vd, p):
13
+ with p.open() as fp:
14
+ r = fp.read(10240)
15
+ if r.strip().startswith('<'):
16
+ m = re.search(r, r'charset=(\S+)')
17
+ if m:
18
+ encoding = m.group(0)
19
+ else:
20
+ encoding = None
21
+ return dict(filetype='html', _likelihood=1, encoding=encoding)
22
+
10
23
  @VisiData.api
11
24
  def open_html(vd, p):
12
- return HtmlTablesSheet(p.name, source=p)
25
+ return HtmlTablesSheet(p.base_stem, source=p)
13
26
 
14
27
  VisiData.open_htm = VisiData.open_html
15
28
 
@@ -20,12 +33,17 @@ class HtmlTablesSheet(IndexSheet):
20
33
  Column('tag', width=0, getter=lambda col,row: row.html.tag),
21
34
  Column('id', getter=lambda col,row: row.html.attrib.get('id')),
22
35
  Column('classes', getter=lambda col,row: row.html.attrib.get('class')),
36
+ Column('title', getter=lambda col,row: row.html.attrib.get('title')),
37
+ Column('aria_label', getter=lambda col,row: row.html.attrib.get('aria-label')),
38
+ Column('caption', getter=lambda col,row: row.html.xpath('normalize-space(./caption)') if row.html.xpath('./caption') else None, cache=True),
39
+ Column('summary', getter=lambda col,row: row.html.attrib.get('summary')),
40
+ Column('heading', getter=lambda col,row: row.html.xpath('normalize-space(./preceding-sibling::*[self::h1 or self::h2 or self::h3 or self::h4 or self::h5 or self::h6][1])') or None, cache=True),
23
41
  ]
24
42
  def iterload(self):
43
+ lxml = vd.importExternal('lxml')
25
44
  from lxml import html
26
- utf8_parser = html.HTMLParser(encoding='utf-8')
27
- with self.source.open_text(encoding='utf-8') as fp:
28
- doc = html.parse(fp, parser=utf8_parser, base_url=self.source.given)
45
+ with self.source.open(encoding='utf-8') as fp:
46
+ doc = html.parse(fp, parser=vd.utf8_parser, base_url=self.source.given)
29
47
  self.setKeys([self.column('name')])
30
48
  self.column('keys').hide()
31
49
  self.column('source').hide()
@@ -56,15 +74,16 @@ class HtmlLinksSheet(Sheet):
56
74
  ItemColumn('link', 2, width=40),
57
75
  ]
58
76
  def iterload(self):
77
+ lxml = vd.importExternal('lxml')
59
78
  from lxml.html import iterlinks
60
79
  root = self.source.getroot()
61
- root.make_links_absolute(self.source.docinfo.URL)
80
+ root.make_links_absolute(self.source.docinfo.URL, handle_failures='ignore')
62
81
  yield from iterlinks(root)
63
82
 
64
83
  def openRow(self, row):
65
84
  return vd.openSource(row[2])
66
85
 
67
- class HtmlElementsSheet(InferColumnsSheet):
86
+ class HtmlElementsSheet(Sheet):
68
87
  rowtype = 'links' # dict
69
88
  columns = [
70
89
  ItemColumn('__element__', width=0),
@@ -89,6 +108,7 @@ class HtmlTableSheet(Sheet):
89
108
  columns = []
90
109
 
91
110
  def iterload(self):
111
+ import lxml
92
112
  headers = []
93
113
 
94
114
  maxlinks = {} # [colnum] -> nlinks:int
@@ -108,8 +128,14 @@ class HtmlTableSheet(Sheet):
108
128
  for cell in r.getchildren():
109
129
  colspan = int(cell.attrib.get('colspan', 1))
110
130
  rowspan = int(cell.attrib.get('rowspan', 1))
131
+ if isinstance(cell, lxml.etree.CommentBase):
132
+ continue
111
133
  cellval = ' '.join(x.strip() for x in cell.itertext()) # text only without markup
112
- links = [urllib.parse.urljoin(self.source.base_url, x.get('href')) for x in cell.iter('a')]
134
+ links = [
135
+ vd.callNoExceptions(urllib.parse.urljoin, self.source.base_url, x.get('href')) or x.get('href')
136
+ for x in cell.iter('a')
137
+ ]
138
+
113
139
  maxlinks[colnum] = max(maxlinks.get(colnum, 0), len(links))
114
140
 
115
141
  if is_header(cell):
@@ -124,7 +150,7 @@ class HtmlTableSheet(Sheet):
124
150
  cellval = '' # use empty non-None value for subsequent rows in the rowspan
125
151
  else:
126
152
  while colnum >= len(row):
127
- row.append(None)
153
+ row.append((None, []))
128
154
  row[colnum] = (cellval, links)
129
155
 
130
156
  colnum += colspan
@@ -137,8 +163,11 @@ class HtmlTableSheet(Sheet):
137
163
  if headers:
138
164
  it = itertools.zip_longest(*headers, fillvalue='')
139
165
  else:
140
- it = list(list(x) for x in self.rows.pop(0))
141
- it += [''] * (ncols-len(it))
166
+ if len(self.rows) > 0:
167
+ it = list(list(x) for x in self.rows.pop(0))
168
+ it += [''] * (ncols-len(it))
169
+ else:
170
+ it = []
142
171
 
143
172
  for colnum, names in enumerate(it):
144
173
  name = '_'.join(str(x) for x in names if x)
@@ -177,7 +206,20 @@ def save_html(vd, p, *vsheets):
177
206
  fp.write('</tr>\n')
178
207
 
179
208
  fp.write('</table>')
180
- vd.status('%s save finished' % p)
209
+
210
+
211
+ @VisiData.lazy_property
212
+ def utf8_parser(vd):
213
+ lxml = vd.importExternal('lxml')
214
+ return lxml.html.HTMLParser(encoding='utf-8')
215
+ # return lxml.etree.HTMLParser(encoding='utf-8')
216
+
217
+
218
+ @VisiData.api
219
+ def HTML(vd, s):
220
+ lxml = vd.importExternal('lxml')
221
+ from lxml import html
222
+ return html.fromstring(s, parser=vd.utf8_parser)
181
223
 
182
224
 
183
225
  VisiData.save_htm = VisiData.save_html