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
visidata/macos.py CHANGED
@@ -69,7 +69,7 @@ BaseSheet.bindkey('¶', 'Alt+7')
69
69
  BaseSheet.bindkey('•', 'Alt+8')
70
70
  BaseSheet.bindkey('ª', 'Alt+9')
71
71
  BaseSheet.bindkey('º', 'Alt+0')
72
- BaseSheet.bindkey('', 'Alt+`')
72
+ #BaseSheet.bindkey('', 'Alt+`')
73
73
  BaseSheet.bindkey('–', 'Alt+-')
74
74
  BaseSheet.bindkey('≠', 'Alt+=')
75
75
  BaseSheet.bindkey('“', 'Alt+[')
visidata/macros.py CHANGED
@@ -1,89 +1,178 @@
1
- from visidata import *
1
+ from copy import copy
2
2
  from functools import wraps
3
3
 
4
4
  from visidata.cmdlog import CommandLog, CommandLogJsonl
5
+ from visidata import vd, UNLOADED, asyncthread, vlen
6
+ from visidata import IndexSheet, VisiData, Sheet, Path, VisiDataMetaSheet, Column, ItemColumn, AttrColumn, BaseSheet, GuideSheet
5
7
 
6
8
  vd.macroMode = None
7
9
  vd.macrobindings = {}
8
10
 
11
+
12
+ vd.macros = vd.StoredList(name='macros')
13
+
14
+
9
15
  class MacroSheet(IndexSheet):
16
+ guide= '''
17
+ # Macros Sheet
18
+ This is a list of user-defined macros.
19
+
20
+ - `Enter` to open the current macro.
21
+ - `d` to mark macro for delete; `z Ctrl+S` to commit.
22
+ '''
23
+ columns = [
24
+ AttrColumn('binding'),
25
+ Column('num_commands', type=vlen, width=0),
26
+ AttrColumn('source'),
27
+ ]
28
+ rowtype = 'macros' # rowdef: CommandLogJsonl
29
+ defer = True
30
+ nKeys = 1
10
31
 
11
32
  def iterload(self):
12
- for ks, fn in self.source.rows:
13
- fp = Path(fn)
14
- if fp.ext == 'vd':
15
- vs = vd.loadInternalSheet(CommandLog, fp)
16
- elif fp.ext == 'vdj':
17
- vs = vd.loadInternalSheet(CommandLogJsonl, fp)
18
- else:
19
- vd.warning(f'failed to load macro {fn}')
20
- continue
21
- setMacro(ks, vs)
22
- yield vs
33
+ yield from vd.macrobindings.values()
34
+
35
+ def commitDeleteRow(self, row):
36
+ del vd.macrobindings[row.binding]
37
+ vd.callNoExceptions(Path(row.source).unlink)
23
38
 
39
+ @asyncthread
40
+ def putChanges(self):
41
+ self.commitDeletes() #1569 apply deletes early for saveSheets below
42
+
43
+ vd.saveSheets(self.source, self, confirm_overwrite=False)
44
+ self._deferredDels.clear()
45
+ self.reload()
46
+
47
+ def newRow(self):
48
+ vd.fail('add macros with `m` instead')
24
49
 
25
50
 
26
51
  @VisiData.lazy_property
27
52
  def macrosheet(vd):
28
- macrospath = Path(os.path.join(options.visidata_dir, 'macros.tsv'))
29
- macrosheet = vd.loadInternalSheet(VisiDataMetaSheet, macrospath, columns=(ColumnItem('command', 0), ColumnItem('filename', 1))) or vd.error('error loading macros')
53
+ return MacroSheet('user_macros', source=vd.macros.path)
54
+
30
55
 
31
- real_macrosheet = MacroSheet('user_macros', rows=[], source=macrosheet)
32
- real_macrosheet.reload()
56
+ @VisiData.api
57
+ def loadMacro(vd, p:Path):
58
+ if p.exists():
59
+ if p.ext == 'vd':
60
+ vs = CommandLog(p.base_stem, source=p)
61
+ vs.ensureLoaded()
62
+ return vs
63
+ elif p.ext == 'vdj':
64
+ vs = CommandLogJsonl(p.base_stem, source=p)
65
+ vs.ensureLoaded()
66
+ return vs
67
+
68
+ vd.warning(f'failed to load macro {p}')
33
69
 
34
- return real_macrosheet
35
70
 
36
71
  @VisiData.api
