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/pyobj.py CHANGED
@@ -1,9 +1,10 @@
1
- from functools import singledispatch
2
1
  from typing import Mapping
3
2
  import inspect
4
3
  import math
5
4
 
6
- from visidata import *
5
+ from visidata import vd, asyncthread, ENTER, deduceType
6
+ from visidata import Sheet, Column, VisiData, ColumnItem, TableSheet, BaseSheet, Progress, ColumnAttr, SuspendCurses, TextSheet
7
+ import visidata
7
8
 
8
9
  vd.option('visibility', 0, 'visibility level')
9
10
  vd.option('default_sample_size', 100, 'number of rows to sample for regex.split (0=all)', replay=True)
@@ -16,177 +17,6 @@ class PythonSheet(Sheet):
16
17
  return PyobjSheet("%s[%s]" % (self.name, self.keystr(row)), source=row)
17
18
 
18
19
 
19
- class InferColumnsSheet(Sheet):
20
- _rowtype = dict
21
- @asyncthread
22
- def reload(self):
23
- self.reloadCols()
24
-
25
- self.rows = []
26
- for r in self.iterload():
27
- self.addRow(r)
28
-
29
- # if an ordering has been specified, sort the sheet
30
- if self._ordering:
31
- vd.sync(self.sort())
32
-
33
- def reloadCols(self):
34
- self.columns = []
35
- self._knownKeys.clear()
36
- for c in type(self).columns:
37
- self.addColumn(deepcopy(c))
38
-
39
- def addColumn(self, *cols, index=None):
40
- for c in cols:
41
- self._knownKeys.add(c.expr or c.name)
42
- return super().addColumn(*cols, index=index)
43
-
44
- def addRow(self, row, index=None):
45
- ret = super().addRow(row, index=index)
46
- for k in row:
47
- if k not in self._knownKeys:
48
- self.addColumn(ColumnItem(k, type=deduceType(row[k])))
49
-
50
- return ret
51
-
52
-
53
- InferColumnsSheet.init('_knownKeys', set, copy=True) # set of row keys already seen
54
- InferColumnsSheet.init('_ordering', list, copy=True)
55
-
56
-
57
- @Sheet.api
58
- def getSampleRows(sheet):
59
- 'Return list of sample rows centered around the cursor.'
60
- n = sheet.options.default_sample_size
61
- if n == 0 or n >= sheet.nRows:
62
- return sheet.rows
63
-
64
- vd.warning(f'sampling {n} rows')
65
- seq = sheet.rows
66
- start = math.ceil(sheet.cursorRowIndex - n / 2) % len(seq)
67
- end = (start + n) % len(seq)
68
- if start < end:
69
- return seq[start:end]
70
- return seq[start:] + seq[:end]
71
-
72
-
73
- @asyncthread
74
- def expand_cols_deep(sheet, cols, rows=None, depth=0): # depth == 0 means drill all the way
75
- 'expand all visible columns of containers to the given depth (0=fully)'
76
- ret = []
77
- if not rows:
78
- rows = sheet.getSampleRows()
79
-
80
- for col in cols:
81
- newcols = col.expand(rows)
82
- if depth != 1: # countdown not yet complete, or negative (indefinite)
83
- ret.extend(expand_cols_deep.__wrapped__(sheet, newcols, rows, depth-1))
84
- return ret
85
-
86
- @singledispatch
87
- def _createExpandedColumns(sampleValue, col, rows):
88
- '''By default, a column is not expandable. Supported container types for
89
- sampleValue trigger alternate, type-specific expansions.'''
90
- return []
91
-
92
- @_createExpandedColumns.register(dict)
93
- def _(sampleValue, col, vals):
94
- '''Build a set of columns to add, using the first occurrence of each key to
95
- determine column type'''
96
- newcols = {}
97
-
98
- for val in Progress(vals, 'expanding'):
99
- colsToAdd = set(val).difference(newcols)
100
- colsToAdd and newcols.update({
101
- k: deduceType(v)
102
- for k, v in val.items()
103
- if k in colsToAdd
104
- })
105
-
106
- return [
107
- ExpandedColumn(col.sheet.options.fmt_expand_dict % (col.name, k), type=v, origCol=col, expr=k)
108
- for k, v in newcols.items()
109
- ]
110
-
111
- def _createExpandedColumnsNamedTuple(col, val):
112
- return [
113
- ExpandedColumn(col.sheet.options.fmt_expand_dict % (col.name, k), type=colType, origCol=col, expr=i)
114
- for i, (k, colType) in enumerate(zip(val._fields, (deduceType(v) for v in val)))
115
- ]
116
-
117
- @_createExpandedColumns.register(list)
118
- @_createExpandedColumns.register(tuple)
119
- def _(sampleValue, col, vals):
120
- '''Use the longest sequence to determine the number of columns we need to
121
- create, and their presumed types'''
122
- def lenNoExceptions(v):
123
- try:
124
- return len(v)
125
- except Exception as e:
126
- return 0
127
-
128
- if hasattr(sampleValue, '_fields'): # looks like a namedtuple
129
- return _createExpandedColumnsNamedTuple(col, vals[0])
130
-
131
- longestSeq = max(vals, key=lenNoExceptions)
132
- colTypes = [deduceType(v) for v in longestSeq]
133
- return [
134
- ExpandedColumn(col.sheet.options.fmt_expand_list % (col.name, k), type=colType, origCol=col, expr=k)
135
- for k, colType in enumerate(colTypes)
136
- ]
137
-
138
-
139
- @Column.api
140
- def expand(col, rows):
141
- isNull = col.sheet.isNullFunc()
142
- nonNulls = [
143
- col.getTypedValue(row)
144
- for row in rows
145
- if not isNull(col.getValue(row))
146
- ]
147
-
148
- if not nonNulls:
149
- return []
150
-
151
- # The type of the first non-null value for col determines if and how the
152
- # column can be expanded.
153
- expandedCols = _createExpandedColumns(nonNulls[0], col, nonNulls)
154
-
155
- idx = col.sheet.columns.index(col)
156
-
157
- for i, c in enumerate(expandedCols):
158
- col.sheet.addColumn(c, index=idx+i+1)
159
- if expandedCols:
160
- col.hide()
161
- return expandedCols
162
-
163
-
164
- def deduceType(v):
165
- if isinstance(v, (float, int)):
166
- return type(v)
167
- else:
168
- return anytype
169
-
170
-
171
- class ExpandedColumn(Column):
172
- def calcValue(self, row):
173
- return getitemdef(self.origCol.getValue(row), self.expr)
174
-
175
- def setValue(self, row, value):
176
- self.origCol.getValue(row)[self.expr] = value
177
-
178
-
179
- def closeColumn(sheet, col):
180
- if hasattr(col, 'origCol'):
181
- origCol = col.origCol
182
- else:
183
- vd.fail('column has not been expanded')
184
- vd.addUndo(setattr, sheet, 'columns', sheet.columns)
185
- origCol.width = options.default_width
186
- cols = [c for c in sheet.columns if getattr(c, "origCol", None) is not origCol]
187
- sheet.columns = cols
188
-
189
-
190
20
  #### generic list/dict/object browsing
