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,148 @@
1
+ '''
2
+ # Matrix Chat Sheet
3
+ A list of messages from [:underline]{sheet.sourcename}[/].
4
+
5
+ - `Enter` to push a sheet with all the messages from [:underline]{sheet.cursorRow.room.display_name}[/].
6
+ - `a` to send a message to [:underline]{sheet.cursorRow.room.display_name}[/].
7
+ '''
8
+
9
+ from visidata import vd, VisiData, Sheet, Column, ItemColumn, date, asyncthread, AttrDict, vlen, Path
10
+
11
+
12
+ vd.option('matrix_token', '', 'matrix API token')
13
+ vd.option('matrix_user_id', '', 'matrix user ID associated with token')
14
+ vd.option('matrix_device_id', 'VisiData', 'device ID associated with matrix login')
15
+
16
+ vd.matrix_client = None
17
+
18
+
19
+ @VisiData.api
20
+ def openhttp_matrix(vd, p):
21
+ vd.importExternal('matrix_client')
22
+
23
+ if not vd.options.matrix_token:
24
+ from matrix_client.client import MatrixClient
25
+
26
+ username = vd.input(f'{p.given} username: ', record=False)
27
+ password = vd.input('password: ', record=False, display=False)
28
+
29
+ vd.matrix_client = MatrixClient(p.given)
30
+ matrix_token = vd.matrix_client.login(username, password, device_id=vd.options.matrix_device_id)
31
+
32
+ vd.setPersistentOptions(matrix_user_id=username, matrix_token=matrix_token)
33
+
34
+ vd.timeouts_before_idle = -1
35
+ return MatrixSheet(p.base_stem, source=p)
36
+
37
+ vd.open_matrix = vd.openhttp_matrix
38
+
39
+
40
+ class MatrixRoomsSheet(Sheet):
41
+ def iterload(self):
42
+ yield from vd.matrix_client.get_rooms().values()
43
+
44
+
45
+ class MatrixSheet(Sheet):
46
+ help = __doc__
47
+ columns = [
48
+ Column('room', width=12, getter=lambda c,r: r.room and r.room.display_name),
49
+ ItemColumn('sender', width=0),
50
+ Column('sender', width=10, getter=lambda c,r: r.sender.split(':')[0][1:]),
51
+ Column('timestamp', width=16, type=date, getter=lambda c,r: r and r.origin_server_ts/1000, fmtstr='%Y-%m-%d %H:%m'),
52
+ ItemColumn('type', width=15),
53
+ ItemColumn('content', width=0),
54
+ ItemColumn('content.body', width=50),
55
+ ItemColumn('received', type=vlen, width=0),
56
+ ItemColumn('content.msgtype'),
57
+ ]
58
+
59
+ @property
60
+ def sourcename(self):
61
+ from matrix_client.room import Room
62
+ if isinstance(self.source, Room):
63
+ return self.source.display_name or self.source.room_id
64
+ else:
65
+ return str(self.source)
66
+
67
+ @asyncthread
68
+ def reload(self):
69
+ from matrix_client.client import MatrixClient
70
+ from matrix_client.room import Room
71
+ if not vd.matrix_client:
72
+ vd.matrix_client = MatrixClient(self.source.given,
73
+ token=self.options.matrix_token,
74
+ user_id=self.options.matrix_user_id)
75
+
76
+ if isinstance(self.source, Room):
77
+ self.add_room(self.source)
78
+ self.get_room_messages(self.source)
79
+ return
80
+
81
+ for room in vd.matrix_client.get_rooms().values():
82
+ self.add_room(room)
83
+ room.backfill_previous_messages(limit=1)
84
+
85
+ vd.matrix_client.add_listener(self.global_event)
86
+ vd.matrix_client.add_ephemeral_listener(self.global_event)
87
+
88
+ vd.matrix_client.start_listener_thread(exception_handler=vd.exceptionCaught)
89
+
90
+ vd.matrix_client._sync()
91
+
92
+ vd.matrix_client.listen_for_events() # vd.matrix_client.sync(full_state=True)
93
+
94
+ def add_room(self, room):
95
+ room.add_listener(self.room_event)
96
+ room.add_ephemeral_listener(self.room_event)
97
+ room.add_state_listener(self.global_event)
98
+
99
+ @asyncthread
100
+ def get_room_messages(self, room):
101
+ while room.prev_batch:
102
+ ret = vd.matrix_client.api.get_room_messages(room.room_id, room.prev_batch, direction='b', limit=100)
103
+ for r in ret['chunk']:
104
+ r['room'] = room
105
+ self.addRow(r)
106
+
107
+ if 'end' not in ret or ret['end'] == room.prev_batch:
108
+ break
109
+ room.prev_batch = ret['end']
110
+
111
+ def addRow(self, r, **kwargs):
112
+ r = AttrDict(r)
113
+ if r.event_id not in self.event_index:
114
+ super().addRow(r, **kwargs)
115
+ self.event_index[r.event_id] = r
116
+
117
+ def global_event(self, chunk):
118
+ self.addRow(chunk)
119
+
120
+ def room_event(self, room, chunk):
121
+ ev = AttrDict(chunk)
122
+ t = chunk['type']
123
+ if t == 'm.receipt':
124
+ for msgid, content in chunk['content'].items():
125
+ msg = self.event_index.setdefault(msgid, {})
126
+ if 'received' not in msg:
127
+ msg['received'] = {}
128
+
129
+ for t, c in content.items():
130
+ assert t == 'm.read'
131
+ for userid, v in c.items():
132
+ msg['received'][(t, userid)] = v['ts']/1000
133
+ return
134
+
135
+ chunk['room'] = room
136
+ self.addRow(chunk)
137
+
138
+ def add_message(self, text):
139
+ vd.matrix_client.send_text(text)
140
+
141
+ def openRow(self, row):
142
+ return MatrixSheet(row.room.display_name, source=row.room, last_id=row.event_id)
143
+
144
+
145
+ MatrixSheet.init('event_index', dict)
146
+
147
+ MatrixSheet.addCommand('a', 'add-row', 'cursorRow.room.send_text(input(cursorRow.room.display_name+"> "))', 'send chat message to current room')
148
+ vd.addMenuItems('Row > Add > send matrix message > add-msg')
@@ -0,0 +1,306 @@
1
+ '''# RedditSheet
2
+
3
+ - [:keys]Ctrl+O[/] to open a browser tab to [:code]{sheet.cursorRow.display_name_prefixed}[/]
4
+ - [:keys]g Ctrl+O[/] to open browser windows for {sheet.nSelectedRows} selected subreddits
5
+
6
+ - [:keys]Enter[/] to open sheet with top ~1000 submissions for [:code]{sheet.cursorRow.display_name_prefixed}[/]
7
+ - [:keys]g Enter[/] to open sheet with top ~1000 submissions for {sheet.nSelectedRows} selected subreddits
8
+
9
+ - [:keys]ga[/] to append more subreddits matching input by name or description
10
+ '''
11
+
12
+ import visidata
13
+ from visidata import vd, VisiData, Sheet, AttrColumn, asyncthread, ENTER, anytype, date
14
+
15
+
16
+ vd.option('reddit_client_id', '', 'client_id for reddit api')
17
+ vd.option('reddit_client_secret', '', 'client_secret for reddit api')
18
+ vd.option('reddit_user_agent', visidata.__version__, 'user_agent for reddit api')
19
+
20
+
21
+ @VisiData.api
22
+ def open_reddit(vd, p):
23
+ vd.importExternal('praw')
24
+ vd.enable_requests_cache()
25
+
26
+ if not vd.options.reddit_client_id:
27
+ return RedditGuide('reddit_guide')
28
+
29
+ if p.given.startswith('r/') or p.given.startswith('/r/'):
30
+ return SubredditSheet(p.base_stem, source=p.base_stem.split('+'), search=(p.given[0]=='/'))
31
+
32
+ if p.given.startswith('u/') or p.given.startswith('/u/'):
33
+ return RedditorsSheet(p.base_stem, source=p.base_stem.split('+'), search=(p.given[0]=='/'))
34
+
35
+ return SubredditSheet(p.base_stem, source=p)
36
+
37
+ vd.new_reddit = vd.open_reddit
38
+
39
+ @VisiData.cached_property
40
+ def reddit(vd):
41
+ import praw
42
+ return praw.Reddit(check_for_updates=False, **vd.options.getall('reddit_'))
43
+
44
+
45
+ subreddit_hidden_attrs='''
46
+ name #accounts_active accounts_active_is_fuzzed advertiser_category
47
+ all_original_content allow_chat_post_creation allow_discovery
48
+ allow_galleries allow_images allow_polls allow_predictions
49
+ allow_predictions_tournament allow_videogifs allow_videos
50
+ banner_background_color banner_background_image banner_img banner_size
51
+ can_assign_link_flair can_assign_user_flair collapse_deleted_comments
52
+ comment_score_hide_mins community_icon community_reviewed @created
53
+ @created_utc description_html disable_contributor_requests
54
+ display_name display_name_prefixed emoji emojis_custom_size
55
+ emojis_enabled filters free_form_reports fullname has_menu_widget
56
+ header_img header_size header_title hide_ads icon_img icon_size
57
+ is_chat_post_feature_enabled is_crosspostable_subreddit
58
+ is_enrolled_in_new_modmail key_color lang link_flair_enabled
59
+ link_flair_position mobile_banner_image mod notification_level
60
+ original_content_tag_enabled over18 prediction_leaderboard_entry_type
61
+ primary_color public_description public_description_html public_traffic
62
+ quaran quarantine restrict_commenting restrict_posting show_media
63
+ show_media_preview spoilers_enabled submission_type submit_link_label
64
+ submit_text submit_text_html submit_text_label suggested_comment_sort
65
+ user_can_flair_in_sr user_flair_background_color user_flair_css_class
66
+ user_flair_enabled_in_sr user_flair_position user_flair_richtext
67
+ user_flair_template_id user_flair_text user_flair_text_color
68
+ user_flair_type user_has_favorited user_is_banned user_is_contributor
69
+ user_is_moderator user_is_muted user_is_subscriber user_sr_flair_enabled
70
+ user_sr_theme_enabled #videostream_links_count whitelist_status widgets
71
+ wiki wiki_enabled wls
72
+ '''
73
+
74
+ post_hidden_attrs='''
75
+ all_awardings allow_live_comments @approved_at_utc approved_by archived
76
+ author_flair_background_color author_flair_css_class author_flair_richtext
77
+ author_flair_template_id author_flair_text author_flair_text_color
78
+ author_flair_type author_fullname author_patreon_flair author_premium
79
+ awarders @banned_at_utc banned_by can_gild can_mod_post category clicked
80
+ comment_limit comment_sort content_categories contest_mode @created_utc
81
+ discussion_type distinguished domain edited flair fullname gilded
82
+ gildings hidden hide_score is_crosspostable is_meta is_original_content
83
+ is_reddit_media_domain is_robot_indexable is_self is_video likes
84
+ link_flair_background_color link_flair_css_class link_flair_richtext
85
+ link_flair_text link_flair_text_color link_flair_type locked media
86
+ media_embed media_only mod mod_note mod_reason_by mod_reason_title
87
+ mod_reports name no_follow num_crossposts num_duplicates num_reports
88
+ over_18 parent_whitelist_status permalink pinned pwls quarantine
89
+ removal_reason removed_by removed_by_category report_reasons saved
90
+ score secure_media secure_media_embed selftext_html send_replies
91
+ shortlink spoiler stickied subreddit_id subreddit_name_prefixed
92
+ subreddit_subscribers subreddit_type suggested_sort thumbnail
93
+ thumbnail_height thumbnail_width top_awarded_type total_awards_received
94
+ treatment_tags upvote_ratio user_reports #view_count visited
95
+ whitelist_status wls
96
+ '''
97
+
98
+ comment_hidden_attrs='''
99
+ all_awardings @approved_at_utc approved_by archived associated_award
100
+ author_flair_background_color author_flair_css_class author_flair_richtext
101
+ author_flair_template_id author_flair_text author_flair_text_color
102
+ author_flair_type author_fullname author_patreon_flair author_premium
103
+ awarders @banned_at_utc banned_by body_html can_gild can_mod_post
104
+ collapsed collapsed_because_crowd_control collapsed_reason comment_type
105
+ controversiality @created_utc distinguished fullname gilded gildings
106
+ is_root is_submitter likes link_id locked mod mod_note mod_reason_by
107
+ mod_reason_title mod_reports name no_follow num_reports parent_id
108
+ permalink removal_reason report_reasons saved #score #score_hidden
109
+ send_replies stickied submission subreddit_id subreddit_name_prefixed
110
+ subreddit_type top_awarded_type total_awards_received treatment_tags
111
+ user_reports
112
+ '''
113
+
114
+ redditor_hidden_attrs='''
115
+ #awardee_karma #awarder_karma @created @created_utc
116
+ fullname has_subscribed has_verified_email hide_from_robots icon_img id
117
+ is_employee is_friend is_gold is_mod pref_show_snoovatar
118
+ snoovatar_img snoovatar_size stream #total_karma verified
119
+ subreddit.banner_img subreddit.name subreddit.over_18 subreddit.public_description #subreddit.subscribers subreddit.title
120
+ '''
121
+
122
+ def hiddenCols(hidden_attrs):
123
+ coltypes = { t.icon:t.typetype for t in vd.typemap.values() if not t.icon.isalpha() }
124
+ for attr in hidden_attrs.split():
125
+ coltype = anytype
126
+ if attr[0] in coltypes:
127
+ coltype = coltypes.get(attr[0])
128
+ attr = attr[1:]
129
+ yield AttrColumn(attr, type=coltype, width=0)
130
+
131
+
132
+ class SubredditSheet(Sheet):
133
+ guide = __doc__
134
+ # source is a text list of subreddits
135
+ rowtype = 'subreddits' # rowdef: praw.Subreddit
136
+ nKeys=1
137
+ search=False
138
+ columns = [
139
+ AttrColumn('display_name_prefixed', width=15),
140
+ AttrColumn('active_user_count', type=int),
141
+ AttrColumn('subscribers', type=int),
142
+ AttrColumn('subreddit_type'),
143
+ AttrColumn('title'),
144
+ AttrColumn('description', width=50),
145
+ AttrColumn('url', width=10),
146
+ ] + list(hiddenCols(subreddit_hidden_attrs))
147
+
148
+ def iterload(self):
149
+ for name in self.source:
150
+ name = name.strip()
151
+ if self.search:
152
+ yield from vd.reddit.subreddits.search(name)
153
+ else:
154
+ try:
155
+ r = vd.reddit.subreddit(name)
156
+ r.display_name_prefixed
157
+ yield r
158
+ except Exception as e:
159
+ vd.exceptionCaught(e)
160
+
161
+ def openRow(self, row):
162
+ return RedditSubmissions(row.display_name_prefixed, source=row)
163
+
164
+ def openRows(self, rows):
165
+ comboname = '+'.join(row.display_name for row in rows)
166
+ return RedditSubmissions(comboname, source=vd.reddit.subreddit(comboname))
167
+
168
+
169
+ class RedditorsSheet(Sheet):
170
+ # source is a text list of usernames
171
+ rowtype = 'redditors' # rowdef: praw.Subreddit
172
+ nKeys=1
173
+ columns = [
174
+ AttrColumn('name', width=15),
175
+ AttrColumn('comment_karma', type=int),
176
+ AttrColumn('link_karma', type=int),
177
+ AttrColumn('comments'),
178
+ AttrColumn('submissions'),
179
+ ] + list(hiddenCols(redditor_hidden_attrs))
180
+
181
+ def iterload(self):
182
+ for name in self.source:
183
+ if self.search:
184
+ yield from vd.reddit.redditors.popular(name)
185
+ else:
186
+ yield vd.reddit.redditor(name)
187
+
188
+ def openRow(self, row):
189
+ return RedditSubmissions(row.fullname, source=row.submissions)
190
+
191
+ def openRows(self, rows):
192
+ comboname = '+'.join(row.name for row in rows)
193
+ return RedditSubmissions(comboname, source=vd.reddit.redditor(comboname).submissions)
194
+
195
+
196
+ class RedditSubmissions(Sheet):
197
+ guide = '''# Reddit Submissions
198
+
199
+ [:keys]Enter[/] to open sheet with comments for the current post
200
+ [:keys]ga[/] to add posts in this subreddit matching input'''
201
+
202
+ # source=ListingGenerator
203
+ rowtype='reddit posts' # rowdef: praw.Submission
204
+ nKeys=2
205
+ columns = [
206
+ AttrColumn('subreddit'),
207
+ AttrColumn('id', width=0),
208
+ AttrColumn('created', width=12, type=date),
209
+ AttrColumn('author'),
210
+ AttrColumn('ups', width=8, type=int),
211
+ AttrColumn('downs', width=8, type=int),
212
+ AttrColumn('num_comments', width=8, type=int),
213
+ AttrColumn('title', width=50),
214
+ AttrColumn('selftext', width=60),
215
+ AttrColumn('url'),
216
+ AttrColumn('comments', width=0),
217
+ ] + list(hiddenCols(post_hidden_attrs))
218
+
219
+ def iterload(self):
220
+ kind = 'new' # 'top'
221
+ f = getattr(self.source, kind, None)
222
+ if f:
223
+ yield from f(limit=10000)
224
+
225
+ def openRow(self, row):
226
+ return RedditComments(row.id, source=row.comments.list())
227
+
228
+
229
+ class RedditComments(Sheet):
230
+ # source=list of comments
231
+ rowtype='comments' # rowdef: praw.Comment
232
+ nKeys=2
233
+ columns=[
234
+ AttrColumn('subreddit', width=0),
235
+ AttrColumn('id', width=0),
236
+ AttrColumn('ups', width=4, type=int),
237
+ AttrColumn('downs', width=4, type=int),
238
+ AttrColumn('replies', type=list),
239
+ AttrColumn('created', type=date),
240
+ AttrColumn('author'),
241
+ AttrColumn('depth', type=int),
242
+ AttrColumn('body', width=60),
243
+ AttrColumn('edited', width=0),
244
+ ] + list(hiddenCols(comment_hidden_attrs))
245
+
246
+ def iterload(self):
247
+ yield from self.source
248
+
249
+ def openRow(self, row):
250
+ return RedditComments(row.id, source=row.replies)
251
+
252
+
253
+ class RedditGuide(RedditSubmissions):
254
+ guide = '''# Authenticate Reddit
255
+ The Reddit API must be configured before use.
256
+
257
+ 1. Login to Reddit and go to [:underline]https://www.reddit.com/prefs/apps[/].
258
+ 2. Create a "script" app. (Use "[:underline]http://localhost:8000[/]" for the redirect uri)
259
+ 3. Add credentials to visidatarc:
260
+
261
+ options.reddit_client_id = '...' # below the description in the upper left
262
+ options.reddit_client_secret = '...'
263
+
264
+ ## Use [:code]reddit[/] filetype for subreddits or users
265
+
266
+ Multiple may be specified, joined with "+".
267
+
268
+ vd r/commandline.reddit
269
+ vd u/gallowboob.reddit
270
+ vd r/rust+golang+python.reddit
271
+ vd u/spez+kn0thing.reddit
272
+ '''
273
+
274
+ @SubredditSheet.api
275
+ @asyncthread
276
+ def addRowsFromQuery(sheet, q):
277
+ for r in vd.reddit.subreddits.search(q):
278
+ sheet.addRow(r, index=sheet.cursorRowIndex+1)
279
+
280
+
281
+ @RedditSubmissions.api
282
+ @asyncthread
283
+ def addRowsFromQuery(sheet, q):
284
+ for r in sheet.source.search(q, limit=None):
285
+ sheet.addRow(r, index=sheet.cursorRowIndex+1)
286
+
287
+
288
+ @VisiData.api
289
+ def sysopen_subreddits(vd, *subreddits):
290
+ url = "https://www.reddit.com/r/"+"+".join(subreddits)
291
+ vd.launchBrowser(url)
292
+
293
+
294
+ SubredditSheet.addCommand('^O', 'sysopen-subreddit', 'sysopen_subreddits(cursorRow.display_name)', 'open browser window with subreddit')
295
+ SubredditSheet.addCommand('g^O', 'sysopen-subreddits', 'sysopen_subreddits(*(row.display_name for row in selectedRows))', 'open browser window with messages from selected subreddits')
296
+ SubredditSheet.addCommand('g'+ENTER, 'open-subreddits', 'vd.push(openRows(selectedRows))', 'open sheet with top ~1000 submissions for each selected subreddit')
297
+ SubredditSheet.addCommand('ga', 'add-subreddits-match', 'addRowsFromQuery(input("add subreddits matching: "))', 'add subreddits matching input by name or description')
298
+ RedditSubmissions.addCommand('ga', 'add-submissions-match', 'addRowsFromQuery(input("add posts matching: "))', 'add posts in this subreddit matching input')
299
+
300
+ vd.addMenuItems('''
301
+ File > Reddit > open selected subreddits > open-subreddits
302
+ File > Reddit > add > matching subreddits > add-subreddits-match
303
+ File > Reddit > add > matching submissions > add-submissions-match
304
+ File > Reddit > open in browser > subreddit in current row > sysopen-subreddit
305
+ File > Reddit > open in browser > selected subreddits > sysopen-subreddits
306
+ ''')