37
- def runMacro(vd, macro):
38
- vd.replay_sync(macro, live=True)
72
+ def runMacro(vd, binding:str):
73
+ vd.replay_sync(vd.macrobindings[binding])
39
74
 
40
- def setMacro(ks, vs):
75
+
76
+ @VisiData.api
77
+ def setMacro(vd, ks:str, vs):
78
+ 'Set *ks* which is either a keystroke or a longname to run the cmdlog in *vs*.'
79
+ vs.binding = ks
41
80
  vd.macrobindings[ks] = vs
42
81
  if vd.isLongname(ks):
43
- BaseSheet.addCommand('', ks, 'runMacro(vd.macrobindings[longname])')
82
+ BaseSheet.addCommand('', ks, f'runMacro("{ks}")')
44
83
  else:
45
- BaseSheet.addCommand(ks, vs.name, 'runMacro(vd.macrobindings[keystrokes])')
84
+ BaseSheet.addCommand(ks, f'exec-{vs.name}', f'runMacro("{ks}")')
46
85
 
47
86
 
48
87
  @CommandLogJsonl.api
49
88
  def saveMacro(self, rows, ks):
50
89
  vs = copy(self)
51
90
  vs.rows = rows
52
- macropath = Path(vd.fnSuffix(options.visidata_dir+"macro"))
91
+ macropath = Path(vd.fnSuffix(str(Path(vd.options.visidata_dir)/ks)))
53
92
  vd.save_vdj(macropath, vs)
54
- setMacro(ks, vs)
55
- vd.macrosheet.source.append_tsv_row((ks, macropath))
56
- vd.sync(vd.macrosheet.source.reload())
57
- vd.sync(vd.macrosheet.reload())
93
+ vd.status(f'{ks} saved to {macropath}')
94
+ vd.setMacro(ks, vs)
95
+ vd.macros.append(dict(binding=ks, source=str(macropath)))
96
+ vd.reloadMacros()
97
+ vd.macrosheet.reload()
58
98
 
59
- @CommandLogJsonl.api
60
- @wraps(CommandLogJsonl.afterExecSheet)
99
+
100
+ # needs to happen before, because the original afterexecsheet resets vd.activecommand to None
101
+ @CommandLogJsonl.before
61
102
  def afterExecSheet(cmdlog, sheet, escaped, err):
62
103
  if vd.macroMode and (vd.activeCommand is not None) and (vd.activeCommand is not UNLOADED) and (vd.isLoggableCommand(vd.activeCommand.longname)):
63
104
  cmd = copy(vd.activeCommand)
64
- cmd.row = cmd.col = cmd.sheet = ''
105
+ cmd.sheet = ''
65
106
  vd.macroMode.addRow(cmd)
66
107
 
67
- # the following needs to happen at the end, bc
68
- # once cmdlog.afterExecSheet.__wrapped__ runs, vd.activeCommand resets to None
69
- cmdlog.afterExecSheet.__wrapped__(cmdlog, sheet, escaped, err)
70
108
 
71
109
  @CommandLogJsonl.api
72
110
  def startMacro(cmdlog):
111
+ if not Path(vd.options.visidata_dir).is_dir():
112
+ vd.fail(f'create {vd.options.visidata_dir} to save macros')
73
113
  if vd.macroMode:
74
- ks = vd.input('set macro to keybinding: ')
75
- while ks in vd.macrobindings:
76
- ks = vd.input(f'{ks} already in use; set macro to keybinding: ')
77
- vd.cmdlog.saveMacro(vd.macroMode.rows, ks)
78
- vd.macroMode = None
114
+ try:
115
+ ks = vd.input('bind macro to: ', help=f'''
116
+ # Finish recording macro
117
+ Type in either a longname like `happy-time` (with at least one hyphen),
118
+ or spell out a keybinding (like `Alt+b`) manually.
119
+
120
+ - Prefixes allowed with a keybinding: `{' '.join(vd.allPrefixes)}`
121
+ - Press `Ctrl+N` and then press another keystroke to spell that keystroke.
122
+ - Press `Ctrl+C` to cancel the macro recording.
123
+ ''')
124
+ while ks in vd.macrobindings:
125
+ ks = vd.input(f'{ks} already in use; set macro to keybinding: ')
126
+ vd.cmdlog.saveMacro(vd.macroMode.rows, ks)
127
+ finally:
128
+ vd.macroMode = None
79
129
  else:
80
- vd.status("recording macro")
130
+ vd.status("recording macro; stop recording with `m`")
81
131
  vd.macroMode = CommandLogJsonl('current_macro', rows=[])
