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.
- visidata/__init__.py +12 -10
- visidata/_input.py +208 -202
- visidata/_open.py +4 -1
- visidata/_types.py +4 -3
- visidata/aggregators.py +88 -39
- visidata/apps/vdsql/_ibis.py +7 -11
- visidata/apps/vdsql/clickhouse.py +2 -2
- visidata/apps/vdsql/snowflake.py +1 -1
- visidata/apps/vgit/status.py +1 -1
- visidata/basesheet.py +11 -4
- visidata/canvas.py +54 -20
- visidata/clipboard.py +13 -6
- visidata/cliptext.py +7 -6
- visidata/cmdlog.py +40 -27
- visidata/column.py +14 -49
- visidata/ddw/regex.ddw +3 -2
- visidata/deprecated.py +14 -2
- visidata/desktop/visidata.desktop +2 -2
- visidata/editor.py +1 -0
- visidata/errors.py +1 -1
- visidata/experimental/sort_selected.py +54 -0
- visidata/expr.py +69 -18
- visidata/features/change_precision.py +1 -3
- visidata/features/cmdpalette.py +17 -2
- visidata/features/colorsheet.py +1 -1
- visidata/features/dedupe.py +3 -3
- visidata/features/go_col.py +71 -0
- visidata/features/graph_seaborn.py +1 -1
- visidata/features/join.py +20 -10
- visidata/features/layout.py +16 -3
- visidata/features/ping.py +16 -12
- visidata/features/regex.py +5 -5
- visidata/features/status_source.py +3 -1
- visidata/features/sysedit.py +1 -1
- visidata/features/transpose.py +2 -1
- visidata/features/type_ipaddr.py +2 -4
- visidata/features/unfurl.py +1 -0
- visidata/form.py +2 -2
- visidata/freqtbl.py +16 -11
- visidata/fuzzymatch.py +1 -0
- visidata/graph.py +163 -12
- visidata/guide.py +57 -24
- visidata/guides/ClipboardGuide.md +48 -0
- visidata/guides/ColumnsGuide.md +52 -0
- visidata/guides/CommandsSheet.md +28 -0
- visidata/guides/DirSheet.md +34 -0
- visidata/{features/errors_guide.py → guides/ErrorsSheet.md} +2 -11
- visidata/guides/FrequencyTable.md +42 -0
- visidata/guides/GrepSheet.md +28 -0
- visidata/guides/JsonSheet.md +38 -0
- visidata/guides/MacrosSheet.md +19 -0
- visidata/guides/MeltGuide.md +52 -0
- visidata/guides/MemorySheet.md +7 -0
- visidata/guides/MenuGuide.md +26 -0
- visidata/guides/ModifyGuide.md +38 -0
- visidata/guides/PivotGuide.md +71 -0
- visidata/guides/RegexGuide.md +107 -0
- visidata/guides/SelectionGuide.md +44 -0
- visidata/guides/SlideGuide.md +26 -0
- visidata/guides/SplitpaneGuide.md +15 -0
- visidata/guides/TypesSheet.md +43 -0
- visidata/guides/XsvGuide.md +36 -0
- visidata/help.py +6 -6
- visidata/hint.py +2 -1
- visidata/indexsheet.py +2 -2
- visidata/interface.py +13 -14
- visidata/keys.py +4 -1
- visidata/loaders/api_airtable.py +1 -1
- visidata/loaders/archive.py +1 -1
- visidata/loaders/csv.py +9 -5
- visidata/loaders/eml.py +11 -6
- visidata/loaders/f5log.py +1 -0
- visidata/loaders/fec.py +18 -42
- visidata/loaders/fixed_width.py +19 -3
- visidata/loaders/grep.py +121 -0
- visidata/loaders/html.py +1 -0
- visidata/loaders/http.py +6 -1
- visidata/loaders/json.py +22 -4
- visidata/loaders/jsonla.py +8 -2
- visidata/loaders/mailbox.py +1 -0
- visidata/loaders/markdown.py +25 -6
- visidata/loaders/msgpack.py +19 -0
- visidata/loaders/npy.py +0 -1
- visidata/loaders/odf.py +18 -4
- visidata/loaders/orgmode.py +1 -1
- visidata/loaders/rec.py +6 -4
- visidata/loaders/sas.py +11 -4
- visidata/loaders/scrape.py +0 -1
- visidata/loaders/texttables.py +2 -0
- visidata/loaders/tsv.py +24 -7
- visidata/loaders/unzip_http.py +127 -3
- visidata/loaders/vds.py +4 -0
- visidata/loaders/vdx.py +1 -1
- visidata/loaders/xlsx.py +5 -0
- visidata/loaders/xml.py +2 -1
- visidata/macros.py +14 -31
- visidata/main.py +14 -13
- visidata/mainloop.py +14 -6
- visidata/man/vd.1 +72 -39
- visidata/man/vd.txt +72 -41
- visidata/memory.py +15 -4
- visidata/menu.py +14 -3
- visidata/metasheets.py +5 -6
- visidata/modify.py +4 -4
- visidata/mouse.py +2 -0
- visidata/movement.py +14 -28
- visidata/optionssheet.py +3 -5
- visidata/path.py +59 -37
- visidata/pivot.py +8 -5
- visidata/pyobj.py +63 -9
- visidata/save.py +16 -9
- visidata/search.py +4 -4
- visidata/selection.py +10 -56
- visidata/settings.py +37 -35
- visidata/sheets.py +186 -108
- visidata/shell.py +22 -12
- visidata/sidebar.py +71 -16
- visidata/sort.py +21 -6
- visidata/statusbar.py +42 -5
- visidata/stored_list.py +5 -2
- visidata/tests/conftest.py +1 -0
- visidata/tests/test_commands.py +9 -1
- visidata/tests/test_completer.py +18 -0
- visidata/tests/test_edittext.py +3 -2
- visidata/text_source.py +7 -4
- visidata/textsheet.py +20 -6
- visidata/themes/ascii8.py +9 -6
- visidata/themes/asciimono.py +14 -4
- visidata/threads.py +13 -3
- visidata/tuiwin.py +5 -1
- visidata/type_currency.py +1 -2
- visidata/type_date.py +6 -1
- visidata/undo.py +10 -5
- visidata/utils.py +9 -3
- visidata/vdobj.py +21 -1
- visidata/wrappers.py +9 -1
- {visidata-3.0.2.data → visidata-3.1.1.data}/data/share/applications/visidata.desktop +2 -2
- {visidata-3.0.2.data → visidata-3.1.1.data}/data/share/man/man1/vd.1 +72 -39
- {visidata-3.0.2.data → visidata-3.1.1.data}/data/share/man/man1/visidata.1 +72 -39
- {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/METADATA +24 -6
- visidata-3.1.1.dist-info/RECORD +280 -0
- visidata/loaders/api_bitio.py +0 -102
- visidata/stored_prop.py +0 -38
- visidata-3.0.2.dist-info/RECORD +0 -258
- {visidata-3.0.2.data → visidata-3.1.1.data}/scripts/vd +0 -0
- {visidata-3.0.2.data → visidata-3.1.1.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/WHEEL +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/entry_points.txt +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.1.dist-info}/top_level.txt +0 -0
visidata/features/cmdpalette.py
CHANGED
@@ -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'[:
|
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')
|
visidata/features/colorsheet.py
CHANGED
visidata/features/dedupe.py
CHANGED
@@ -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,
|
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
|
-
|
107
|
-
|
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
|
+
''')
|
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
|
-
|
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': '
|
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
|
-
|
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
|
-
|
188
|
-
|
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
|
-
|
270
|
-
|
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)
|
visidata/features/layout.py
CHANGED
@@ -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 >
|
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
|
-
|
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
|
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
|
-
|
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(
|
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
|
visidata/features/regex.py
CHANGED
@@ -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")))', '
|
130
|
-
Sheet.addCommand(';', 'addcol-capture', 'addColumnAtCursor(RegexColumn(makeRegexMatcher, cursorCol, inputRegex("capture regex: ", type="regex-capture")))', '
|
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
|
133
|
-
Sheet.addCommand('g*', 'setcol-regex-subst', 'setValuesFromRegex([cursorCol], someSelectedRows, **inputRegexSubst("regex transform column"))', '
|
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
|
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
|
-
|
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():
|
visidata/features/sysedit.py
CHANGED
visidata/features/transpose.py
CHANGED
@@ -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)
|
visidata/features/type_ipaddr.py
CHANGED
@@ -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)
|
visidata/features/unfurl.py
CHANGED
@@ -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=
|
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*
|
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
|
-
|
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 = [(
|
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(
|
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
|
-
|
156
|
+
HistogramColumn.init('largest', lambda: 1)
|
152
157
|
|
153
158
|
vd.addGlobals(
|
154
159
|
makeFreqTable=makeFreqTable,
|
visidata/fuzzymatch.py
CHANGED