visidata 2.11.1__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 (255) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  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. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +59 -50
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +5 -1
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +30 -5
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +163 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +75 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +180 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +17 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +48 -10
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +301 -148
  187. visidata/man/vd.txt +290 -153
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +50 -201
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +229 -257
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +113 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/conftest.py +3 -3
  213. visidata/tests/test_cliptext.py +39 -0
  214. visidata/tests/test_commands.py +62 -7
  215. visidata/tests/test_edittext.py +2 -2
  216. visidata/tests/test_features.py +17 -0
  217. visidata/tests/test_menu.py +14 -0
  218. visidata/tests/test_path.py +13 -4
  219. visidata/text_source.py +53 -0
  220. visidata/textsheet.py +10 -3
  221. visidata/theme.py +44 -0
  222. visidata/themes/__init__.py +0 -0
  223. visidata/themes/ascii8.py +84 -0
  224. visidata/themes/asciimono.py +84 -0
  225. visidata/themes/light.py +17 -0
  226. visidata/threads.py +87 -39
  227. visidata/tuiwin.py +22 -0
  228. visidata/type_currency.py +22 -3
  229. visidata/type_date.py +31 -9
  230. visidata/type_floatsi.py +5 -1
  231. visidata/undo.py +17 -5
  232. visidata/utils.py +106 -23
  233. visidata/vdobj.py +28 -17
  234. visidata/windows.py +10 -0
  235. visidata/wrappers.py +9 -3
  236. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  237. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/vd.1 +301 -148
  238. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +301 -148
  239. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  240. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/METADATA +12 -8
  241. visidata-3.0.dist-info/RECORD +257 -0
  242. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  243. vgit/__init__.py +0 -1
  244. vgit/gitsheet.py +0 -164
  245. visidata/layout.py +0 -44
  246. visidata/misc.py +0 -5
  247. visidata-2.11.1.data/scripts/vgit +0 -9
  248. visidata-2.11.1.dist-info/RECORD +0 -155
  249. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  250. {vgit → visidata/apps/vgit}/abort.py +0 -0
  251. /visidata/{repeat.py → features/repeat.py} +0 -0
  252. {visidata-2.11.1.data → visidata-3.0.data}/scripts/vd +0 -0
  253. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -0
  255. {visidata-2.11.1.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,17 +1,17 @@
1
1
  import collections
2
+ import os
2
3
  from copy import copy
3
4
 
4
- from visidata import *
5
+ from visidata import vd
6
+ from visidata import Sheet, BaseSheet, VisiData, IndexSheet, Path, Progress, TypedExceptionWrapper
5
7
 
6
-
7
- vd.option('confirm_overwrite', True, 'whether to prompt for overwrite confirmation on save')
8
8
  vd.option('safe_error', '#ERR', 'error string to use while saving', replay=True)
9
- 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)
10
10
 
11
11
  @Sheet.api
12
12
  def safe_trdict(vs):
13
13
  'returns string.translate dictionary for replacing tabs and newlines'
14
- if options.safety_first:
14
+ if vs.options.safety_first:
15
15
  delim = vs.options.delimiter
16
16
  return {
17
17
  0: '', # strip NUL completely
@@ -38,7 +38,7 @@ def iterdispvals(sheet, *cols, format=False):
38
38
  if trdict:
39
39
  transformers[col].append(lambda v,trdict=trdict: v.translate(trdict))
40
40
 
41
- options_safe_error = options.safe_error
41
+ options_safe_error = sheet.options.safe_error
42
42
  for r in Progress(sheet.rows):
43
43
  dispvals = collections.OrderedDict() # [col] -> value
44
44
  for col, transforms in transformers.items():
@@ -85,7 +85,7 @@ def getDefaultSaveName(sheet):
85
85
  return str(src.with_suffix('')) + '.' + sheet.options.save_filetype
86
86
  return str(src)
87
87
  else:
88
- return sheet.name+'.'+getattr(sheet, 'filetype', options.save_filetype)
88
+ return sheet.name+'.'+getattr(sheet, 'filetype', sheet.options.save_filetype)
89
89
 
90
90
 
91
91
  @VisiData.api
@@ -99,28 +99,32 @@ def save_cols(vd, cols):
99
99
  else:
100
100
  savedcoltxt = '%s columns' % len(cols)
101
101
  path = vd.inputPath('save %s to: ' % savedcoltxt, value=vs.getDefaultSaveName())
102
- vd.saveSheets(path, vs, confirm_overwrite=options.confirm_overwrite)
102
+ vd.saveSheets(path, vs)
103
103
 
104
104
 
105
105
  @VisiData.api
106
- def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=False):
106
+ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
107
107
  'Save all *vsheets* to *givenpath*.'
108
108
 
109
109
  if not vsheets: # blank tuple
110
110
  vd.warning('no sheets to save')
111
111
  return
112
112
 
113
- filetype = givenpath.ext or options.save_filetype
113
+ filetypes = [givenpath.ext, vd.options.save_filetype]
114
114
 
115
115
  vd.clearCaches()
116
116
 
117
- 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
118
122
 
119
123
  if savefunc is None:
120
124
  vd.fail(f'no function to save as {filetype}')
121
125
 
122
- if givenpath.exists() and confirm_overwrite:
123
- vd.confirm("%s already exists. overwrite? " % givenpath.given)
126
+ if confirm_overwrite:
127
+ vd.confirmOverwrite(givenpath)
124
128
 
125
129
  vd.status('saving %s sheets to %s as %s' % (len(vsheets), givenpath.given, filetype))
126
130
 
@@ -149,6 +153,9 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=False):
149
153
  p = Path((givenpath / vs.name).with_suffix('.'+filetype))
150
154
  savefunc(p, vs)
151
155
  vs.hasBeenModified = False
156
+
157
+ vd.status(f'{givenpath} save finished') #2157
158
+
152
159
  return vd.execAsync(_savefiles, vsheets, givenpath, savefunc, filetype)
153
160
 
154
161
 
@@ -166,19 +173,20 @@ def save_zip(vd, p, *vsheets):
166
173
  savefunc = getattr(vs, 'save_' + filetype, None) or getattr(vd, 'save_' + filetype, None)
167
174
  savefunc(tmpp, vs)
168
175
  zfp.write(tmpp, f'{vs.name}.{vs.options.save_filetype}')
169
- vd.status('%s save finished' % p)
170
176
 
171
177
 
172
178
  @VisiData.api
173
179
  def save_txt(vd, p, *vsheets):
174
- 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:
175
184
  for vs in vsheets:
176
185
  unitsep = vs.options.delimiter
177
186
  rowsep = vs.options.row_delimiter
178
187
  for dispvals in vs.iterdispvals(*vs.visibleCols, format=True):
179
188
  fp.write(unitsep.join(dispvals.values()))
180
189
  fp.write(rowsep)
181
- vd.status('%s save finished' % p)
182
190
 
183
191
 
184
192
  @BaseSheet.api
@@ -189,9 +197,18 @@ def rootSheet(sheet):
189
197
 
190
198
  return r
191
199
 
192
- 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)')
193
- BaseSheet.addCommand('', 'save-source', 'vd.saveSheets(rootSheet().source, rootSheet(), confirm_overwrite=options.confirm_overwrite)', 'save root sheet to its source')
194
- 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)')
195
- 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')
196
206
  Sheet.addCommand('', 'save-col', 'save_cols([cursorCol])', 'save current column only to filename in format determined by extension (default .tsv)')
197
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
+ ''')