191
21
  @VisiData.global_api
192
22
  def view(vd, obj):
@@ -212,10 +42,10 @@ def SheetList(*names, **kwargs):
212
42
 
213
43
  src = kwargs.get('source', None)
214
44
  if not src:
215
- vd.status('no content in %s' % names)
216
- return
45
+ vd.warning('no content in %s' % names)
46
+ return Sheet(*names, **kwargs)
217
47
 
218
- if isinstance(src[0], dict):
48
+ if isinstance(src[0], Mapping):
219
49
  return ListOfDictSheet(*names, **kwargs)
220
50
  elif isinstance(src[0], tuple):
221
51
  if getattr(src[0], '_fields', None): # looks like a namedtuple
@@ -226,7 +56,7 @@ def SheetList(*names, **kwargs):
226
56
 
227
57
  class ListOfPyobjSheet(PythonSheet):
228
58
  rowtype = 'python objects'
229
- def reload(self):
59
+ def loader(self):
230
60
  self.rows = self.source
231
61
  self.columns = []
232
62
  self.addColumn(Column(self.name,
@@ -275,7 +105,7 @@ class SheetNamedTuple(PythonSheet):
275
105
  self.rows = list(zip(self.source._fields, self.source))
276
106
 
277
107
  def openRow(self, row):
278
- return PyobjSheet(self.name, row[0], source=row[1])
108
+ return PyobjSheet(f'{self.name}.{row[0]}', source=row[1])
279
109
 
280
110
 
281
111
  # source is dict
@@ -291,7 +121,7 @@ class SheetDict(PythonSheet):
291
121
  self.rows = list(self.source.keys())
292
122
 
293
123
  def openRow(self, row):
294
- return PyobjSheet(self.name, row, source=self.source[row])
124
+ return PyobjSheet(f'{self.name}.{row}', source=self.source[row])
295
125
 
296
126
 
297
127
  class ColumnSourceAttr(Column):
@@ -347,10 +177,11 @@ class PyobjSheet(PythonSheet):
347
177
  if vislevel <= 2 and r.startswith('__'): continue
348
178
  if vislevel <= 1 and r.startswith('_'): continue
349
179
  if vislevel <= 0 and callable(getattr(self.source, r)): continue
350
- self.addRow(r)
351
180
  except Exception:
352
181
  pass
353
182
 
183
+ self.addRow(r)
184
+
354
185
  def openRow(self, row):
355
186
  'dive further into Python object'
356
187
  v = getattr(self.source, row)
@@ -358,9 +189,13 @@ class PyobjSheet(PythonSheet):
358
189
 
359
190
 
360
191
  @TableSheet.api
361
- def openRow(sheet, row):
192
+ def openRow(sheet, row, rowidx=None):
362
193
  'Return Sheet diving into *row*.'
363
- k = sheet.keystr(row) or sheet.cursorRowIndex
194
+ if rowidx is None:
195
+ k = sheet.keystr(row) or str(sheet.cursorRowIndex)
196
+ else:
197
+ k = rowidx
198
+
364
199
  name = f'{sheet.name}[{k}]'
365
200
  return TableSheet(name,
366
201
  rows=sheet.visibleCols,
@@ -372,12 +207,26 @@ def openRow(sheet, row):
372
207
  nKeys=1)
373
208
 
374
209
  @TableSheet.api
375
- def openCell(sheet, col, row):
210
+ def openCell(sheet, col, row, rowidx=None):
376
211
  'Return Sheet diving into cell at *row* in *col*.'
377
- k = sheet.keystr(row) or [str(sheet.cursorRowIndex)]
212
+ if rowidx is None:
213
+ k = sheet.keystr(row) or str(sheet.cursorRowIndex)
214
+ else:
215
+ k = rowidx
378
216
  name = f'{sheet.name}[{k}].{col.name}'
379
217
  return PyobjSheet(name, source=col.getTypedValue(row))
380
218
 
219
+ @TableSheet.api
220
+ def openRowPyobj(sheet, rowidx):
221
+ 'Return Sheet of raw Python object of row.'
222
+ return PyobjSheet("%s[%s]" % (sheet.name, rowidx), source=sheet.rows[rowidx])
223
+
224
+ @TableSheet.api
225
+ def openCellPyobj(sheet, col, rowidx):
226
+ 'Return Sheet of raw Python object of cell.'
227
+ name = f'{sheet.name}[{rowidx}].{col.name}'
228
+ return PyobjSheet(name, source=col.getValue(sheet.rows[rowidx]))
229
+
381
230
 
382
231
  @BaseSheet.api
383
232
  def pyobj_expr(sheet):
@@ -392,20 +241,13 @@ def pyobj_expr(sheet):
392
241
  BaseSheet.addCommand('^X', 'pyobj-expr', 'pyobj_expr()', 'evaluate Python expression and open result as Python object')
393
242
  BaseSheet.addCommand('', 'exec-python', 'expr = input("exec: ", "expr", completer=CompleteExpr()); exec(expr, getGlobals(), LazyChainMap(sheet, *vd.contexts, locals=vd.getGlobals()))', 'execute Python statement with expression scope')
394
243
  BaseSheet.addCommand('g^X', 'import-python', 'modname=input("import: ", type="import_python"); exec("import "+modname, getGlobals())', 'import Python module in the global scope')
395
- globalCommand('z^X', 'pyobj-expr-row', 'expr = input("eval over current row: ", "expr", completer=CompleteExpr()); vd.push(PyobjSheet(expr, source=evalExpr(expr, row=cursorRow)))', 'evaluate Python expression, in context of current row, and open result as Python object')
396
-
397
- Sheet.addCommand('^Y', 'pyobj-row', 'status(type(cursorRow)); vd.push(PyobjSheet("%s[%s]" % (sheet.name, cursorRowIndex), source=cursorRow))', 'open current row as Python object')
398
- Sheet.addCommand('z^Y', 'pyobj-cell', 'status(type(cursorValue)); vd.push(PyobjSheet("%s[%s].%s" % (sheet.name, cursorRowIndex, cursorCol.name), source=cursorValue))', 'open current cell as Python object')
399
- globalCommand('g^Y', 'pyobj-sheet', 'status(type(sheet)); vd.push(PyobjSheet(sheet.name+"_sheet", source=sheet))', 'open current sheet as Python object')
244
+ BaseSheet.addCommand('z^X', 'pyobj-expr-row', 'expr = input("eval over current row: ", "expr", completer=CompleteExpr()); vd.push(PyobjSheet(expr, source=evalExpr(expr, row=cursorRow)))', 'evaluate Python expression, in context of current row, and open result as Python object')
400
245
 
401
- Sheet.addCommand('(', 'expand-col', 'expand_cols_deep(sheet, [cursorCol], depth=1)', 'expand current column of containers one level')
402
- Sheet.addCommand('g(', 'expand-cols', 'expand_cols_deep(sheet, visibleCols, depth=1)', 'expand all visible columns of containers one level')
403
- Sheet.addCommand('z(', 'expand-col-depth', 'expand_cols_deep(sheet, [cursorCol], depth=int(input("expand depth=", value=1)))', 'expand current column of containers to given depth (0=fully)')
404
- Sheet.addCommand('gz(', 'expand-cols-depth', 'expand_cols_deep(sheet, visibleCols, depth=int(input("expand depth=", value=1)))', 'expand all visible columns of containers to given depth (0=fully)')
246
+ Sheet.addCommand('^Y', 'pyobj-row', 'status(type(cursorRow).__name__); vd.push(openRowPyobj(cursorRowIndex))', 'open current row as Python object')
247
+ Sheet.addCommand('z^Y', 'pyobj-cell', 'status(type(cursorValue).__name__); vd.push(openCellPyobj(cursorCol, cursorRowIndex))', 'open current cell as Python object')
248
+ BaseSheet.addCommand('g^Y', 'pyobj-sheet', 'status(type(sheet).__name__); vd.push(PyobjSheet(sheet.name+"_sheet", source=sheet))', 'open current sheet as Python object')
405
249
 
406
- Sheet.addCommand(')', 'contract-col', 'closeColumn(sheet, cursorCol)', 'unexpand current column; restore original column and remove other columns at this level')
407
-
408
- Sheet.addCommand('', 'open-row-basic', 'vd.push(TableSheet.openRow(sheet, cursorRow))', 'open sheet with open sheet with copies of rows referenced in current row')
250
+ Sheet.addCommand('', 'open-row-basic', 'vd.push(TableSheet.openRow(sheet, cursorRow))', 'dive into current row as basic table (ignoring subsheet dive)')
409
251
  Sheet.addCommand(ENTER, 'open-row', 'vd.push(openRow(cursorRow))', 'open current row with sheet-specific dive')
410
252
  Sheet.addCommand('z'+ENTER, 'open-cell', 'vd.push(openCell(cursorCol, cursorRow))', 'open sheet with copies of rows referenced in current cell')
411
253
  Sheet.addCommand('g'+ENTER, 'dive-selected', 'for r in selectedRows: vd.push(openRow(r))', 'open sheet with copies of rows referenced in selected rows')
@@ -416,14 +258,21 @@ PyobjSheet.addCommand('gv', 'show-hidden', 'sheet.options.visibility = 2; reload
416
258
  PyobjSheet.addCommand('zv', 'hide-hidden', 'sheet.options.visibility -= 1; reload()', 'hide methods and hidden properties')
417
259
 
418
260
  vd.addGlobals({
419
- 'ExpandedColumn': ExpandedColumn,
420
261
  'PythonSheet': PythonSheet,
421
- 'expand_cols_deep': expand_cols_deep,
422
- 'deduceType': deduceType,
423
- 'closeColumn': closeColumn,
424
262
  'ListOfDictSheet': ListOfDictSheet,
425
263
  'SheetDict': SheetDict,
426
- 'InferColumnsSheet': InferColumnsSheet,
427
264
  'PyobjSheet': PyobjSheet,
428
265
  'view': view,
429
266
  })
267
+
268
+ vd.addMenuItems('''
269
+ View > Visibility > Methods and dunder attributes > show > show-hidden
270
+ View > Visibility > Methods and dunder attributes > hide > hide-hidden
271
+ Row > Dive into > open-row
272
+ System > Python > import library > import-python
273
+ System > Python > current sheet > pyobj-sheet
274
+ System > Python > current row > pyobj-row
275
+ System > Python > current cell > pyobj-cell
276
+ System > Python > expression > pyobj-expr
277
+ System > Python > exec() > exec-python
278
+ ''')
visidata/rename_col.py ADDED
@@ -0,0 +1,20 @@
1
+ from visidata import vd, Sheet
2
+
3
+ @Sheet.api
4
+ def hint_rename_col(sheet):
5
+ if vd.cleanName(sheet.cursorCol.name) != sheet.cursorCol.name:
6
+ return 5, f"[:hint]The current column can't be used in an expression because [:code]{sheet.cursorCol.name}[/] is not a valid Python identifier. [:onclick rename-col]Rename the column[/] with `^`.[/]"
7
+
8
+
9
+ Sheet.addCommand('^', 'rename-col', 'vd.addUndoColNames([cursorCol]); cursorCol.name = editCell(cursorVisibleColIndex, -1, value=cleanName(cursorCol.name))', 'rename current column')
10
+ Sheet.addCommand('z^', 'rename-col-selected', 'updateColNames(selectedRows or [cursorRow], [sheet.cursorCol], overwrite=True)', 'rename current column to combined contents of current cell in selected rows (or current row)')
11
+ Sheet.addCommand('g^', 'rename-cols-row', 'updateColNames(selectedRows or [cursorRow], sheet.visibleCols)', 'rename all unnamed visible columns to contents of selected rows (or current row)')
12
+ Sheet.addCommand('gz^', 'rename-cols-selected', 'updateColNames(selectedRows or [cursorRow], sheet.visibleCols, overwrite=True)', 'rename all visible columns to combined contents of selected rows (or current row)')
13
+
14
+
15
+ vd.addMenuItems('''
16
+ Column > Rename > current column > rename-col
17
+ Column > Rename > from selected cells > current column > rename-col-selected
18
+ Column > Rename > from selected cells > unnamed columns > rename-cols-row
19
+ Column > Rename > from selected cells > all columns > rename-cols-selected
20
+ ''')
visidata/save.py CHANGED
@@ -1,16 +1,17 @@
1
1
  import collections
2
+ import os
3
+ from copy import copy
2
4
 
3
- from visidata import *
5
+ from visidata import vd
6
+ from visidata import Sheet, BaseSheet, VisiData, IndexSheet, Path, Progress, TypedExceptionWrapper
4
7
 
5
-
6
- vd.option('confirm_overwrite', True, 'whether to prompt for overwrite confirmation on save')
7
8
  vd.option('safe_error', '#ERR', 'error string to use while saving', replay=True)
8
- vd.option('disp_formatter', 'generic', 'formatter to use for display and saving', replay=True)
9
+ vd.option('save_encoding', 'utf-8', 'encoding passed to codecs.open when saving a file', replay=True, help=vd.help_encoding)
9
10
 
10
11
  @Sheet.api
11
12
  def safe_trdict(vs):
12
13
  'returns string.translate dictionary for replacing tabs and newlines'
13
- if options.safety_first:
14
+ if vs.options.safety_first:
14
15
  delim = vs.options.delimiter
15
16
  return {
16
17
  0: '', # strip NUL completely
@@ -37,7 +38,7 @@ def iterdispvals(sheet, *cols, format=False):
37
38
  if trdict:
38
39
  transformers[col].append(lambda v,trdict=trdict: v.translate(trdict))
39
40
 
40
- options_safe_error = options.safe_error
41
+ options_safe_error = sheet.options.safe_error
41
42
  for r in Progress(sheet.rows):
42
43
  dispvals = collections.OrderedDict() # [col] -> value
43
44
  for col, transforms in transformers.items():
@@ -84,7 +85,7 @@ def getDefaultSaveName(sheet):
84
85
  return str(src.with_suffix('')) + '.' + sheet.options.save_filetype
85
86
  return str(src)
86
87
  else:
87
- return sheet.name+'.'+getattr(sheet, 'filetype', options.save_filetype)
88
+ return sheet.name+'.'+getattr(sheet, 'filetype', sheet.options.save_filetype)
88
89
 
89
90
 
90
91
  @VisiData.api
@@ -98,24 +99,32 @@ def save_cols(vd, cols):
98
99
  else:
99
100
  savedcoltxt = '%s columns' % len(cols)
100
101
  path = vd.inputPath('save %s to: ' % savedcoltxt, value=vs.getDefaultSaveName())
101
- vd.saveSheets(path, vs, confirm_overwrite=options.confirm_overwrite)
102
+ vd.saveSheets(path, vs)
102
103
 
103
104
 
104
105
  @VisiData.api
105
- def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=False):
106
+ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
106
107
  'Save all *vsheets* to *givenpath*.'
107
108
 
108
- filetype = givenpath.ext or options.save_filetype
109
+ if not vsheets: # blank tuple
110
+ vd.warning('no sheets to save')
111
+ return
112
+
113
+ filetypes = [givenpath.ext, vd.options.save_filetype]
109
114
 
110
115
  vd.clearCaches()
111
116
 
112
- savefunc = getattr(vsheets[0], 'save_' + filetype, None) or getattr(vd, 'save_' + filetype, None)
117
+ for ft in filetypes:
118
+ savefunc = getattr(vsheets[0], 'save_' + ft, None) or getattr(vd, 'save_' + ft, None)
119
+ if savefunc:
120
+ filetype = ft
121
+ break
113
122
 
114
123
  if savefunc is None:
115
124
  vd.fail(f'no function to save as {filetype}')
116
125
 
117
- if givenpath.exists() and confirm_overwrite:
118
- vd.confirm("%s already exists. overwrite? " % givenpath.given)
126
+ if confirm_overwrite:
127
+ vd.confirmOverwrite(givenpath)
119
128
 
120
129
  vd.status('saving %s sheets to %s as %s' % (len(vsheets), givenpath.given, filetype))
121
130
 
@@ -144,6 +153,9 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=False):
144
153
  p = Path((givenpath / vs.name).with_suffix('.'+filetype))
145
154
  savefunc(p, vs)
146
155
  vs.hasBeenModified = False
156
+
157
+ vd.status(f'{givenpath} save finished') #2157
158
+
147
159
  return vd.execAsync(_savefiles, vsheets, givenpath, savefunc, filetype)
148
160
 
149
161
 
@@ -161,19 +173,20 @@ def save_zip(vd, p, *vsheets):
161
173
  savefunc = getattr(vs, 'save_' + filetype, None) or getattr(vd, 'save_' + filetype, None)
162
174
  savefunc(tmpp, vs)
163
175
  zfp.write(tmpp, f'{vs.name}.{vs.options.save_filetype}')
164
- vd.status('%s save finished' % p)
165
176
 
166
177
 
167
178
  @VisiData.api
168
179
  def save_txt(vd, p, *vsheets):
169
- with p.open_text(mode='w', encoding=vsheets[0].options.encoding) as fp:
180
+ if len(vsheets) == 1 and vsheets[0].nVisibleCols > 1: #2173
181
+ return vd.save_tsv(p, vsheets[0])
182
+
183
+ with p.open(mode='w', encoding=vsheets[0].options.save_encoding) as fp:
170
184
  for vs in vsheets:
171
185
  unitsep = vs.options.delimiter
172
186
  rowsep = vs.options.row_delimiter
173
187
  for dispvals in vs.iterdispvals(*vs.visibleCols, format=True):
174
188
  fp.write(unitsep.join(dispvals.values()))
175
189
  fp.write(rowsep)
176
- vd.status('%s save finished' % p)
177
190
 
178
191
 
179
192
  @BaseSheet.api
@@ -184,9 +197,18 @@ def rootSheet(sheet):
184
197
 
185
198
  return r
186
199
 
187
- BaseSheet.addCommand('^S', 'save-sheet', 'vd.saveSheets(inputPath("save to: ", value=getDefaultSaveName()), sheet, confirm_overwrite=options.confirm_overwrite)', 'save current sheet to filename in format determined by extension (default .tsv)')
188
- BaseSheet.addCommand('', 'save-source', 'vd.saveSheets(rootSheet().source, rootSheet(), confirm_overwrite=options.confirm_overwrite)', 'save root sheet to its source')
189
- BaseSheet.addCommand('g^S', 'save-all', 'vd.saveSheets(inputPath("save all sheets to: "), *vd.stackedSheets, confirm_overwrite=options.confirm_overwrite)', 'save all sheets to given file or directory)')
190
- IndexSheet.addCommand('g^S', 'save-selected', 'vd.saveSheets(inputPath("save %d sheets to: " % nSelectedRows, value="_".join(getattr(vs, "name", None) or "blank" for vs in selectedRows)), *selectedRows, confirm_overwrite=options.confirm_overwrite)', 'save all selected sheets to given file or directory')
200
+
201
+ BaseSheet.addCommand('^S', 'save-sheet', 'vd.saveSheets(inputPath("save to: ", value=getDefaultSaveName()), sheet)', 'save current sheet to filename in format determined by extension (default .tsv)')
202
+ BaseSheet.addCommand('', 'save-sheet-really', 'vd.saveSheets(Path(getDefaultSaveName()), sheet, confirm_overwrite=False)', 'save current sheet without asking for filename or confirmation')
203
+ BaseSheet.addCommand('', 'save-source', 'vd.saveSheets(rootSheet().source, rootSheet())', 'save root sheet to its source')
204
+ BaseSheet.addCommand('g^S', 'save-all', 'vd.saveSheets(inputPath("save all sheets to: "), *vd.stackedSheets)', 'save all sheets to given file or directory)')
205
+ IndexSheet.addCommand('g^S', 'save-selected', 'vd.saveSheets(inputPath("save %d sheets to: " % nSelectedRows, value="_".join(getattr(vs, "name", None) or "blank" for vs in selectedRows)), *selectedRows)', 'save all selected sheets to given file or directory')
191
206
  Sheet.addCommand('', 'save-col', 'save_cols([cursorCol])', 'save current column only to filename in format determined by extension (default .tsv)')
192
207
  Sheet.addCommand('', 'save-col-keys', 'save_cols(keyCols + [cursorCol])', 'save key columns and current column to filename in format determined by extension (default .tsv)')
208
+
209
+ vd.addMenuItems('''
210
+ File > Save > current sheet > save-sheet
211
+ File > Save > all sheets > save-all
212
+ File > Save > current column > save-col
213
+ File > Save > keys and current column > save-col-keys
214
+ ''')
visidata/search.py CHANGED
@@ -3,6 +3,15 @@ from visidata import vd, VisiData, BaseSheet, Sheet, Column, Progress, asyncthre
3
3
 
4
4
  VisiData.init('searchContext', dict) # [(regex, columns, backward)] -> kwargs from previous search
5
5
 
6
+ vd.help_regex_flags = '''# Regex Flags Help
7
+ - `A` (ASCII) ASCII-only matching (not unicode)
8
+ - `I` (IGNORECASE): case-insensitive matching
9
+ - `M` (MULTILINE): `^` and `$` match after/before newlines
10
+ - `S` (DOTALL): `.` match any character at all, including newline
11
+ - `X` (VERBOSE): allow verbose regex
12
+ '''
13
+
14
+
6
15
  @VisiData.api
7
16
  @asyncthread
8
17
  def moveRegex(vd, sheet, *args, **kwargs):
@@ -11,7 +20,7 @@ def moveRegex(vd, sheet, *args, **kwargs):
11
20
 
12
21
  # kwargs: regex=None, columns=None, backward=False
13
22
  @VisiData.api
14
- def searchRegex(vd, sheet, moveCursor=False, reverse=False, **kwargs):
23
+ def searchRegex(vd, sheet, moveCursor=False, reverse=False, regex_flags=None, **kwargs):
15
24
  'Set row index if moveCursor, otherwise return list of row indexes.'
16
25
  def findMatchingColumn(sheet, row, columns, func):
17
26
  'Find column for which func matches the displayed value in this row'
@@ -23,7 +32,15 @@ def searchRegex(vd, sheet, moveCursor=False, reverse=False, **kwargs):
23
32
 
24
33
  regex = kwargs.get("regex")
25
34
  if regex:
26
- vd.searchContext["regex"] = re.compile(regex, sheet.regex_flags()) or vd.error('invalid regex: %s' % regex)
35
+ if regex_flags is None:
36
+ regex_flags = sheet.options.regex_flags # regex_flags defined in features.regex
37
+ flagbits = sum(getattr(re, f.upper()) for f in regex_flags)
38
+ try:
39
+ compiled_re = re.compile(regex, flagbits)
40
+ vd.searchContext["regex"] = compiled_re
41
+ except re.error as e:
42
+ vd.searchContext["regex"] = None # make future calls to search-next fail
43
+ vd.error('invalid regex: %s' % e.msg)
27
44
 
28
45
  regex = vd.searchContext.get("regex") or vd.fail("no regex")
29
46
 
@@ -54,8 +71,23 @@ def searchRegex(vd, sheet, moveCursor=False, reverse=False, **kwargs):
54
71
  matchingRowIndexes += 1
55
72
  yield rowidx
56
73
 
57
- vd.status('%s matches for /%s/' % (matchingRowIndexes, regex.pattern))
74
+ if kwargs.get('printStatus', True):
75
+ vd.status('%s matches for /%s/' % (matchingRowIndexes, regex.pattern))
76
+
77
+
78
+ @Sheet.api
79
+ def searchInputRegex(sheet, action:str, columns:str='cursorCol'):
80
+ r = vd.inputMultiple(regex=dict(prompt=f"{action} regex: ", type="regex", defaultLast=True, help=vd.help_regex),
81
+ flags=dict(prompt="regex flags: ", type="regex_flags", value=sheet.options.regex_flags, help=vd.help_regex_flags))
58
82
 
83
+ return vd.searchRegex(sheet, regex=r['regex'], regex_flags=r['flags'], columns=columns)
84
+
85
+ @Sheet.api
86
+ def moveInputRegex(sheet, action:str, type="regex", **kwargs):
87
+ r = vd.inputMultiple(regex=dict(prompt=f"{action} regex: ", type=type, defaultLast=True, help=vd.help_regex),
88
+ flags=dict(prompt="regex flags: ", type="regex_flags", value=sheet.options.regex_flags, help=vd.help_regex_flags))
89
+
90
+ return vd.moveRegex(sheet, regex=r['regex'], regex_flags=r['flags'], **kwargs)
59
91
 
60
92
  @Sheet.api
61
93
  @asyncthread
@@ -71,13 +103,25 @@ def search_expr(sheet, expr, reverse=False):
71
103
  vd.fail(f'no {sheet.rowtype} where {expr}')
72
104
 
73
105
 
74
- Sheet.addCommand('r', 'search-keys', 'tmp=cursorVisibleColIndex; vd.moveRegex(sheet, regex=input("row key regex: ", type="regex-row", defaultLast=True), columns=keyCols or [visibleCols[0]]); sheet.cursorVisibleColIndex=tmp', 'go to next row with key matching regex')
75
- Sheet.addCommand('/', 'search-col', 'vd.moveRegex(sheet, regex=input("/", type="regex", defaultLast=True), columns="cursorCol", backward=False)', 'search for regex forwards in current column'),
76
- Sheet.addCommand('?', 'searchr-col', 'vd.moveRegex(sheet, regex=input("?", type="regex", defaultLast=True), columns="cursorCol", backward=True)', 'search for regex backwards in current column'),
77
- Sheet.addCommand('n', 'search-next', 'vd.moveRegex(sheet, reverse=False)', 'go to next match from last regex search'),
78
- Sheet.addCommand('N', 'searchr-next', 'vd.moveRegex(sheet, reverse=True)', 'go to previous match from last regex search'),
106
+ Sheet.addCommand('r', 'search-keys', 'tmp=cursorVisibleColIndex; moveInputRegex("row key", type="regex-row", columns=keyCols or [visibleCols[0]]); sheet.cursorVisibleColIndex=tmp', 'go to next row with key matching regex')
107
+ Sheet.addCommand('/', 'search-col', 'moveInputRegex("search", columns="cursorCol", backward=False)', 'search for regex forwards in current column')
108
+ Sheet.addCommand('?', 'searchr-col', 'moveInputRegex("reverse search", columns="cursorCol", backward=True)', 'search for regex backwards in current column')
109
+ Sheet.addCommand('n', 'search-next', 'vd.moveRegex(sheet, reverse=False)', 'go to next match from last regex search')
110
+ Sheet.addCommand('N', 'searchr-next', 'vd.moveRegex(sheet, reverse=True)', 'go to previous match from last regex search')
79
111
 
80
- Sheet.addCommand('g/', 'search-cols', 'vd.moveRegex(sheet, regex=input("g/", type="regex", defaultLast=True), backward=False, columns="visibleCols")', 'search for regex forwards over all visible columns'),
81
- Sheet.addCommand('g?', 'searchr-cols', 'vd.moveRegex(sheet, regex=input("g?", type="regex", defaultLast=True), backward=True, columns="visibleCols")', 'search for regex backwards over all visible columns'),
112
+ Sheet.addCommand('g/', 'search-cols', 'moveInputRegex("g/", backward=False, columns="visibleCols")', 'search for regex forwards over all visible columns')
113
+ Sheet.addCommand('g?', 'searchr-cols', 'moveInputRegex("g?", backward=True, columns="visibleCols")', 'search for regex backwards over all visible columns')
82
114
  Sheet.addCommand('z/', 'search-expr', 'search_expr(inputExpr("search by expr: ") or fail("no expr"))', 'search by Python expression forwards in current column (with column names as variables)')
83
115
  Sheet.addCommand('z?', 'searchr-expr', 'search_expr(inputExpr("searchr by expr: ") or fail("no expr"), reverse=True)', 'search by Python expression backwards in current column (with column names as variables)')
116
+
117
+ vd.addMenuItems('''
118
+ View > Search > current column > search-col
119
+ View > Search > visible columns > search-cols
120
+ View > Search > key columns > search-keys
121
+ View > Search > by Python expr > search-expr
122
+ View > Search > again > search-next
123
+ View > Search backward > current column > searchr-col
124
+ View > Search backward > visible columns > searchr-cols
125
+ View > Search backward > by Python expr > searchr-expr
126
+ View > Search backward > again > searchr-next
127
+ ''')