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,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)
@@ -84,7 +84,7 @@ def save_fixed(vd, p, *vsheets):
84
84
  widths = {} # Column -> width:int
85
85
  # headers
86
86
  for col in Progress(sheet.visibleCols, gerund='sizing'):
87
- widths[col] = col.width or sheet.options.default_width or col.getMaxWidth(sheet.rows)
87
+ widths[col] = col.getMaxWidth(sheet.rows) #1849
88
88
  fp.write(('{0:%s} ' % widths[col]).format(col.name))
89
89
  fp.write('\n')
90
90
 
@@ -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,6 +74,7 @@ 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
80
  root.make_links_absolute(self.source.docinfo.URL)
@@ -64,7 +83,7 @@ class HtmlLinksSheet(Sheet):
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,6 +128,8 @@ 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
134
  links = [urllib.parse.urljoin(self.source.base_url, x.get('href')) for x in cell.iter('a')]
113
135
  maxlinks[colnum] = max(maxlinks.get(colnum, 0), len(links))
@@ -124,7 +146,7 @@ class HtmlTableSheet(Sheet):
124
146
  cellval = '' # use empty non-None value for subsequent rows in the rowspan
125
147
  else:
126
148
  while colnum >= len(row):
127
- row.append(None)
149
+ row.append((None, []))
128
150
  row[colnum] = (cellval, links)
129
151
 
130
152
  colnum += colspan
@@ -137,8 +159,11 @@ class HtmlTableSheet(Sheet):
137
159
  if headers:
138
160
  it = itertools.zip_longest(*headers, fillvalue='')
139
161
  else:
140
- it = list(list(x) for x in self.rows.pop(0))
141
- it += [''] * (ncols-len(it))
162
+ if len(self.rows) > 0:
163
+ it = list(list(x) for x in self.rows.pop(0))
164
+ it += [''] * (ncols-len(it))
165
+ else:
166
+ it = []
142
167
 
143
168
  for colnum, names in enumerate(it):
144
169
  name = '_'.join(str(x) for x in names if x)
@@ -177,7 +202,20 @@ def save_html(vd, p, *vsheets):
177
202
  fp.write('</tr>\n')
178
203
 
179
204
  fp.write('</table>')
180
- vd.status('%s save finished' % p)
205
+
206
+
207
+ @VisiData.lazy_property
208
+ def utf8_parser(vd):
209
+ lxml = vd.importExternal('lxml')
210
+ return lxml.html.HTMLParser(encoding='utf-8')
211
+ # return lxml.etree.HTMLParser(encoding='utf-8')
212
+
213
+
214
+ @VisiData.api
215
+ def HTML(vd, s):
216
+ lxml = vd.importExternal('lxml')
217
+ from lxml import html
218
+ return html.fromstring(s, parser=vd.utf8_parser)
181
219
 
182
220
 
183
221
  VisiData.save_htm = VisiData.save_html