82
132
 
133
+
83
134
  @VisiData.before
135
+ @asyncthread
84
136
  def run(vd, *args, **kwargs):
85
- vd.macrosheet
137
+ vd.reloadMacros()
138
+
139
+
140
+ @VisiData.api
141
+ def reloadMacros(vd):
142
+ vd.macros.reload()
143
+ for r in vd.macros:
144
+ vs = vd.loadMacro(Path(r.source))
145
+ if vs:
146
+ vd.setMacro(r.binding, vs)
147
+
148
+
149
+ class MacrosGuide(GuideSheet):
150
+ guide_text = '''# Macros
151
+ Macros allow you to bind a command sequence to a keystroke or longname, to replay when that keystroke is pressed or the command is executed by longname.
152
+
153
+ The basic usage is:
154
+ 1. {help.commands.macro_record}.
155
+ 2. Execute a series of commands.
156
+ 3. `m` again to complete the recording, and prompt for the keystroke or longname to bind it to.
157
+
158
+ The macro will then be executed everytime the provided keystroke or longname are used. Note: the Alt+keys and the function keys are left unbound; overriding other keys may conflict with existing bindings, now or in the future.
159
+
160
+ Executing a macro will the series of commands starting on the current row and column on the current sheet.
161
+
162
+ # The Macros Sheet
163
+
164
+ - {help.commands.macro_sheet}
165
+
166
+ - `d` (`delete-row`) to mark macros for deletion
167
+ - {help.commands.commit_sheet}
168
+ - `Enter` (`open-row`) to open the macro in the current row, and view the series of commands composing it'''
86
169
 
87
170
 
88
171
  Sheet.addCommand('m', 'macro-record', 'vd.cmdlog.startMacro()', 'record macro')
89
- Sheet.addCommand('gm', 'macro-sheet', 'vd.push(vd.macrosheet)', 'open macros sheet')
172
+ Sheet.addCommand('gm', 'macro-sheet', 'vd.push(vd.macrosheet)', 'open an index of existing macros')
173
+
174
+ vd.addMenuItems('''
175
+ System > Macros sheet > macro-sheet
176
+ ''')
177
+
178
+ vd.addGuide('MacrosSheet', MacrosGuide)
visidata/main.py CHANGED
@@ -2,7 +2,7 @@
2
2
  # Usage: $0 [<options>] [<input> ...]
3
3
  # $0 [<options>] --play <cmdlog> [--batch] [-w <waitsecs>] [-o <output>] [field=value ...]
4
4
 
5
- __version__ = '2.11dev'
5
+ __version__ = '3.0'
6
6
  __version_info__ = 'saul.pw/VisiData v' + __version__
7
7
 
8
8
  from copy import copy
@@ -16,8 +16,6 @@ import signal
16
16
  import warnings
17
17
  import builtins # to override print
18
18
 
19
- from pkg_resources import resource_filename
20
-
21
19
  from visidata import vd, options, run, BaseSheet, AttrDict
22
20
  from visidata import Path
23
21
  from visidata.settings import _get_config_file
@@ -26,12 +24,13 @@ import visidata
26
24
  vd.version_info = __version_info__
27
25
 
28
26
  vd.option('config', _get_config_file(), 'config file to exec in Python', sheettype=None)
29
- vd.option('play', '', 'file.vd to replay')
27
+ vd.option('play', '', 'file.vdj to replay')
30
28
  vd.option('batch', False, 'replay in batch mode (with no interface and all status sent to stdout)')
31
29
  vd.option('output', None, 'save the final visible sheet to output at the end of replay')
32
30
  vd.option('preplay', '', 'longnames to preplay before replay')
33
31
  vd.option('imports', 'plugins', 'imports to preload before .visidatarc (command-line only)')
34
32
  vd.option('nothing', False, 'no config, no plugins, nothing extra')
33
+ vd.option('interactive', False, 'run interactive mode after batch replay')
35
34
 
36
35
  # for --play
37
36
  def eval_vd(logpath, *args, **kwargs):
@@ -57,7 +56,9 @@ def duptty():
57
56
  try:
58
57
  fin = open('/dev/tty')
59
58
  fout = open('/dev/tty', mode='w')
60
- stdin = open(os.dup(0), encoding=vd.options.getonly('encoding', 'global', 'utf-8'))
59
+ stdin = open(os.dup(0),
60
+ encoding=vd.options.getonly('encoding', 'global', 'utf-8'),
61
+ errors=vd.options.getonly('encoding_errors', 'global', 'surrogateescape')) #2047
61
62
  stdout = open(os.dup(1)) # for dumping to stdout from interface
62
63
  os.dup2(fin.fileno(), 0)
63
64
  os.dup2(fout.fileno(), 1)
@@ -71,24 +72,98 @@ def duptty():
71
72
 
72
73
  return stdin, stdout
73
74
 
74
- option_aliases = {}
75
- def optalias(abbr, name, val=None):
76
- option_aliases[abbr] = (name, val)
77
-
78
-
79
- optalias('N', 'nothing')
80
- optalias('f', 'filetype')
81
- optalias('p', 'play')
82
- optalias('b', 'batch')
83
- optalias('P', 'preplay')
84
- optalias('y', 'confirm_overwrite', False)
85
- optalias('o', 'output')
86
- optalias('w', 'replay_wait')
87
- optalias('d', 'delimiter')
88
- optalias('c', 'config')
89
- optalias('r', 'dir_recurse')
90
- optalias('force_valid_colnames', 'clean_names') # deprecated
75
+ vd.optalias('i', 'interactive')
76
+ vd.optalias('N', 'nothing')
77
+ vd.optalias('f', 'filetype')
78
+ vd.optalias('p', 'play')
79
+ vd.optalias('b', 'batch')
80
+ vd.optalias('P', 'preplay')
81
+ vd.optalias('o', 'output')
82
+ vd.optalias('w', 'replay_wait')
83
+ vd.optalias('d', 'delimiter')
84
+ vd.optalias('c', 'config')
85
+ vd.optalias('r', 'dir_depth', 100000)
86
+
87
+
88
+ @visidata.VisiData.api
89
+ def parsePos(vd, arg:str, inputs=None):
90
+ 'Return (startsheets:list, startrow:str, startcol:str) from *arg* like "+sheet:subsheet:col:row". Empty sheetstr in startsheets means the starting pos applies to all sheets.'
91
+ startsheets, startrow, startcol = [], None, None
91
92
 
93
+ if ':' not in arg:
94
+ return (None, arg, None)
95
+
96
+ pos = arg.split(':')
97
+ if len(pos) == 1:
98
+ startsheet = [Path(inputs[-1]).base_stem] if inputs else None
99
+ start_pos = (startsheet, pos[0], None)
100
+ elif len(pos) == 2:
101
+ startsheet = [Path(inputs[-1]).base_stem] if inputs else None
102
+ startrow, startcol = pos
103
+ start_pos = (None, startrow, startcol)
104
+ else: # if len(pos) >= 3:
105
+ startsheets = pos[:-2]
106
+ startrow, startcol = pos[-2:]
107
+ start_pos = (startsheets, startrow, startcol)
108
+
109
+ # index subsheets need to be loaded *after* the cursor indexing
110
+ vd.options.set('load_lazy', True, obj=start_pos[0])
111
+
112
+ return start_pos
113
+
114
+
115
+ @visidata.VisiData.api
116
+ def outputProgressEvery(vd, sheet, seconds:float=0.5):
117
+ import time
118
+ t0 = time.time()
119
+ while True:
120
+ time.sleep(seconds)
121
+ t = time.time()
122
+ print(f'\r[{t-t0:.1f}s] ', end='', file=sys.stderr)
123
+ if sheet:
124
+ print(f'{sheet.progressPct} ', end='', file=sys.stderr)
125
+ sys.stderr.flush()
126
+
127
+ @visidata.VisiData.api
128
+ def moveToPos(vd, sources, startsheets, startrow, startcol):
129
+ sheets = [] # sheets to apply startrow:startcol to
130
+ if not startsheets:
131
+ sheets = sources # apply row/col to all sheets
132
+ else:
133
+ startsheet = startsheets[0] or sources[-1]
134
+ vs = vd.getSheet(startsheet)
135
+ if not vs:
136
+ vd.warning(f'no sheet "{startsheet}"')
137
+ return
138
+
139
+ vd.sync(vs.ensureLoaded())
140
+ vd.clearCaches()
141
+ for startsheet in startsheets[1:]:
142
+ rowidx = vs.getRowIndexFromStr(vd.options.rowkey_prefix + startsheet)
143
+ if rowidx is None:
144
+ vd.warning(f'{vs.name} has no subsheet "{startsheet}"')
145
+ vs = None
146
+ break
147
+ vs = vs.rows[rowidx]
148
+ vd.sync(vs.ensureLoaded())
149
+ vd.clearCaches()
150
+ if vs:
151
+ vd.push(vs)
152
+ sheets = [vs]
153
+
154
+ if startrow:
155
+ for vs in sheets:
156
+ if vs:
157
+ vs.moveToRow(startrow) or vd.warning(f'{vs} has no row "{startrow}"')
158
+
159
+ if startcol:
160
+ for vs in sheets:
161
+ if vs:
162
+ if not vs.moveToCol(startcol):
163
+ if startcol.isdigit():
164
+ vs.moveToCol(int(startcol)) # handle indexing by column number
165
+ else:
166
+ vd.warning(f'{vs} has no column "{startcol}"')
92
167
 
93
168
  def main_vd():
94
169
  'Open the given sources using the VisiData interface.'
@@ -96,9 +171,9 @@ def main_vd():
96
171
  print(vd.version_info)
97
172
  return 0
98
173
  if '-h' in sys.argv or '--help' in sys.argv:
99
- with open(resource_filename(__name__, 'man/vd.txt'), 'r') as fp:
100
- print(fp.read())
174
+ print((Path(vd.pkg_resources_files(visidata)) / 'man' / 'vd.txt').open().read())
101
175
  return 0
176
+ vd.status(__version_info__)
102
177
 
103
178
  try:
104
179
  locale.setlocale(locale.LC_ALL, '')
@@ -107,7 +182,6 @@ def main_vd():
107
182
 
108
183
  warnings.showwarning = vd.warning
109
184
  vd.printout = builtins.print
110
- vd.printerr = lambda *args, **kwargs: builtins.print(*args, file=sys.stderr) # ignore kwargs (like priority)
111
185
 
112
186
  flPipedInput = not sys.stdin.isatty()
113
187
  flPipedOutput = not sys.stdout.isatty()
@@ -122,8 +196,7 @@ def main_vd():
122
196
  vd.stdinSource = Path('-', fp=None) # fp filled in below after options parsed for encoding
123
197
 
124
198
  # parse args, including +sheetname:subsheet:4:3 starting at row:col on sheetname:subsheet[:...]
125
- start_positions = [] # (list_of_sheetstr, str, str) # empty sheetstr means all sheets
126
- startsheets, startrow, startcol = [], None, None
199
+ after_config = []
127
200
  fmtargs = []
128
201
  fmtkwargs = {}
129
202
  inputs = []
@@ -158,10 +231,10 @@ def main_vd():
158
231
  pass
159
232
 
160
233
  optname = optname.replace('-', '_')
161
- optname, optval = option_aliases.get(optname, (optname, optval))
234
+ optname, optval = vd._resolve_optalias(optname, optval)
162
235
 
163
- if optval is None:
164
- opt = options._get(optname)
236
+ if optval is None: # missing argument, maybe bool?
237
+ opt = vd.options._get(optname)
165
238
  if opt:
166
239
  if type(opt.value) is bool:
167
240
  optval = True
@@ -177,24 +250,7 @@ def main_vd():
177
250
  global_args[optname] = optval
178
251
 
179
252
  elif arg.startswith('+'): # position cursor at start
180
- if ':' in arg:
181
- pos = arg[1:].split(':')
182
- if len(pos) == 1:
183
- startsheet = [Path(inputs[-1]).name] if inputs else None
184
- start_positions.append((startsheet, pos[0], None))
185
- elif len(pos) == 2:
186
- startsheet = [Path(inputs[-1]).name] if inputs else None
187
- startrow, startcol = pos
188
- start_positions.append((None, startrow, startcol))
189
- elif len(pos) >= 3:
190
- startsheets = pos[:-2]
191
- startrow, startcol = pos[-2:]
192
- start_positions.append((startsheets, startrow, startcol))
193
- if start_positions[-1]:
194
- # index subsheets need to be loaded *after* the cursor indexing
195
- options.set('load_lazy', True, obj=start_positions[-1][0])
196
- else:
197
- start_positions.append((None, arg[1:], None))
253
+ after_config.append((vd.moveToPos, *vd.parsePos(arg[1:], inputs=inputs)))
198
254
 
199
255
  elif current_args.get('play', None) and '=' in arg:
200
256
  # parse 'key=value' pairs for formatting cmdlog template in replay mode
@@ -216,17 +272,16 @@ def main_vd():
216
272
 
217
273
  vd._stdin, vd._stdout = duptty() # always dup stdin/stdout
218
274
  vd.stdinSource.fptext = vd._stdin
