visidata 3.0.2__py3-none-any.whl → 3.1.1__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 (150) hide show
  1. visidata/__init__.py +12 -10
  2. visidata/_input.py +208 -202
  3. visidata/_open.py +4 -1
  4. visidata/_types.py +4 -3
  5. visidata/aggregators.py +88 -39
  6. visidata/apps/vdsql/_ibis.py +7 -11
  7. visidata/apps/vdsql/clickhouse.py +2 -2
  8. visidata/apps/vdsql/snowflake.py +1 -1
  9. visidata/apps/vgit/status.py +1 -1
  10. visidata/basesheet.py +11 -4
  11. visidata/canvas.py +54 -20
  12. visidata/clipboard.py +13 -6
  13. visidata/cliptext.py +7 -6
  14. visidata/cmdlog.py +40 -27
  15. visidata/column.py +14 -49
  16. visidata/ddw/regex.ddw +3 -2
  17. visidata/deprecated.py +14 -2
  18. visidata/desktop/visidata.desktop +2 -2
  19. visidata/editor.py +1 -0
  20. visidata/errors.py +1 -1
  21. visidata/experimental/sort_selected.py +54 -0
  22. visidata/expr.py +69 -18
  23. visidata/features/change_precision.py +1 -3
  24. visidata/features/cmdpalette.py +17 -2
  25. visidata/features/colorsheet.py +1 -1
  26. visidata/features/dedupe.py +3 -3
  27. visidata/features/go_col.py +71 -0
  28. visidata/features/graph_seaborn.py +1 -1
  29. visidata/features/join.py +20 -10
  30. visidata/features/layout.py +16 -3
  31. visidata/features/ping.py +16 -12
  32. visidata/features/regex.py +5 -5
  33. visidata/features/status_source.py +3 -1
  34. visidata/features/sysedit.py +1 -1
  35. visidata/features/transpose.py +2 -1
  36. visidata/features/type_ipaddr.py +2 -4
  37. visidata/features/unfurl.py +1 -0
  38. visidata/form.py +2 -2
  39. visidata/freqtbl.py +16 -11
  40. visidata/fuzzymatch.py +1 -0
  41. visidata/graph.py +163 -12
  42. visidata/guide.py +57 -24
  43. visidata/guides/ClipboardGuide.md +48 -0
  44. visidata/guides/ColumnsGuide.md +52 -0
  45. visidata/guides/CommandsSheet.md +28 -0
  46. visidata/guides/DirSheet.md +34 -0
  47. visidata/{features/errors_guide.py → guides/ErrorsSheet.md} +2 -11
  48. visidata/guides/FrequencyTable.md +42 -0
  49. visidata/guides/GrepSheet.md +28 -0
  50. visidata/guides/JsonSheet.md +38 -0
  51. visidata/guides/MacrosSheet.md +19 -0
  52. visidata/guides/MeltGuide.md +52 -0
  53. visidata/guides/MemorySheet.md +7 -0
  54. visidata/guides/MenuGuide.md +26 -0
  55. visidata/guides/ModifyGuide.md +38 -0
  56. visidata/guides/PivotGuide.md +71 -0
  57. visidata/guides/RegexGuide.md +107 -0
  58. visidata/guides/SelectionGuide.md +44 -0
  59. visidata/guides/SlideGuide.md +26 -0
  60. visidata/guides/SplitpaneGuide.md +15 -0
  61. visidata/guides/TypesSheet.md +43 -0
  62. visidata/guides/XsvGuide.md +36 -0
  63. visidata/help.py +6 -6
  64. visidata/hint.py +2 -1
  65. visidata/indexsheet.py +2 -2
  66. visidata/interface.py +13 -14
  67. visidata/keys.py +4 -1
  68. visidata/loaders/api_airtable.py +1 -1
  69. visidata/loaders/archive.py +1 -1
  70. visidata/loaders/csv.py +9 -5
  71. visidata/loaders/eml.py +11 -6
  72. visidata/loaders/f5log.py +1 -0
  73. visidata/loaders/fec.py +18 -42
  74. visidata/loaders/fixed_width.py +19 -3
  75. visidata/loaders/grep.py +121 -0
  76. visidata/loaders/html.py +1 -0
  77. visidata/loaders/http.py +6 -1
  78. visidata/loaders/json.py +22 -4
  79. visidata/loaders/jsonla.py +8 -2
  80. visidata/loaders/mailbox.py +1 -0
  81. visidata/loaders/markdown.py +25 -6
  82. visidata/loaders/msgpack.py +19 -0
  83. visidata/loaders/npy.py +0 -1
  84. visidata/loaders/odf.py +18 -4
  85. visidata/loaders/orgmode.py +1 -1
  86. visidata/loaders/rec.py +6 -4
  87. visidata/loaders/sas.py +11 -4
  88. visidata/loaders/scrape.py +0 -1
  89. visidata/loaders/texttables.py +2 -0
  90. visidata/loaders/tsv.py +24 -7
  91. visidata/loaders/unzip_http.py +127 -3
  92. visidata/loaders/vds.py +4 -0
  93. visidata/loaders/vdx.py +1 -1
  94. visidata/loaders/xlsx.py +5 -0
  95. visidata/loaders/xml.py +2 -1
  96. visidata/macros.py +14 -31
  97. visidata/main.py +14 -13
  98. visidata/mainloop.py +14 -6
  99. visidata/man/vd.1 +72 -39
  100. visidata/man/vd.txt +72 -41
  101. visidata/memory.py +15 -4
  102. visidata/menu.py +14 -3
  103. visidata/metasheets.py +5 -6
  104. visidata/modify.py +4 -4
  105. visidata/mouse.py +2 -0
  106. visidata/movement.py +14 -28
  107. visidata/optionssheet.py +3 -5
  108. visidata/path.py +59 -37
  109. visidata/pivot.py +8 -5
  110. visidata/pyobj.py +63 -9
  111. visidata/save.py +16 -9
  112. visidata/search.py +4 -4
  113. visidata/selection.py +10 -56
  114. visidata/settings.py +37 -35
  115. visidata/sheets.py +186 -108
  116. visidata/shell.py +22 -12
  117. visidata/sidebar.py +71 -16
  118. visidata/sort.py +21 -6
  119. visidata/statusbar.py +42 -5
  120. visidata/stored_list.py +5 -2
  121. visidata/tests/conftest.py +1 -0
  122. visidata/tests/test_commands.py +9 -1
  123. visidata/tests/test_completer.py +18 -0
  124. visidata/tests/test_edittext.py +3 -2
  125. visidata/text_source.py +7 -4
  126. visidata/textsheet.py +20 -6
  127. visidata/themes/ascii8.py +9 -6
  128. visidata/themes/asciimono.py +14 -4
  129. visidata/threads.py +13 -3
  130. visidata/tuiwin.py +5 -1
  131. visidata/type_currency.py +1 -2
  132. visidata/type_date.py +6 -1
  133. visidata/undo.py +10 -5
  134. visidata/utils.py +9 -3
  135. visidata/vdobj.py +21 -1
  136. visidata/wrappers.py +9 -1
  137. {visidata-3.0.2.data → visidata-3.1.1.data}/data/share/applications/visidata.desktop +2 -2
  138. {visidata-3.0.2.data → visidata-3.1.1.data}/data/share/man/man1/vd.1 +72 -39
  139. {visidata-3.0.2.data → visidata-3.1.1.data}/data/share/man/man1/visidata.1 +72 -39
  140. {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/METADATA +24 -6
  141. visidata-3.1.1.dist-info/RECORD +280 -0
  142. visidata/loaders/api_bitio.py +0 -102
  143. visidata/stored_prop.py +0 -38
  144. visidata-3.0.2.dist-info/RECORD +0 -258
  145. {visidata-3.0.2.data → visidata-3.1.1.data}/scripts/vd +0 -0
  146. {visidata-3.0.2.data → visidata-3.1.1.data}/scripts/vd2to3.vdx +0 -0
  147. {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/LICENSE.gpl3 +0 -0
  148. {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/WHEEL +0 -0
  149. {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/entry_points.txt +0 -0
  150. {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/top_level.txt +0 -0
@@ -58,12 +58,18 @@ def inputPalette(sheet, prompt, items,
58
58
  formatter=lambda m, item, trigger_key: f'{trigger_key} {item}',
59
59
  multiple=False,
60
60
  **kwargs):
61
+ if sheet.options.disp_expert >= 5:
62
+ return vd.input(prompt,
63
+ completer=CompleteKey(sorted(item[value_key] for item in items)),
64
+ **kwargs)
65
+
61
66
  bindings = dict()
62
67
 
63
68
  tabitem = -1
64
69
 
65
70
  def tab(n, nitems):
66
71
  nonlocal tabitem
72
+ if not nitems: return None
67
73
  tabitem = (tabitem + n) % nitems
68
74
 
69
75
  def _draw_palette(value):
@@ -123,12 +129,14 @@ def inputPalette(sheet, prompt, items,
123
129
 
124
130
  if tabitem < 0 and palrows:
125
131
  _ , topitem = palrows[0]
132
+ if not topitem: return
126
133
  if multiple:
127
134
  bindings[' '] = partial(add_to_input, value=topitem[value_key])
128
135
  bindings['^J'] = partial(accept_input_if_subset, value=topitem[value_key])
129
136
  else:
130
137
  bindings['^J'] = partial(accept_input, value=topitem[value_key])
131
138
  elif item and i == tabitem:
139
+ if not item: return
132
140
  if multiple:
133
141
  bindings['^J'] = partial(accept_input_if_subset, value=item[value_key])
134
142
  bindings[' '] = partial(add_to_input, value=item[value_key])
@@ -164,12 +172,12 @@ def inputLongname(sheet):
164
172
  prompt = 'command name: '
165
173
  # get set of commands possible in the sheet
166
174
  this_sheets_help = HelpSheet('', source=sheet)
167
- this_sheets_help.ensureLoaded()
175
+ vd.sync(this_sheets_help.ensureLoaded())
168
176
 
169
177
  def _fmt_cmdpal_summary(match, row, trigger_key):
170
178
  keystrokes = this_sheets_help.revbinds.get(row.longname, [None])[0] or ' '
171
179
  formatted_longname = match.formatted.get('longname', row.longname) if match else row.longname
172
- formatted_name = f'[:longname][:onclick {row.longname}]{formatted_longname}[/][/]'
180
+ formatted_name = f'[:bold][:onclick {row.longname}]{formatted_longname}[/][/]'
173
181
  if vd.options.debug and match:
174
182
  keystrokes = f'[{match.score}]'
175
183
  r = f' [:keystrokes]{keystrokes.rjust(len(prompt)-5)}[/] '
@@ -190,6 +198,12 @@ def inputLongname(sheet):
190
198
  help=vd.help_longname,
191
199
  type='longname')
192
200
 
201
+ @BaseSheet.api
202
+ def inputLongnameSimple(sheet):
203
+ 'Input a command longname without using the command palette.'
204
+ longnames = set(k for (k, obj), v in vd.commands.iter(sheet))
205
+ return vd.input("command name: ", completer=CompleteKey(sorted(longnames)), type='longname')
206
+
193
207
 
194
208
  @BaseSheet.api
195
209
  def exec_longname(sheet, longname):
@@ -199,3 +213,4 @@ def exec_longname(sheet, longname):
199
213
 
200
214
 
201
215
  vd.addCommand('Space', 'exec-longname', 'exec_longname(inputLongname())', 'execute command by its longname')
216
+ vd.addCommand('zSpace', 'exec-longname-simple', 'exec_longname(inputLongnameSimple())', 'execute command by its longname')
@@ -44,4 +44,4 @@ BaseSheet.addCommand(None, 'open-colors', 'vd.push(vd.colorsSheet)', 'open Color
44
44
 
45
45
  @VisiData.lazy_property
46
46
  def colorsSheet(vd):
47
- return ColorSheet()
47
+ return ColorSheet('colors')
@@ -24,7 +24,7 @@ __author__ = "Jeremy Singer-Vine <jsvine@gmail.com>"
24
24
 
25
25
  from copy import copy
26
26
 
27
- from visidata import Sheet, BaseSheet, asyncthread, Progress, vd
27
+ from visidata import Sheet, TableSheet, asyncthread, Progress, vd
28
28
 
29
29
 
30
30
  def gen_identify_duplicates(sheet):
@@ -103,8 +103,8 @@ def dedupe_rows(sheet):
103
103
 
104
104
 
105
105
  # Add longname-commands to VisiData to execute these methods
106
- BaseSheet.addCommand(None, "select-duplicate-rows", "sheet.select_duplicate_rows()", "select each row that is a duplicate of a prior row")
107
- BaseSheet.addCommand(None, "dedupe-rows", "vd.push(sheet.dedupe_rows())", "open new sheet in which only non-duplicate rows in the active sheet are included")
106
+ TableSheet.addCommand(None, "select-duplicate-rows", "sheet.select_duplicate_rows()", "select each row that is a duplicate of a prior row")
107
+ TableSheet.addCommand(None, "dedupe-rows", "vd.push(sheet.dedupe_rows())", "open new sheet in which only non-duplicate rows in the active sheet are included")
108
108
 
109
109
  vd.addMenuItems('''
110
110
  Row > Select > duplicate rows > select-duplicate-rows
@@ -0,0 +1,71 @@
1
+ import itertools
2
+ import re
3
+ from visidata import vd, Sheet, AttrDict, dispwidth
4
+
5
+
6
+ @Sheet.api
7
+ def nextColRegex(sheet, colregex):
8
+ 'Go to first visible column after the cursor matching `colregex`.'
9
+ pivot = sheet.cursorVisibleColIndex
10
+ for i in itertools.chain(range(pivot+1, len(sheet.visibleCols)), range(0, pivot+1)):
11
+ c = sheet.visibleCols[i]
12
+ if re.search(colregex, c.name, sheet.regex_flags()):
13
+ return i
14
+
15
+ vd.fail('no column name matches /%s/' % colregex)
16
+
17
+ @Sheet.api
18
+ def nextColName(sheet, show_cells=True):
19
+ if len(sheet.visibleCols) == 0: vd.fail('no columns to choose from')
20
+
21
+ prompt = 'choose column name: '
22
+ colnames = []
23
+ for c in sheet.visibleCols:
24
+ dv = None
25
+ if show_cells and len(sheet.rows) > 0:
26
+ dv = c.getDisplayValue(sheet.cursorRow)
27
+ #the underscore that starts _cursor_cell excludes it from being fuzzy matched
28
+ item = AttrDict(name_lower=c.name.lower(),
29
+ name=c.name,
30
+ _cursor_cell=dv)
31
+ colnames.append(item)
32
+
33
+ def _fmt_colname(match, row, trigger_key):
34
+ name = match.formatted.get('name', row.name) if match else row.name
35
+ r = ' '*(len(prompt)-3)
36
+ r += f'[:keystrokes]{trigger_key}[/] '
37
+ if show_cells and len(sheet.rows) > 0:
38
+ # pad the right side with spaces
39
+ # use row.name, because name from match contains
40
+ # extra formatting characters that change its length
41
+ n_spaces = max(20 - dispwidth(row.name), 0)
42
+ r += name + n_spaces*' '
43
+ r += ' '
44
+ #todo: does not show disp_note_none for None
45
+ r += row._cursor_cell
46
+ else:
47
+ r += name
48
+ return r
49
+ name = vd.activeSheet.inputPalette(prompt,
50
+ colnames,
51
+ value_key='name',
52
+ type='column_name',
53
+ formatter=_fmt_colname)
54
+
55
+ pivot = sheet.cursorVisibleColIndex
56
+ for i in itertools.chain(range(pivot+1, len(sheet.visibleCols)), range(0, pivot+1)):
57
+ if name == sheet.visibleCols[i].name:
58
+ return i
59
+
60
+ vd.warning(f'found no column with name: {name}')
61
+
62
+
63
+ Sheet.addCommand('c', 'go-col-regex', 'sheet.cursorVisibleColIndex=nextColRegex(inputRegex("column name regex: ", type="regex-col", defaultLast=True))', 'go to next column with name matching regex')
64
+ Sheet.addCommand('zc', 'go-col-number', 'sheet.cursorVisibleColIndex = int(input("move to column number: "))', 'go to given column number (0-based)')
65
+ Sheet.addCommand('', 'go-col-name', 'sheet.cursorVisibleColIndex=nextColName()', 'go to next column with name matching string, case-insensitive')
66
+
67
+ vd.addMenuItems('''
68
+ Column > Goto > by regex > go-col-regex
69
+ Column > Goto > by number > go-col-number
70
+ Column > Goto > by name > go-col-name
71
+ ''')
@@ -54,7 +54,7 @@ def ext_plot_seaborn(rows, xcols, ycols):
54
54
  nplotted += 1
55
55
  except Exception:
56
56
  nerrors += 1
57
- if options.debug:
57
+ if vd.options.debug:
58
58
  raise
59
59
 
60
60
  sns.scatterplot(
visidata/features/join.py CHANGED
@@ -18,7 +18,8 @@ def ensureLoaded(vd, sheets):
18
18
 
19
19
  @asyncthread
20
20
  def _appendRowsAfterLoading(joinsheet, origsheets):
21
- if vd.ensureLoaded(origsheets):
21
+ with Progress(gerund='loading'):
22
+ vd.ensureLoaded(origsheets)
22
23
  vd.sync()
23
24
 
24
25
  colnames = {c.name:c for c in joinsheet.visibleCols}
@@ -95,7 +96,7 @@ vd.jointypes = [AttrDict(key=k, desc=v) for k, v in {
95
96
  'append': 'all rows from all sheets; columns from all sheets',
96
97
  'concat': 'all rows from all sheets; columns and type from first sheet',
97
98
  'extend': 'only rows from first sheet; type from first sheet; columns from all sheets',
98
- 'merge': 'merge differences from other sheets into first sheet (including new rows)',
99
+ 'merge': 'all rows from all sheets; where keys match, use latest truthy value',
99
100
  }.items()]
100
101
 
101
102
  def joinkey(sheetKeyCols, row):
@@ -154,7 +155,9 @@ class JoinKeyColumn(Column):
154
155
 
155
156
 
156
157
  class MergeColumn(Column):
158
+ # .cols is { sheet: col, ... } in sheet-join order
157
159
  def calcValue(self, row):
160
+ 'Return value from last joined sheet with truth-y value in this column for the given row.'
158
161
  for vs, c in reversed(list(self.cols.items())):
159
162
  if c:
160
163
  v = c.getTypedValue(row[vs])
@@ -167,7 +170,11 @@ class MergeColumn(Column):
167
170
 
168
171
  def isDiff(self, row, value):
169
172
  col = list(self.cols.values())[0]
170
- return col and value != col.getValue(row[col.sheet])
173
+ if not col:
174
+ return False
175
+ if row[col.sheet] is None:
176
+ return True
177
+ return value != col.getValue(row[col.sheet])
171
178
 
172
179
 
173
180
  #### slicing and dicing
@@ -184,8 +191,9 @@ class JoinSheet(Sheet):
184
191
  def loader(self):
185
192
  sheets = self.sources
186
193
 
187
- vd.ensureLoaded(sheets)
188
- vd.sync()
194
+ with Progress(gerund='loading'):
195
+ vd.ensureLoaded(sheets)
196
+ vd.sync()
189
197
 
190
198
  # first item in joined row is the key tuple from the first sheet.
191
199
  # first columns are the key columns from the first sheet, using its row (0)
@@ -196,6 +204,7 @@ class JoinSheet(Sheet):
196
204
  self.setKeys(self.columns)
197
205
 
198
206
  allcols = collections.defaultdict(dict) # colname: { sheet: origcol, ... }
207
+ # MergeColumn relies on allcols having sheets in this specific order
199
208
  for sheetnum, vs in enumerate(sheets):
200
209
  for c in vs.visibleCols:
201
210
  if c not in self.sheetKeyCols[vs]:
@@ -266,8 +275,9 @@ def ExtendedSheet_reload(self, sheets):
266
275
  # first item in joined row is the key tuple from the first sheet.
267
276
  # first columns are the key columns from the first sheet, using its row (0)
268
277
 
269
- vd.ensureLoaded(sheets)
270
- vd.sync()
278
+ with Progress(gerund='loading'):
279
+ vd.ensureLoaded(sheets)
280
+ vd.sync()
271
281
 
272
282
  self.columns = []
273
283
  for i, c in enumerate(sheets[0].keyCols):
@@ -331,11 +341,11 @@ class ConcatSheet(Sheet):
331
341
  # only one column with each name allowed per sheet
332
342
  keyedcols = collections.defaultdict(dict) # name -> { sheet -> col }
333
343
 
344
+ with Progress(gerund='loading'):
345
+ vd.ensureLoaded(self.source)
346
+ vd.sync()
334
347
  with Progress(gerund='joining', sheet=self, total=sum(vs.nRows for vs in self.source)) as prog:
335
348
  for sheet in self.source:
336
- if sheet.ensureLoaded():
337
- vd.sync()
338
-
339
349
  for r in sheet.rows:
340
350
  yield (sheet, r)
341
351
  prog.addProgress(1)
@@ -1,4 +1,4 @@
1
- from visidata import VisiData, vd, Column, Sheet, Fanout
1
+ from visidata import VisiData, vd, Column, Sheet, Fanout, asyncthread
2
2
 
3
3
  @Column.api
4
4
  def setWidth(self, w):
@@ -35,6 +35,17 @@ def hide_col(vd, col):
35
35
  if not col: vd.fail("no columns to hide")
36
36
  col.hide()
37
37
 
38
+ @Sheet.api
39
+ @asyncthread
40
+ def hide_uniform_cols(sheet):
41
+ if len(sheet.rows) < 2:
42
+ return
43
+ for col in sheet.visibleCols:
44
+ vals = (col.getTypedValue(r) for r in sheet.rows)
45
+ first = next(vals)
46
+ if all(v == first for v in vals):
47
+ col.hide()
48
+
38
49
  Sheet.addCommand('_', 'resize-col-max', 'if cursorCol: cursorCol.toggleWidth(cursorCol.getMaxWidth(visibleRows))', 'toggle width of current column between full and default width')
39
50
  Sheet.addCommand('z_', 'resize-col-input', 'width = int(input("set width= ", value=cursorCol.width)); cursorCol.setWidth(width)', 'adjust width of current column to N')
40
51
  Sheet.addCommand('g_', 'resize-cols-max', 'for c in visibleCols: c.setWidth(c.getMaxWidth(visibleRows))', 'toggle widths of all visible columns between full and default width')
@@ -42,6 +53,7 @@ Sheet.addCommand('gz_', 'resize-cols-input', 'width = int(input("set width= ", v
42
53
 
43
54
  Sheet.addCommand('-', 'hide-col', 'hide_col(cursorCol)', 'hide the current column')
44
55
  Sheet.addCommand('z-', 'resize-col-half', 'cursorCol.setWidth(cursorCol.width//2)', 'reduce width of current column by half')
56
+ Sheet.addCommand(None, 'hide-uniform-cols', 'sheet.hide_uniform_cols()', 'hide any column that has multiple rows but only one distinct value')
45
57
 
46
58
  Sheet.addCommand('gv', 'unhide-cols', 'unhide_cols(columns, visibleRows)', 'unhide all hidden columns on current sheet')
47
59
  Sheet.addCommand('v', 'toggle-multiline', 'for c in visibleCols: c.toggleMultiline()', 'toggle multiline display')
@@ -49,8 +61,9 @@ Sheet.addCommand('zv', 'resize-height-input', 'Fanout(visibleCols).height=int(in
49
61
  Sheet.addCommand('gzv', 'resize-height-max', 'h=calc_height(cursorRow, {}, maxheight=windowHeight-1); vd.status(f"set height for all columns to {h}"); Fanout(visibleCols).height=h', 'resize row height to max height needed to see this row')
50
62
 
51
63
  vd.addMenuItems('''
52
- Column > Hide > hide-col
53
- Column > Unhide all > unhide-cols
64
+ Column > Hide > Hide > hide-col
65
+ Column > Hide > Hide uniform > hide-uniform-cols
66
+ Column > Hide > Unhide all > unhide-cols
54
67
  Column > Resize > half width > resize-col-half
55
68
  Column > Resize > current column width to max > resize-col-max
56
69
  Column > Resize > current column width to N > resize-col-input
visidata/features/ping.py CHANGED
@@ -7,23 +7,23 @@ from statistics import mean
7
7
 
8
8
  from visidata import vd, VisiData, BaseSheet, Sheet, Column, AttrColumn, Progress
9
9
 
10
- vd.theme_option('color_shellcmd', '21 on 114 green', '')
11
- vd.theme_option('color_colname', 'underline', '')
12
- vd.theme_option('color_longname', 'bold 52 on 114 green', '')
13
-
14
-
15
10
  @VisiData.api
16
11
  def new_ping(vd, p):
17
12
  'Open a sheet with the round-trip time for each hop along the path to the given host.'
18
- pingsheet = PingSheet(p.given, source=p.given)
19
- return PingStatsSheet("traceroute_"+pingsheet.name, source=pingsheet)
13
+ return makePingStats(p.given)
20
14
 
15
+ # open_ping() is called when a local file exists matching the path p, otherwise new_ping() is called
16
+ vd.open_ping = vd.new_ping
17
+
18
+ def makePingStats(ip):
19
+ pingsheet = PingSheet(ip, source=ip)
20
+ return PingStatsSheet("traceroute_"+pingsheet.name, source=pingsheet)
21
21
 
22
22
  class PingStatsSheet(Sheet):
23
23
  help='''# ping/traceroute
24
- This sheet runs {shellcmd}traceroute{} to generate intermediate hops, then runs {shellcmd}ping{} against each hop N times to get the {colname}avg_ms{} and {colname}max_ms{} ping time.
24
+ This sheet runs `traceroute` to generate intermediate hops, then runs `ping` against each hop N times to get the `avg_ms` and `max_ms` ping time.
25
25
 
26
- {longname}open-source{} to open the raw ping data.
26
+ - {help.commands.open_source} (for raw ping data)
27
27
  '''
28
28
  rowtype='hosts' # rowdef: PingColumn
29
29
  columns = [
@@ -36,7 +36,7 @@ This sheet runs {shellcmd}traceroute{} to generate intermediate hops, then runs
36
36
  ]
37
37
  def loader(self):
38
38
  sheet = self.source
39
- sheet.ensureLoaded()
39
+ vd.sync(sheet.ensureLoaded())
40
40
 
41
41
  self.rows = sheet.columns
42
42
 
@@ -76,7 +76,7 @@ class PingSheet(Sheet):
76
76
  def ping_error(self, ip, data):
77
77
  if ip in self.sources:
78
78
  self.sources.remove(ip)
79
- vd.warning("%s removed: %s" % (ip, data))
79
+ vd.warning("%s removed: %s" % (ip, data.rstrip()))
80
80
 
81
81
  def update_traces(self, row, ip):
82
82
  rtes = self.routes.get(ip)
@@ -150,7 +150,11 @@ vd.option('ping_count', 3, 'send this many pings to each host', sheettype=PingSh
150
150
  vd.option('ping_interval', 0.1, 'wait between ping rounds, in seconds', sheettype=PingSheet)
151
151
 
152
152
  PingSheet.options.null_value = False
153
- BaseSheet.addCommand('', 'open-ping', 'vd.push(openSource(input("ping: ", type="hostip"), filetype="ping"))', 'open sheet to ping input IP Address')
153
+ BaseSheet.addCommand('', 'open-ping', 'vd.push(makePingStats(vd.input("ping: ", type="hostip")))', 'open sheet to ping input IP Address')
154
+
155
+ vd.addGlobals(
156
+ makePingStats=makePingStats,
157
+ )
154
158
 
155
159
  vd.addMenuItems('''
156
160
  System > Ping IP/hostname > open-ping
@@ -126,12 +126,12 @@ def inputRegexSubst(vd, prompt):
126
126
  after=dict(type='regex-replace', prompt='replace: ', help=prompt))
127
127
 
128
128
 
129
- Sheet.addCommand(':', 'addcol-split', 'addColumnAtCursor(RegexColumn(makeRegexSplitter, cursorCol, inputRegex("split regex: ", type="regex-split")))', 'Add column split by regex')
130
- Sheet.addCommand(';', 'addcol-capture', 'addColumnAtCursor(RegexColumn(makeRegexMatcher, cursorCol, inputRegex("capture regex: ", type="regex-capture")))', 'Add column captured by regex')
129
+ Sheet.addCommand(':', 'addcol-split', 'addColumnAtCursor(RegexColumn(makeRegexSplitter, cursorCol, inputRegex("split regex: ", type="regex-split")))', 'add column split by regex')
130
+ Sheet.addCommand(';', 'addcol-capture', 'addColumnAtCursor(RegexColumn(makeRegexMatcher, cursorCol, inputRegex("capture regex: ", type="regex-capture")))', 'add column captured by regex')
131
131
 
132
- Sheet.addCommand('*', 'addcol-regex-subst', 'addColumnAtCursor(Column(cursorCol.name + "_re", getter=regexTransform(cursorCol, **inputRegexSubst("regex transform column"))))', 'add column derived from current column, replacing regex with subst (may include \1 backrefs)')
133
- Sheet.addCommand('g*', 'setcol-regex-subst', 'setValuesFromRegex([cursorCol], someSelectedRows, **inputRegexSubst("regex transform column"))', 'regex/subst - modify selected rows in current column, replacing regex with subst, (may include backreferences \\1 etc)')
134
- Sheet.addCommand('gz*', 'setcol-regex-subst-all', 'setValuesFromRegex(visibleCols, someSelectedRows, **inputRegexSubst(f"regex transform {nVisibleCols} columns"))', 'modify selected rows in all visible columns, replacing regex with subst (may include \\1 backrefs)')
132
+ Sheet.addCommand('*', 'addcol-regex-subst', 'addColumnAtCursor(Column(cursorCol.name + "_re", getter=regexTransform(cursorCol, **inputRegexSubst("regex transform column"))))', 'add column derived from current column, replacing `search` regex with `replace` (may include \\1 backrefs)')
133
+ Sheet.addCommand('g*', 'setcol-regex-subst', 'setValuesFromRegex([cursorCol], someSelectedRows, **inputRegexSubst("regex transform column"))', 'modify selected rows in current column, replacing `search` regex with `replace`, (may include backreferences \\1 etc)')
134
+ Sheet.addCommand('gz*', 'setcol-regex-subst-all', 'setValuesFromRegex(visibleCols, someSelectedRows, **inputRegexSubst(f"regex transform {nVisibleCols} columns"))', 'modify selected rows in all visible columns, replacing `search` regex with `replace` (may include \\1 backrefs)')
135
135
 
136
136
 
137
137
  vd.addMenuItems('''
@@ -5,7 +5,9 @@ from visidata import VisiData
5
5
 
6
6
  @VisiData.api
7
7
  def getStatusSource(vd):
8
- stack = inspect.stack()
8
+ if not vd.options.debug:
9
+ return ''
10
+ stack = inspect.stack(context=0) #2370
9
11
  for i, sf in enumerate(stack):
10
12
  if sf.function in 'status aside'.split():
11
13
  if stack[i+1].function in 'error fail warning debug'.split():
@@ -30,7 +30,7 @@ def syseditCells_async(sheet, cols, rows, filetype=None):
30
30
  vd.sync(tempvs.ensureLoaded())
31
31
 
32
32
  while isinstance(tempvs, IndexSheet):
33
- tempvs.ensureLoaded()
33
+ vd.sync(tempvs.ensureLoaded())
34
34
  tempvs = tempvs.rows[0]
35
35
 
36
36
  for col in sheet.visibleCols:
@@ -16,7 +16,8 @@ class TransposeSheet(Sheet):
16
16
  # rows become columns
17
17
  for row in Progress(self.source.rows, 'transposing'):
18
18
  self.addColumn(Column('_'.join(map(str, self.source.rowkey(row))),
19
- getter=lambda c,origcol,row=row: origcol.getValue(row)))
19
+ getter=lambda c,origcol,row=row: origcol.getValue(row),
20
+ setter=lambda c,origcol,v,row=row: origcol.setValue(row, v)))
20
21
 
21
22
  # columns become rows
22
23
  self.rows = list(self.source.nonKeyVisibleCols)
@@ -7,8 +7,8 @@ from visidata import vd
7
7
  from visidata.sheets import Column, TableSheet
8
8
 
9
9
 
10
- vd.addType(ip_address, icon=":", formatter=lambda fmt, ip: str(ip))
11
- vd.addType(ip_network, icon="/", formatter=lambda fmt, ip: str(ip))
10
+ vd.addType(ip_address, icon=":", formatter=lambda fmt, ip: str(ip), name='ip_address')
11
+ vd.addType(ip_network, icon="/", formatter=lambda fmt, ip: str(ip), name='ip_network')
12
12
 
13
13
 
14
14
  def isSupernet(cell, network, isNull):
@@ -69,5 +69,3 @@ TableSheet.addCommand(
69
69
  'cursorCol.selectSupernets(input("ip or cidr block: "))',
70
70
  "select rows where the CIDR block value includes the input address space",
71
71
  )
72
-
73
- vd.addGlobals(ip_address=ip_address, ip_network=ip_network)
@@ -25,6 +25,7 @@ class UnfurledSheet(Sheet):
25
25
  self.cursorVisibleColIndex = len(self.columns)-1
26
26
  self.addColumn(ColumnItem(col.name + "_key", 1))
27
27
  self.addColumn(ColumnItem(col.name + "_value", 2))
28
+ self.cursorVisibleColIndex = len(self.columns)-1
28
29
  else:
29
30
  self.addColumn(SubColumnFunc(col.name, col, 0, keycol=col.keycol))
30
31
 
visidata/form.py CHANGED
@@ -13,8 +13,8 @@ class FormSheet(VisiDataMetaSheet):
13
13
  rowtype='labels' # rowdef: { .x .y .text .color .command .input .key}
14
14
 
15
15
  @VisiData.api
16
- def replayCommand(vd, longname, sheet=None, col='', row=''):
17
- vd.replayOne(vd.cmdlog.newRow(sheet=self.name, col=col, row=row, longname=r.command, input=r.input))
16
+ def replayCommand(vd, longname, input=None, sheet=None, col='', row=''):
17
+ vd.replayOne(vd.cmdlog.newRow(sheet=sheet, col=col, row=row, longname=longname, input=input))
18
18
 
19
19
 
20
20
  class FormCanvas(BaseSheet):
visidata/freqtbl.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from copy import copy
2
2
  import itertools
3
3
 
4
- from visidata import vd, asyncthread, vlen, VisiData, Column, AttrColumn, Sheet, ColumnsSheet, ENTER
4
+ from visidata import vd, asyncthread, vlen, VisiData, Column, AttrColumn, Sheet, ColumnsSheet, ENTER, Fanout
5
5
  from visidata.pivot import PivotSheet, PivotGroupRow
6
6
 
7
7
 
@@ -19,13 +19,19 @@ def valueNames(vd, discrete_vals, numeric_vals):
19
19
  return '+'.join(ret)
20
20
 
21
21
  class HistogramColumn(Column):
22
+ '.sourceCol is the column to be histogrammed'
22
23
  def calcValue(col, row):
23
24
  histogram = col.sheet.options.disp_histogram
24
25
  histolen = col.width-2
25
- return histogram*(histolen*len(row.sourcerows)//col.sheet.largest)
26
+ return histogram*(histolen*col.sourceCol.getTypedValue(row)//col.largest)
27
+
28
+ def updateLargest(col, row):
29
+ col.largest = max(col.largest, col.sourceCol.getTypedValue(row))
26
30
 
27
31
 
28
32
  def makeFreqTable(sheet, *groupByCols):
33
+ if not any(groupByCols):
34
+ vd.fail('FreqTableSheet requires at least 1 column for grouping')
29
35
  return FreqTableSheet(sheet.name,
30
36
  '%s_freq' % '-'.join(col.name for col in groupByCols),
31
37
  groupByCols=groupByCols,
@@ -61,32 +67,31 @@ Each row on this sheet corresponds to a *bin* of rows on the source sheet that h
61
67
  self.source.unselect(row.sourcerows)
62
68
  return super().unselectRow(row)
63
69
 
64
- def updateLargest(self, grouprow):
65
- self.largest = max(self.largest, len(grouprow.sourcerows))
66
-
67
70
  def resetCols(self):
68
71
  super().resetCols()
69
72
 
70
73
  # add default bonus columns
74
+ countCol = AttrColumn('count', 'sourcerows', type=vlen)
71
75
  for c in [
72
- AttrColumn('count', 'sourcerows', type=vlen),
76
+ countCol,
73
77
  Column('percent', type=float, getter=lambda col,row: len(row.sourcerows)*100/col.sheet.source.nRows),
74
78
  ]:
75
79
  self.addColumn(c)
76
80
 
77
81
  if self.options.disp_histogram:
78
- c = HistogramColumn('histogram', type=str, width=self.options.default_width*2)
82
+ c = HistogramColumn('histogram', type=str, width=self.options.default_width*2, sourceCol=countCol)
79
83
  self.addColumn(c)
80
84
 
81
85
  # if non-numeric grouping, reverse sort by count at end of load
82
86
  if not any(vd.isNumeric(c) for c in self.groupByCols):
83
- self._ordering = [('count', True)]
87
+ self._ordering = [(countCol, True)]
84
88
 
85
89
  def loader(self):
86
90
  'Generate frequency table.'
87
91
  # two more threads
92
+ histcols = [col for col in self.visibleCols if isinstance(col, HistogramColumn)]
88
93
  vd.sync(self.addAggregateCols(),
89
- self.groupRows(self.updateLargest))
94
+ self.groupRows(lambda row, cols=Fanout(histcols): cols.updateLargest(row)))
90
95
 
91
96
  def afterLoad(self):
92
97
  super().afterLoad()
@@ -138,7 +143,7 @@ class FreqTablePreviewSheet(Sheet):
138
143
  FreqTableSheet.addCommand('', 'open-preview', 'vd.push(FreqTablePreviewSheet(sheet.name, "preview", source=sheet, columns=source.columns), pane=2); vd.options.disp_splitwin_pct=50', 'open split preview of source rows at cursor')
139
144
 
140
145
  Sheet.addCommand('F', 'freq-col', 'vd.push(makeFreqTable(sheet, cursorCol))', 'open Frequency Table grouped on current column, with aggregations of other columns')
141
- Sheet.addCommand('gF', 'freq-keys', 'vd.push(makeFreqTable(sheet, *keyCols))', 'open Frequency Table grouped by all key columns on source sheet, with aggregations of other columns')
146
+ Sheet.addCommand('gF', 'freq-keys', 'vd.push(makeFreqTable(sheet, *keyCols)) if keyCols else vd.fail("there are no key columns to group by")', 'open Frequency Table grouped by all key columns on source sheet, with aggregations of other columns')
142
147
  Sheet.addCommand('zF', 'freq-summary', 'vd.push(makeFreqTableSheetSummary(sheet, Column("Total", sheet=sheet, getter=lambda col, row: "Total")))', 'open one-line summary for all rows and selected rows')
143
148
 
144
149
  ColumnsSheet.addCommand(ENTER, 'freq-row', 'vd.push(makeFreqTable(source[0], cursorRow))', 'open a Frequency Table sheet grouped on column referenced in current row')
@@ -148,7 +153,7 @@ FreqTableSheet.addCommand('gu', 'unselect-rows', 'unselect(selectedRows)', 'unse
148
153
  FreqTableSheet.addCommand('g'+ENTER, 'dive-selected', 'vd.push(openRows(selectedRows))', 'open copy of source sheet with rows that are grouped in selected rows')
149
154
  FreqTableSheet.addCommand('', 'select-first', 'for r in rows: source.select([r.sourcerows[0]])', 'select first source row in each bin')
150
155
 
151
- FreqTableSheet.init('largest', lambda: 1)
156
+ HistogramColumn.init('largest', lambda: 1)
152
157
 
153
158
  vd.addGlobals(
154
159
  makeFreqTable=makeFreqTable,
visidata/fuzzymatch.py CHANGED
@@ -374,6 +374,7 @@ def fuzzymatch(vd, haystack:"list[dict[str, str]]", needles:"list[str]) -> list[
374
374
  match = {}
375
375
  formatted_hay = {}
376
376
  for k, v in h.items():
377
+ if k[0] == '_': continue
377
378
  for p in needles:
378
379
  mr = _fuzzymatch(v, p)
379
380
  if mr.score > 0: