visidata 2.11.dev0__py3-none-any.whl → 3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- visidata/__init__.py +72 -91
- visidata/_input.py +263 -44
- visidata/_open.py +84 -29
- visidata/_types.py +22 -4
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +65 -25
- visidata/apps/__init__.py +0 -0
- visidata/apps/vdsql/__about__.py +8 -0
- visidata/apps/vdsql/__init__.py +5 -0
- visidata/apps/vdsql/__main__.py +27 -0
- visidata/apps/vdsql/_ibis.py +748 -0
- visidata/apps/vdsql/bigquery.py +61 -0
- visidata/apps/vdsql/clickhouse.py +53 -0
- visidata/apps/vdsql/setup.py +40 -0
- visidata/apps/vdsql/snowflake.py +67 -0
- visidata/apps/vgit/__init__.py +13 -0
- visidata/apps/vgit/__main__.py +3 -0
- visidata/apps/vgit/abort.py +23 -0
- visidata/apps/vgit/blame.py +76 -0
- visidata/apps/vgit/branch.py +153 -0
- visidata/apps/vgit/config.py +95 -0
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- visidata/apps/vgit/grep.py +37 -0
- visidata/apps/vgit/log.py +81 -0
- visidata/apps/vgit/main.py +55 -0
- visidata/apps/vgit/remote.py +57 -0
- visidata/apps/vgit/repos.py +71 -0
- visidata/apps/vgit/setup.py +37 -0
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- visidata/apps/vgit/statusbar.py +34 -0
- visidata/basesheet.py +59 -50
- visidata/canvas.py +251 -99
- visidata/choose.py +15 -11
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +84 -18
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +89 -114
- visidata/color.py +142 -56
- visidata/column.py +134 -131
- visidata/ddw/input.ddw +74 -79
- visidata/ddw/regex.ddw +57 -0
- visidata/ddwplay.py +33 -14
- visidata/deprecated.py +77 -3
- visidata/desktop/visidata.desktop +7 -0
- visidata/editor.py +12 -6
- visidata/errors.py +5 -1
- visidata/experimental/__init__.py +0 -0
- visidata/experimental/diff_sheet.py +29 -0
- visidata/experimental/digit_autoedit.py +6 -0
- visidata/experimental/gdrive.py +89 -0
- visidata/experimental/google.py +37 -0
- visidata/experimental/gsheets.py +79 -0
- visidata/experimental/live_search.py +37 -0
- visidata/experimental/liveupdate.py +45 -0
- visidata/experimental/mark.py +133 -0
- visidata/experimental/noahs_tapestry/__init__.py +1 -0
- visidata/experimental/noahs_tapestry/tapestry.py +147 -0
- visidata/experimental/rownum.py +73 -0
- visidata/experimental/slide_cells.py +26 -0
- visidata/expr.py +8 -4
- visidata/extensible.py +32 -6
- visidata/features/__init__.py +0 -0
- visidata/features/addcol_audiometadata.py +42 -0
- visidata/features/addcol_histogram.py +34 -0
- visidata/features/canvas_save_svg.py +69 -0
- visidata/features/change_precision.py +46 -0
- visidata/features/cmdpalette.py +163 -0
- visidata/features/colorbrewer.py +363 -0
- visidata/{colorsheet.py → features/colorsheet.py} +17 -16
- visidata/features/command_server.py +105 -0
- visidata/features/currency_to_usd.py +70 -0
- visidata/{customdate.py → features/customdate.py} +2 -0
- visidata/features/dedupe.py +132 -0
- visidata/{describe.py → features/describe.py} +17 -15
- visidata/features/errors_guide.py +26 -0
- visidata/features/expand_cols.py +202 -0
- visidata/{fill.py → features/fill.py} +4 -2
- visidata/{freeze.py → features/freeze.py} +11 -6
- visidata/features/graph_seaborn.py +79 -0
- visidata/features/helloworld.py +10 -0
- visidata/features/hint_types.py +17 -0
- visidata/{incr.py → features/incr.py} +5 -0
- visidata/{join.py → features/join.py} +107 -53
- visidata/features/known_cols.py +21 -0
- visidata/features/layout.py +62 -0
- visidata/{melt.py → features/melt.py} +33 -21
- visidata/features/normcol.py +118 -0
- visidata/features/open_config.py +7 -0
- visidata/features/open_syspaste.py +18 -0
- visidata/features/ping.py +157 -0
- visidata/features/procmgr.py +208 -0
- visidata/features/random_sample.py +6 -0
- visidata/{regex.py → features/regex.py} +47 -31
- visidata/features/reload_every.py +55 -0
- visidata/features/rename_col_cascade.py +30 -0
- visidata/features/scroll_context.py +60 -0
- visidata/features/select_equal_selected.py +11 -0
- visidata/features/setcol_fake.py +65 -0
- visidata/{slide.py → features/slide.py} +75 -21
- visidata/features/sparkline.py +48 -0
- visidata/features/status_source.py +20 -0
- visidata/{sysedit.py → features/sysedit.py} +2 -1
- visidata/features/sysopen_mailcap.py +46 -0
- visidata/features/term_extras.py +13 -0
- visidata/{transpose.py → features/transpose.py} +5 -4
- visidata/features/type_ipaddr.py +73 -0
- visidata/features/type_url.py +11 -0
- visidata/{unfurl.py → features/unfurl.py} +9 -9
- visidata/{window.py → features/window.py} +2 -2
- visidata/form.py +50 -21
- visidata/freqtbl.py +81 -33
- visidata/fuzzymatch.py +414 -0
- visidata/graph.py +105 -33
- visidata/guide.py +180 -0
- visidata/help.py +75 -44
- visidata/hint.py +39 -0
- visidata/indexsheet.py +109 -0
- visidata/input_history.py +55 -0
- visidata/interface.py +58 -0
- visidata/keys.py +17 -16
- visidata/loaders/__init__.py +9 -0
- visidata/loaders/_pandas.py +61 -21
- visidata/loaders/api_airtable.py +70 -0
- visidata/loaders/api_bitio.py +102 -0
- visidata/loaders/api_matrix.py +148 -0
- visidata/loaders/api_reddit.py +306 -0
- visidata/loaders/api_zulip.py +249 -0
- visidata/loaders/archive.py +41 -7
- visidata/loaders/arrow.py +7 -7
- visidata/loaders/conll.py +49 -0
- visidata/loaders/csv.py +25 -7
- visidata/loaders/eml.py +3 -4
- visidata/loaders/f5log.py +1204 -0
- visidata/loaders/fec.py +325 -0
- visidata/loaders/fixed_width.py +3 -5
- visidata/loaders/frictionless.py +3 -3
- visidata/loaders/geojson.py +8 -5
- visidata/loaders/google.py +48 -0
- visidata/loaders/graphviz.py +4 -4
- visidata/loaders/hdf5.py +4 -4
- visidata/loaders/html.py +48 -10
- visidata/loaders/http.py +84 -30
- visidata/loaders/imap.py +20 -10
- visidata/loaders/jrnl.py +52 -0
- visidata/loaders/json.py +83 -29
- visidata/loaders/jsonla.py +74 -0
- visidata/loaders/lsv.py +15 -11
- visidata/loaders/mailbox.py +40 -0
- visidata/loaders/markdown.py +1 -3
- visidata/loaders/mbtiles.py +4 -5
- visidata/loaders/mysql.py +11 -13
- visidata/loaders/npy.py +7 -7
- visidata/loaders/odf.py +4 -1
- visidata/loaders/orgmode.py +428 -0
- visidata/loaders/pandas_freqtbl.py +14 -20
- visidata/loaders/parquet.py +62 -6
- visidata/loaders/pcap.py +3 -3
- visidata/loaders/pdf.py +4 -3
- visidata/loaders/png.py +19 -13
- visidata/loaders/postgres.py +9 -8
- visidata/loaders/rec.py +7 -3
- visidata/loaders/s3.py +342 -0
- visidata/loaders/sas.py +5 -5
- visidata/loaders/scrape.py +186 -0
- visidata/loaders/shp.py +6 -5
- visidata/loaders/spss.py +5 -6
- visidata/loaders/sqlite.py +68 -28
- visidata/loaders/texttables.py +1 -1
- visidata/loaders/toml.py +60 -0
- visidata/loaders/tsv.py +61 -19
- visidata/loaders/ttf.py +19 -7
- visidata/loaders/unzip_http.py +6 -5
- visidata/loaders/usv.py +1 -1
- visidata/loaders/vcf.py +16 -16
- visidata/loaders/vds.py +10 -7
- visidata/loaders/vdx.py +30 -5
- visidata/loaders/xlsb.py +8 -1
- visidata/loaders/xlsx.py +145 -25
- visidata/loaders/xml.py +6 -3
- visidata/loaders/xword.py +4 -4
- visidata/loaders/yaml.py +15 -5
- visidata/macos.py +1 -1
- visidata/macros.py +130 -41
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -154
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -147
- visidata/man/vd.txt +291 -151
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +79 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +131 -43
- visidata/pivot.py +74 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +50 -201
- visidata/rename_col.py +20 -0
- visidata/save.py +42 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -24
- visidata/sheets.py +229 -257
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +113 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +62 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +17 -0
- visidata/tests/test_menu.py +14 -0
- visidata/tests/test_path.py +13 -4
- visidata/text_source.py +53 -0
- visidata/textsheet.py +10 -3
- visidata/theme.py +44 -0
- visidata/themes/__init__.py +0 -0
- visidata/themes/ascii8.py +84 -0
- visidata/themes/asciimono.py +84 -0
- visidata/themes/light.py +17 -0
- visidata/threads.py +87 -39
- visidata/tuiwin.py +22 -0
- visidata/type_currency.py +22 -3
- visidata/type_date.py +31 -9
- visidata/type_floatsi.py +5 -1
- visidata/undo.py +18 -6
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/vd.1 +302 -147
- {visidata-2.11.dev0.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +302 -147
- visidata-3.0.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/METADATA +13 -11
- visidata-3.0.dist-info/RECORD +257 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -1
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.dev0.dist-info/RECORD +0 -142
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.dev0.data → visidata-3.0.data}/scripts/vd +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.dev0.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
visidata/loaders/fec.py
ADDED
@@ -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
|
+
})
|
visidata/loaders/fixed_width.py
CHANGED
@@ -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.
|
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.
|
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.
|
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)
|
visidata/loaders/frictionless.py
CHANGED
@@ -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.
|
5
|
+
return FrictionlessIndexSheet(p.base_stem, source=p)
|
6
6
|
|
7
7
|
class FrictionlessIndexSheet(IndexSheet):
|
8
8
|
def iterload(self):
|
9
|
-
|
10
|
-
self.dp = datapackage.Package(self.source.
|
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'))
|
visidata/loaders/geojson.py
CHANGED
@@ -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,
|
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.
|
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.
|
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.
|
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
|
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
|
visidata/loaders/graphviz.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
from visidata import vd, options, TypedWrapper, asyncthread, Progress
|
2
|
-
from visidata import wrapply,
|
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.
|
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 =
|
32
|
-
downdst =
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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.
|
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
|
-
|
27
|
-
|
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(
|
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
|
-
|
141
|
-
|
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
|
-
|
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
|