275
+ vd._stdin.close = vd.nop #1759
219
276
 
220
- # fetch motd and plugins *after* options parsing/setting
221
- vd.pluginsSheet.ensureLoaded()
277
+ # fetch motd *after* options parsing/setting
222
278
  vd.domotd()
223
279
 
224
280
  if args.batch:
225
281
  options.undo = False
226
282
  options.quitguard = False
227
- vd.status = vd.printerr
228
283
  vd.editline = lambda *args, **kwargs: ''
229
- vd.execAsync = lambda func, *args, sheet=None, **kwargs: func(*args, **kwargs) # disable async
284
+ vd.execAsync = vd.execSync # disable async
230
285
 
231
286
  for cmd in (args.preplay or '').split():
232
287
  BaseSheet('').execCommand(cmd)
@@ -275,45 +330,8 @@ def main_vd():
275
330
  vd.push(sources[0])
276
331
  sources[0].reload()
277
332
 
278
- for startsheets, startrow, startcol in start_positions:
279
- sheets = [] # sheets to apply startrow:startcol to
280
- if not startsheets:
281
- sheets = sources # apply row/col to all sheets
282
- else:
283
- startsheet = startsheets[0] or sources[-1]
284
- vs = vd.getSheet(startsheet)
285
- if not vs:
286
- vd.warning(f'no sheet "{startsheet}"')
287
- continue
288
-
289
- vd.sync(vs.ensureLoaded())
290
- vd.clearCaches()
291
- for startsheet in startsheets[1:]:
292
- rowidx = vs.getRowIndexFromStr(options.rowkey_prefix + startsheet)
293
- if rowidx is None:
294
- vd.warning(f'{vs.name} has no subsheet "{startsheet}"')
295
- vs = None
296
- break
297
- vs = vs.rows[rowidx]
298
- vd.sync(vs.ensureLoaded())
299
- vd.clearCaches()
300
- if vs:
301
- vd.push(vs)
302
- sheets = [vs]
303
-
304
- if startrow:
305
- for vs in sheets:
306
- if vs:
307
- vs.moveToRow(startrow) or vd.warning(f'{vs} has no row "{startrow}"')
308
-
309
- if startcol:
310
- for vs in sheets:
311
- if vs:
312
- if not vs.moveToCol(startcol):
313
- if startcol.isdigit():
314
- vs.moveToCol(int(startcol)) # handle indexing by column number
315
- else:
316
- vd.warning(f'{vs} has no column "{startcol}"')
333
+ for (f, *parms) in after_config:
334
+ f(sources, *parms)
317
335
 
318
336
  if not args.batch:
319
337
  run(vd.sheets[0])
@@ -327,10 +345,16 @@ def main_vd():
327
345
  vs = eval_vd(vdfile, *fmtargs, **fmtkwargs)
328
346
  vd.sync(vs.reload())
329
347
  if args.batch:
348
+ if not args.debug:
349
+ vd.outputProgressThread = visidata.VisiData.execAsync(vd, vd.outputProgressEvery, vs, seconds=0.5, sheet=BaseSheet()) #1182
330
350
  if vd.replay_sync(vs): # error
331
351
  return 1
352
+
353
+ if vd.options.interactive:
354
+ vd.editline = lambda *args, vd=vd, **kwargs: visidata.VisiData.editline(vd, *args, **kwargs)
355
+ vd.execAsync = lambda *args, vd=vd, **kwargs: visidata.VisiData.execAsync(vd, *args, **kwargs)
356
+ run()
332
357
  else:
333
- vd.currentReplay = vs
334
358
  vd.replay(vs)
335
359
  run()
336
360
 
@@ -348,18 +372,19 @@ def main_vd():
348
372
  return 0
349
373
 
350
374
  def vd_cli():
351
- vd.status(__version_info__)
352
375
  rc = -1
353
376
  try:
354
377
  rc = main_vd()
355
378
  except BrokenPipeError:
356
379
  os.dup2(os.open(os.devnull, os.O_WRONLY), sys.stdout.fileno()) # handle broken pipe gracefully
357
380
  except visidata.ExpectedException as e:
358
- print('Error: ' + str(e))
381
+ if vd.options.debug:
382
+ raise
359
383
  except FileNotFoundError as e:
360
384
  print(e)
361
385
  if options.debug:
362
386
  raise
387
+
363
388
  sys.stderr.flush()
364
389
  sys.stdout.flush()
365
390
  os._exit(rc) # cleanup can be expensive with large datasets