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,89 @@
1
+ import visidata
2
+ from visidata import vd, VisiData, Sheet, IndexSheet, SequenceSheet, ColumnItem, Path, AttrDict, ColumnAttr, asyncthread, Progress, ColumnExpr, date
3
+
4
+ from .gsheets import GSheetsIndex
5
+
6
+ @VisiData.api
7
+ def open_gdrive(vd, p):
8
+ return GDriveSheet(p.base_stem)
9
+
10
+
11
+ FILES_FIELDS_VISIBLE='''name size modifiedTime mimeType description'''.split()
12
+
13
+ FILES_FIELDS='''
14
+ id name size modifiedTime mimeType description owners
15
+ starred properties spaces version webContentLink webViewLink sharingUser lastModifyingUser shared
16
+ ownedByMe originalFilename md5Checksum size quotaBytesUsed headRevisionId imageMediaMetadata videoMediaMetadata parents
17
+ exportLinks contentRestrictions contentHints trashed
18
+ '''.split()
19
+
20
+
21
+ @VisiData.cached_property
22
+ def _drivebuild(vd):
23
+ return vd.google_discovery.build("drive", "v3", credentials=vd.google_auth('drive.readonly'))
24
+
25
+ @VisiData.cached_property
26
+ def _gdrive(self):
27
+ return vd.google_discovery.build("drive", "v3", credentials=vd.google_auth('drive.readonly')).files()
28
+
29
+ @VisiData.cached_property
30
+ def _gdrive_rw(self):
31
+ return vd.google_discovery.build("drive", "v3", credentials=vd.google_auth('drive')).files()
32
+
33
+
34
+ class GDriveSheet(Sheet):
35
+ rowtype='files' # rowdef: AttrDict of result from Google Drive files.list API
36
+ defer=True
37
+ columns = [
38
+ ColumnItem('name'),
39
+ ColumnItem('size', type=int),
40
+ ColumnItem('modifiedTime', type=date),
41
+ ColumnItem('mimeType'),
42
+ ColumnItem('name'),
43
+ ColumnExpr('owner', expr='owners[0]["displayName"]')
44
+ ] + [
45
+ ColumnItem(x, width=0) for x in FILES_FIELDS if x not in FILES_FIELDS_VISIBLE
46
+ ]
47
+
48
+ def iterload(self):
49
+ self.results = []
50
+ page_token = None
51
+ while True:
52
+ ret = vd._gdrive.list(
53
+ pageSize=1000,
54
+ pageToken=page_token,
55
+ fields="nextPageToken, files(%s)" % ','.join(FILES_FIELDS)
56
+ ).execute()
57
+
58
+ self.results.append(ret)
59
+
60
+ for r in ret.get('files', []):
61
+ yield AttrDict(r)
62
+
63
+ page_token = ret.get('nextPageToken', None)
64
+ if not page_token:
65
+ break
66
+
67
+ def openRow(self, r):
68
+ if r.mimeType == 'application/vnd.google-apps.spreadsheet':
69
+ return GSheetsIndex(r.name, source=r.id)
70
+ if r.mimeType.startswith('image'):
71
+ return vd.launchBrowser(r.webViewLink)
72
+ return vd.openSource(r.webContentLink)
73
+
74
+ @asyncthread
75
+ def deleteFile(self, **kwargs):
76
+ with Progress(total=1) as prog:
77
+ vd._gdrive_rw.delete(**kwargs).execute()
78
+ prog.addProgress(1)
79
+
80
+ @asyncthread
81
+ def putChanges(self):
82
+ adds, mods, dels = self.getDeferredChanges()
83
+
84
+ for row in Progress(dels.values()):
85
+ self.deleteFile(fileId=row.id)
86
+
87
+ vd.sync()
88
+ self.preloadHook()
89
+ self.reload()
@@ -0,0 +1,37 @@
1
+ '''
2
+ # Using VisiData with Google Sheets and Google Drive
3
+
4
+ ## Setup and Authentication
5
+
6
+ Add to .visidatarc:
7
+
8
+ import visidata.experimental.google
9
+
10
+ When VisiData attempts to use the Google API, it uses the "web authentication flow", which causes a web page to open asking for permissions to read and/or write your Google Sheets.
11
+ After granting permissions, VisiData caches the auth token in the .visidata directory. Remove `.visidata/google-*.pickle` to unauthenticate.
12
+
13
+ ## Load a Google Sheet into VisiData
14
+
15
+ Use VisiData to open the URL or spreadsheet id, with filetype `g` (or `gsheets`):
16
+
17
+ vd https://docs.google.com/spreadsheets/d/1WV0JI_SsGfmoocXWJILK2nhfcxU1H7roqL1HE7zBdsY/ -f g
18
+
19
+ VisiData assumes the first row is the header row with column names.
20
+
21
+ ## Save one or more sheets in VisiData as a Google Sheet
22
+
23
+ Save to `<sheetname>.g` using either `Ctrl+S` (current sheet only) or `g Ctrl+S` (all sheets on the sheet stack).
24
+ <sheetname> will be the visible name of the Spreadsheet in Google Drive; each sheet tab within the Spreadsheet will be named according to the sheet name within VisiData.
25
+
26
+ ## List files in Google Drive
27
+
28
+ Use the `gdrive` filetype (the path doesn't matter):
29
+
30
+ vd . -f gdrive
31
+
32
+ - Files can be marked for deletion with `d` and execute those deletions with `z Ctrl+S` (same as on the DirSheet for the local filesystem).
33
+ - Images can be viewed with `Enter` (in browser).
34
+ '''
35
+
36
+ import visidata.experimental.gdrive
37
+ import visidata.experimental.gsheets
@@ -0,0 +1,79 @@
1
+ import re
2
+ from visidata import vd, VisiData, Sheet, IndexSheet, SequenceSheet, ColumnItem, Path, AttrDict, ColumnAttr, asyncthread
3
+
4
+ SPREADSHEET_FIELDS='properties sheets namedRanges spreadsheetUrl developerMetadata dataSources dataSourceSchedules'.split()
5
+ SHEET_FIELDS='merges conditionalFormats filterViews protectedRanges basicFilter charts bandedRanges developerMetadata rowGroups columnGroups slicers'.split()
6
+
7
+ @VisiData.api
8
+ def open_gsheets(vd, p):
9
+ m = re.search(r'([A-z0-9_]{44})', p.given)
10
+ if m:
11
+ return GSheetsIndex(p.base_stem, source=m.groups()[0])
12
+
13
+ vd.open_g = vd.open_gsheets
14
+
15
+ @VisiData.lazy_property
16
+ def google_discovery(self):
17
+ googleapiclient = vd.importExternal('googleapiclient', 'google-api-python-client')
18
+ from googleapiclient import discovery
19
+ return discovery
20
+
21
+
22
+ @VisiData.cached_property
23
+ def _gsheets(vd):
24
+ return vd.google_discovery.build("sheets", "v4", credentials=vd.google_auth('spreadsheets.readonly')).spreadsheets()
25
+
26
+
27
+ @VisiData.cached_property
28
+ def _gsheets_rw(vd):
29
+ return vd.google_discovery.build("sheets", "v4", credentials=vd.google_auth('spreadsheets')).spreadsheets()
30
+
31
+
32
+ class GSheetsIndex(Sheet):
33
+ columns = [
34
+ ColumnAttr('title', 'properties.title'),
35
+ ColumnAttr('type', 'properties.sheetType', width=0),
36
+ ColumnAttr('nRows', 'properties.gridProperties.rowCount', type=int),
37
+ ColumnAttr('nCols', 'properties.gridProperties.columnCount', type=int),
38
+ ]
39
+ def iterload(self):
40
+ googlesheet = vd._gsheets.get(spreadsheetId=self.source, fields=','.join(SPREADSHEET_FIELDS)).execute()
41
+ vd.status(googlesheet['properties']['title'])
42
+
43
+ for gsheet in googlesheet['sheets']:
44
+ yield AttrDict(gsheet)
45
+
46
+ def openRow(self, r):
47
+ return GSheet(r.properties.title, source=self.source)
48
+
49
+
50
+ class GSheet(SequenceSheet):
51
+ '.source is gsheet id; .name is sheet name'
52
+ def iterload(self):
53
+ result = vd._gsheets.values().get(spreadsheetId=self.source, range=self.name).execute()
54
+ yield from result.get('values', [])
55
+
56
+
57
+ @VisiData.api
58
+ def save_gsheets(vd, p, *sheets):
59
+ gsheet = vd._gsheets_rw.create(body={
60
+ 'properties': { 'title': p.base_stem },
61
+ 'sheets': list({'properties': { 'title': vs.name }} for vs in sheets),
62
+ }, fields='spreadsheetId').execute()
63
+
64
+ gsheetId = gsheet.get('spreadsheetId')
65
+ vd.status(f'https://docs.google.com/spreadsheets/d/{gsheetId}/')
66
+
67
+ for vs in sheets:
68
+ rows = [list(c.name for c in vs.visibleCols)]
69
+ rows += list(list(val for col, val in row.items())
70
+ for row in vs.iterdispvals(*vs.visibleCols, format=True))
71
+
72
+ vd._gsheets_rw.values().append(
73
+ spreadsheetId=gsheetId,
74
+ valueInputOption='RAW',
75
+ range=vs.name,
76
+ body=dict(values=rows)
77
+ ).execute()
78
+
79
+ vd.save_g = vd.save_gsheets
@@ -0,0 +1,37 @@
1
+ from copy import copy
2
+
3
+ from visidata import Sheet, vd, asyncsingle
4
+
5
+ @Sheet.api
6
+ def dup_search(sheet, cols='cursorCol'):
7
+ vs = copy(sheet)
8
+ vs.name += "_search"
9
+ vs.rows = sheet.rows
10
+ vs.source = sheet
11
+ vs.search = ''
12
+
13
+ @asyncsingle
14
+ def live_search_async(val, status=False):
15
+ if not val:
16
+ vs.rows = vs.source.rows
17
+ else:
18
+ vs.rows = []
19
+ for i in vd.searchRegex(vs.source, regex=val, columns=cols, printStatus=status):
20
+ vs.addRow(vs.source.rows[i])
21
+
22
+ def live_search(val):
23
+ vs.draw(vs._scr)
24
+ vd.drawRightStatus(vs._scr, vs)
25
+ val = val.rstrip('\n')
26
+ if val == vs.search:
27
+ return
28
+ vs.search = val
29
+ live_search_async(val, sheet=vs, status=False)
30
+
31
+ vd.input("search regex: ", updater=live_search)
32
+ vd.push(vs)
33
+ vs.name = vs.source.name+'_'+vs.search
34
+
35
+
36
+ Sheet.addCommand('^[s', 'dup-search', 'dup_search("cursorCol")', 'search for regex forwards in current column, creating duplicate sheet with matching rows live')
37
+ Sheet.addCommand('g^[s', 'dup-search-cols', 'dup_search("visibleCols")', 'search for regex forwards in all columns, creating duplicate sheet with matching rows live')
@@ -0,0 +1,45 @@
1
+ from visidata import Column, vd, ColumnExpr, CompleteExpr, EscapeException, Sheet
2
+
3
+
4
+ @Column.api
5
+ def updateExpr(col, val):
6
+ col.name = val
7
+ try:
8
+ col.expr = val
9
+ except SyntaxError:
10
+ col.expr = None
11
+
12
+ col.sheet.draw(col.sheet._scr)
13
+
14
+
15
+ @Column.api # expr.setter
16
+ def expr(self, expr):
17
+ try:
18
+ self.compiledExpr = compile(expr, '<expr>', 'eval') if expr else None
19
+ self._expr = expr
20
+ except SyntaxError as e:
21
+ self._expr = None
22
+
23
+
24
+ @Sheet.api
25
+ def addcol_expr(sheet):
26
+ try:
27
+ c = sheet.addColumnAtCursor(ColumnExpr("", width=sheet.options.default_width))
28
+ oldidx = sheet.cursorVisibleColIndex
29
+ sheet.cursorVisibleColIndex = sheet.visibleCols.index(c)
30
+
31
+ expr = sheet.editCell(sheet.cursorVisibleColIndex, -1,
32
+ completer=CompleteExpr(sheet),
33
+ updater=lambda val,col=c: col.updateExpr(val))
34
+
35
+ c.expr = expr or vd.fail("no expr")
36
+ c.name = expr
37
+ c.width = None
38
+ except (Exception, EscapeException):
39
+ sheet.columns.remove(c)
40
+ sheet.cursorVisibleColIndex = oldidx
41
+ raise
42
+
43
+
44
+ Sheet.addCommand(None, 'addcol-expr', 'sheet.addcol_expr()', "create new column from Python expression, updating the column's calculated values live")
45
+ Sheet.addCommand(None, 'addcol-new', 'c=addColumnAtIndex(SettableColumn(width=options.default_width)); draw(sheet._scr); cursorVisibleColIndex=visibleCols.index(c); c.name=editCell(cursorVisibleColIndex, -1); c.width=None', 'append new column, updating the column name live')
@@ -0,0 +1,133 @@
1
+ '''
2
+ Marking selected rows with a keystroke, selecting marked rows,
3
+ and viewing lists of marks and their rows.
4
+ '''
5
+
6
+ from copy import copy
7
+ from visidata import vd, asyncthread, vlen, VisiData, TableSheet, ColumnItem, RowColorizer
8
+
9
+ @VisiData.lazy_property
10
+ def marks(vd):
11
+ return MarksSheet('marks')
12
+
13
+
14
+ class MarkSheet(TableSheet):
15
+ pass
16
+
17
+
18
+ class MarksSheet(TableSheet):
19
+ '''
20
+ The Marks Sheet shows all marks in use (on all sheets) and how many rows have each mark.
21
+ '''
22
+ rowtype = "marks" # rowdef: [mark, color, [rows]]
23
+ columns = [
24
+ ColumnItem('mark', 0),
25
+ ColumnItem('color', 1),
26
+ ColumnItem('rows', 2, type=vlen),
27
+ ]
28
+ colorizers = [
29
+ RowColorizer(2, None, lambda s,c,r,v: r and r[1])
30
+ ]
31
+ def __init__(self, *args, **kwargs):
32
+ super().__init__(*args, **kwargs)
33
+ self.marknotes = list('0123456789')
34
+ self.marks = [] #
35
+ self.markedRows = {} # rowid(row): [row, set(marks)]
36
+ self.rows = []
37
+
38
+ def getColor(self, sheet, row):
39
+ mark = self.getMark(sheet, row)
40
+ if not mark:
41
+ return ''
42
+ return self.getMarkRow(sheet, mark)[1]
43
+
44
+ def getMark(self, sheet, row):
45
+ mrow = self.markedRows.get(sheet.rowid(row), None)
46
+ if not mrow:
47
+ return ''
48
+ if mrow[1]:
49
+ return next(iter(mrow[1])) # first item in set
50
+
51
+ def getMarks(self, row):
52
+ 'Return set of all marks for given row'
53
+ return self.markedRows[self.rowid(row)][1]
54
+
55
+ def isMarked(self, row, mark):
56
+ 'Return True if given row has given mark'
57
+ return mark in self.getMarks(row)
58
+
59
+ def getMarkRow(self, sheet, mark):
60
+ for r in self.rows:
61
+ if r[0] == mark:
62
+ return r
63
+ r = [mark, 'color_note_type', MarkSheet('mark_', rows=[], columns=copy(sheet.columns))]
64
+ self.addRow(r)
65
+ return r
66
+
67
+ def setMark(self, sheet, row, mark):
68
+ rowid = self.rowid(row)
69
+ if rowid not in self.markedRows:
70
+ self.markedRows[rowid] = [row, set(mark)]
71
+ else:
72
+ self.markedRows[rowid][1].add(mark)
73
+
74
+ vd.marks.getMarkRow(sheet, mark)[2].addRow(row)
75
+
76
+ def unsetMark(self, sheet, row, mark):
77
+ rowid = self.rowid(row)
78
+ if rowid in self.markedRows:
79
+ self.markedRows[rowid][1].remove(mark)
80
+ vd.marks.getMarkRow(sheet, mark)[2].deleteBy(lambda r,x=row: r is x)
81
+
82
+ def inputmark(self):
83
+ return vd.inputsingle('mark: ') or self.marknotes.pop(0)
84
+
85
+ def openRow(self, row):
86
+ return row[2]
87
+
88
+
89
+ @VisiData.api
90
+ @asyncthread
91
+ def mark(vd, sheet, rows, m):
92
+ for r in rows:
93
+ vd.marks.setMark(sheet, r, m)
94
+
95
+ @VisiData.api
96
+ @asyncthread
97
+ def unmark(vd, sheet, rows, m):
98
+ for r in rows:
99
+ vd.marks.unsetMark(sheet, r, m)
100
+
101
+
102
+ vd.rowNoters.insert(0, lambda sheet, row: vd.marks.getMark(sheet, row))
103
+
104
+ TableSheet.colorizers.append(RowColorizer(2, None, lambda s,c,r,v: not c and r and vd.marks.getColor(s, r)))
105
+
106
+ TableSheet.addCommand('', 'mark-row', 'vd.mark(sheet, [cursorRow], vd.marks.inputmark())', '')
107
+ TableSheet.addCommand('', 'unmark-row', 'vd.unmark(sheet, [cursorRow], vd.marks.inputmark())', '')
108
+ TableSheet.addCommand('', 'mark-selected', 'vd.mark(sheet, selectedRows, vd.marks.inputmark())', '')
109
+ TableSheet.addCommand('', 'unmark-selected', 'vd.unmark(sheet, selectedRows, vd.marks.inputmark())', '')
110
+
111
+ TableSheet.addCommand('', 'select-marks', 'select(gatherBy(lambda r,mark=vd.marks.inputmark(): vd.marks.isMarked(r, mark)), progress=False)', '')
112
+ TableSheet.addCommand('', 'stoggle-marks', 'toggle(gatherBy(lambda r,mark=vd.marks.inputmark(): vd.marks.isMarked(r, mark)), progress=False)', '')
113
+ TableSheet.addCommand('', 'unselect-marks', 'unselect(gatherBy(lambda r,mark=vd.marks.inputmark(): vd.marks.isMarked(r, mark)), progress=False)', '')
114
+
115
+ TableSheet.addCommand('', 'open-marks', 'vd.push(vd.marks)', '')
116
+
117
+ TableSheet.addCommand('', 'go-prev-mark', 'moveToNextRow(lambda row,mark=vd.marks.inputmark(): vd.marks.isMarked(row, mark), reverse=True, msg="no previous marked row")', 'go up current column to previous row with given mark')
118
+ TableSheet.addCommand('', 'go-next-mark', 'moveToNextRow(lambda row,mark=vd.marks.inputmark(): vd.marks.isMarked(row, mark), msg="no next marked row")', 'go down current column to next row with given mark')
119
+
120
+
121
+ vd.addMenuItems('''
122
+ View > Marks > open-marks
123
+ Row > Mark > open Marks Sheet > open-marks
124
+ Row > Mark > current row > mark-row
125
+ Row > Mark > selected rows > mark-selected
126
+ Row > Unmark > current row > unmark-row
127
+ Row > Unmark > selected rows > unmark-selected
128
+ Row > Select > marked rows > select-marks
129
+ Row > Unselect > marked rows > unselect-marks
130
+ Row > Toggle select > marked rows > stoggle-marks
131
+ Row > Goto > next marked row > go-next-mark
132
+ Row > Goto > previous marked row > go-prev-mark
133
+ ''')
@@ -0,0 +1 @@
1
+ from . import tapestry
@@ -0,0 +1,147 @@
1
+ import json
2
+ import time
3
+ import collections
4
+ import random
5
+ from functools import cached_property
6
+ from base64 import b64encode
7
+
8
+ from visidata import vd, VisiData, Canvas, Animation, Path, asyncthread, clipdraw, colors, ItemColumn, Sheet, wraptext
9
+
10
+ vd.theme_option('color_hint', 'black on yellow', '')
11
+
12
+ @VisiData.api
13
+ def getNoahsPath(vd, name):
14
+ return Path(vd.pkg_resources_files('visidata')/f'experimental/noahs_tapestry/{name}')
15
+
16
+ @VisiData.api
17
+ def openNoahsText(vd, name):
18
+ return vd.getNoahsPath(name).open(encoding='utf-8')
19
+
20
+ @VisiData.cached_property
21
+ def noahsDatabase(vd):
22
+ return vd.open_sqlite(vd.getNoahsPath('noahs.sqlite'))
23
+
24
+ class NoahsPuzzle(Sheet):
25
+ guide = '''
26
+ # Puzzle {sheet.puznum}
27
+ - `Shift+A` to input a solution to the puzzle
28
+ - `Shift+Y` to attempt the current cell as the solution
29
+ - `Shift+B` to open Noah's Database Backup
30
+ - `Shift+V` to view Noah's Tapestry
31
+ '''
32
+ rowtype = 'lines' # rowdef: [linenum, text]
33
+ filetype = 'txt'
34
+ columns = [
35
+ ItemColumn('linenum', 0, type=int, width=0),
36
+ ItemColumn('text', 1, width=80, displayer='full'),
37
+ ]
38
+ precious = False
39
+
40
+ def iterload(self):
41
+ clues = json.loads(vd.getNoahsPath(f'clues.json').read_text())
42
+ source = vd.getNoahsPath(f'puzzle{self.puznum}.md')
43
+ winWidth = 78
44
+ formatted_text = source.open(encoding='utf-8').read().format(**clues)
45
+ for startingLine, text in enumerate(formatted_text.splitlines()):
46
+ text = text.strip()
47
+ if text:
48
+ for i, (L, _) in enumerate(wraptext(str(text), width=winWidth)):
49
+ yield [startingLine+i+1, L]
50
+ else:
51
+ yield [startingLine+1, text]
52
+
53
+ @VisiData.cached_property
54
+ def noahsSolutions(vd):
55
+ return json.loads(vd.getNoahsPath(f'solutions.json').read_text())
56
+
57
+ @VisiData.api
58
+ def solve_puzzle(vd, answer):
59
+ puznum = vd.noahsCurrentPuznum
60
+ if b64encode(answer.encode()).decode() != vd.noahsSolutions[f'p{puznum}']:
61
+ vd.fail("Hmmm, that doesn't seem right. Try again?")
62
+
63
+ vd.noahsTapestry.solved.add(puznum)
64
+ vd.status(f'Correct! The candle is now lit.')
65
+ vd.push(vd.noahsTapestry)
66
+
67
+
68
+ class Tapestry(Canvas):
69
+ @property
70
+ def guide(self):
71
+ ret = ''
72
+ if vd.screenWidth < 120 or vd.screenHeight < 50:
73
+ ret = f'''
74
+ # [:black on yellow]WARNING: TERMINAL TOO SMALL[/]
75
+ Please expand your terminal to at least 120x50 (currently {vd.screenWidth}x{vd.screenHeight})
76
+ '''
77
+
78
+ ret += '''
79
+ # Noah's Tapestry
80
+ An interactive data game
81
+
82
+ - `Shift+N` to open the next puzzle
83
+ '''
84
+
85
+ return ret
86
+
87
+ solved = set()
88
+ def reload(self):
89
+ self.noahs_menorah = Animation(vd.openNoahsText('menorah.ddw'))
90
+ self.noahs_tapestry = Animation(vd.openNoahsText('tapestry.ddw'))
91
+ self.noahs_flame = Animation(vd.openNoahsText('flame.ddw'))
92
+ self.solved = set()
93
+
94
+ # self.keep_running()
95
+
96
+ @asyncthread
97
+ def keep_running(self):
98
+ while True:
99
+ time.sleep(1)
100
+
101
+ def draw(self, scr):
102
+ solvedays = ['menorah']+[f'day{i}' for i in self.solved]
103
+ t = time.time()
104
+ self.noahs_menorah.draw(scr, t=0, y=30, x=19)
105
+ if not self.solved:
106
+ clipdraw(scr, 22, 52, "Light the [:italic]shamash[/]", colors['255'])
107
+ return
108
+
109
+ self.noahs_menorah.draw(scr, t=0.1, y=30, x=19, tags=solvedays)
110
+ self.noahs_tapestry.draw(scr, t=t, tags=solvedays)
111
+
112
+ for i in self.solved:
113
+ xs = [58, 22+9*8, 22+9*7, 22+9*6, 22+9*5, 22+27, 22+18, 22+9, 22, 22]
114
+ ys = [28, 32, 32, 32, 32, 32, 32, 32, 32, 32]
115
+ x = xs[i]
116
+ y = ys[i]
117
+
118
+ self.noahs_flame.draw(scr, t=t+(i+random.random())*0.2, y=y, x=x)
119
+
120
+ def open_puzzle(self, puznum=None):
121
+ if puznum is None:
122
+ puznum = 0
123
+ if self.solved:
124
+ puznum = max(self.solved)+1
125
+
126
+ vs = NoahsPuzzle('puzzle', str(puznum), source=self, puznum=puznum)
127
+ vs.ensureLoaded()
128
+ vd.noahsCurrentPuznum = puznum
129
+ return vs
130
+
131
+
132
+ @VisiData.lazy_property
133
+ def noahsTapestry(vd):
134
+ vd.curses_timeout = 50
135
+ return Tapestry('noahs', 'tapestry')
136
+
137
+
138
+ NoahsPuzzle.options.color_default = '178 yellow on 232 black'
139
+
140
+ vd.addCommand('Shift+B', 'open-noahs-database', 'vd.push(noahsDatabase)', "open database for Noah's Tapestry")
141
+ vd.addCommand('Shift+V', 'open-noahs-tapestry', 'vd.push(noahsTapestry)', "open Noah's Tapestry")
142
+ Tapestry.addCommand('Shift+N', 'open-puzzle-next', 'vd.push(open_puzzle())', 'open next unsolved puzzle')
143
+ NoahsPuzzle.addCommand('Shift+A', 'solve-puzzle-input', 'solve_puzzle(input("Answer: "))', 'input an answer to the current puzzle')
144
+ Sheet.addCommand('Shift+Y', 'solve-puzzle-cell', 'solve_puzzle(cursorValue)', 'input an answer to the current puzzle')
145
+ for i in range(9):
146
+ Tapestry.addCommand(f'{i}', f'open-puzzle-{i}', f'vd.push(open_puzzle({i}))')
147
+ Tapestry.addCommand(f'Alt+{i}', f'solve-puzzle-force-{i}', f'sheet.solved.add({i})')
@@ -0,0 +1,73 @@
1
+ from visidata import *
2
+ from functools import wraps, partial
3
+
4
+
5
+ @asyncthread
6
+ @Sheet.api
7
+ def calcRowIndex(sheet, indexes):
8
+ for rownum, r in enumerate(sheet.rows):
9
+ indexes[sheet.rowid(r)] = rownum
10
+
11
+
12
+ @Sheet.lazy_property
13
+ def _rowindex(sheet):
14
+ ret = {}
15
+ sheet.calcRowIndex(ret)
16
+ return ret
17
+
18
+
19
+ @Sheet.api
20
+ def rowindex(sheet, row):
21
+ 'Returns the rowindex given the row. May spawn a thread to compute underlying _rowindex.'
22
+ return sheet._rowindex.get(sheet.rowid(row))
23
+
24
+
25
+ @Sheet.api
26
+ def prev(sheet, row):
27
+ 'Return the row previous to the given row.'
28
+ rownum = max(sheet.rowindex(row)-1, 0)
29
+ return LazyComputeRow(sheet, sheet.rows[rownum])
30
+
31
+
32
+ @Sheet.api
33
+ def addcol_rowindex(sheet, newcol):
34
+ oldAddRow = sheet.addRow
35
+ def rownum_addRow(sheet, col, row, index=None):
36
+ if index is None:
37
+ index = len(sheet.rows)
38
+
39
+ col._rowindex[sheet.rowid(row)] = index
40
+ return oldAddRow(row, index)
41
+
42
+ # wrapper addRow to keep the index up to date
43
+ sheet.addRow = wraps(oldAddRow)(partial(rownum_addRow, sheet, newcol))
44
+ sheet.addColumnAtCursor(newcol)
45
+
46
+ # spawn a little thread to calc the rowindex
47
+ sheet.calcRowIndex(newcol._rowindex)
48
+
49
+
50
+ @Sheet.api
51
+ def addcol_delta(sheet, vcolidx):
52
+ col = sheet.visibleCols[vcolidx]
53
+
54
+ newcol = ColumnExpr("delta_"+col.name,
55
+ type=col.type,
56
+ _rowindex={}, # [rowid(row)] -> rowidx
57
+ expr="{0}-prev(row).{0}".format(col.name))
58
+
59
+ sheet.addcol_rowindex(newcol)
60
+ return newcol
61
+
62
+ @Sheet.api
63
+ def addcol_rownum(sheet):
64
+ newcol = Column("rownum",
65
+ type=int,
66
+ _rowindex={}, # [rowid(row)] -> rowidx
67
+ getter=lambda col,row: col._rowindex.get(col.sheet.rowid(row)))
68
+
69
+ sheet.addcol_rowindex(newcol)
70
+ return newcol
71
+
72
+ Sheet.addCommand(None, 'addcol-rownum', 'addcol_rownum()', helpstr='add column with original row ordering')
73
+ Sheet.addCommand(None, 'addcol-delta', 'addcol_delta(cursorVisibleColIndex)', helpstr='add column with delta of current column')
@@ -0,0 +1,26 @@
1
+ '''
2
+ # TODO:
3
+ - slide-cells-left
4
+ - slide-cells-<dir>-n
5
+ - slide-cells-selected-<dir>-n
6
+ - rename "slide" to "shift"?
7
+ '''
8
+
9
+ from visidata import vd, TableSheet
10
+
11
+
12
+ @TableSheet.api
13
+ def slide_cells_right(sheet, row, vcolidx):
14
+ for oldcol, newcol in reversed(list(zip(sheet.visibleCols[vcolidx:], sheet.visibleCols[vcolidx+1:]))):
15
+ newcol.setValue(row, oldcol.getValue(row))
16
+
17
+ sheet.visibleCols[vcolidx].setValue(row, None)
18
+
19
+
20
+ TableSheet.addCommand('', 'slide-cells-right', 'slide_cells_right(cursorRow, cursorVisibleColIndex)', '''
21
+ Shift individual values in current row one visible column to the right, with leftmost cell set to null.
22
+ ''')
23
+
24
+ vd.addMenuItems('''
25
+ Edit > Slide > cells > right > slide-cells-right
26
+ ''')