visidata 3.0.1__py3-none-any.whl → 3.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 -199
- visidata/_open.py +4 -1
- visidata/_types.py +4 -3
- visidata/aggregators.py +88 -39
- visidata/apps/vdsql/_ibis.py +9 -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 +66 -24
- 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 +23 -4
- 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 +18 -5
- visidata/features/ping.py +16 -12
- visidata/features/regex.py +5 -5
- visidata/features/slide.py +15 -17
- 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 +173 -12
- visidata/guide.py +61 -25
- 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/guides/ErrorsSheet.md +17 -0
- 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/SortGuide.md +0 -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 +20 -15
- visidata/mainloop.py +17 -6
- visidata/man/vd.1 +74 -39
- visidata/man/vd.txt +73 -41
- visidata/memory.py +16 -5
- 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/rename_col.py +18 -1
- visidata/save.py +16 -9
- visidata/search.py +4 -4
- visidata/selection.py +10 -56
- visidata/settings.py +37 -35
- visidata/sheets.py +189 -118
- visidata/shell.py +23 -14
- 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 -13
- visidata/utils.py +9 -3
- visidata/vdobj.py +21 -1
- visidata/wrappers.py +9 -1
- {visidata-3.0.1.data → visidata-3.1.data}/data/share/applications/visidata.desktop +2 -2
- {visidata-3.0.1.data → visidata-3.1.data}/data/share/man/man1/vd.1 +74 -39
- {visidata-3.0.1.data → visidata-3.1.data}/data/share/man/man1/visidata.1 +74 -39
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/METADATA +33 -5
- visidata-3.1.dist-info/RECORD +284 -0
- visidata-3.0.1.dist-info/RECORD +0 -258
- {visidata-3.0.1.data → visidata-3.1.data}/scripts/vd +0 -0
- {visidata-3.0.1.data → visidata-3.1.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/WHEEL +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/entry_points.txt +0 -0
- {visidata-3.0.1.dist-info → visidata-3.1.dist-info}/top_level.txt +0 -0
@@ -32,9 +32,7 @@ def setcol_precision(col, amount:int):
|
|
32
32
|
if not precision_str is None:
|
33
33
|
col.fmtstr = f'%.{max(0, int(precision_str[1]) + amount)}f'
|
34
34
|
else:
|
35
|
-
|
36
|
-
if col.fmtstr == '':
|
37
|
-
col.fmtstr = '%.2f'
|
35
|
+
vd.fail('column type must be numeric or date')
|
38
36
|
|
39
37
|
|
40
38
|
vd.addMenuItems('''
|
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):
|
@@ -109,22 +115,28 @@ def inputPalette(sheet, prompt, items,
|
|
109
115
|
for i in range(nitems-len(palrows)):
|
110
116
|
palrows.append((None, None))
|
111
117
|
|
118
|
+
used_triggers = set()
|
112
119
|
for i, (m, item) in enumerate(palrows):
|
113
120
|
trigger_key = ''
|
114
121
|
if tabitem >= 0 and item:
|
115
|
-
|
116
|
-
|
122
|
+
tkey = f'{i+1}'[-1]
|
123
|
+
if tkey not in used_triggers:
|
124
|
+
trigger_key = tkey
|
125
|
+
bindings[trigger_key] = partial(add_to_input if multiple else accept_input, value=item[value_key])
|
126
|
+
used_triggers.add(trigger_key)
|
117
127
|
|
118
128
|
attr = colors.color_cmdpalette
|
119
129
|
|
120
130
|
if tabitem < 0 and palrows:
|
121
131
|
_ , topitem = palrows[0]
|
132
|
+
if not topitem: return
|
122
133
|
if multiple:
|
123
134
|
bindings[' '] = partial(add_to_input, value=topitem[value_key])
|
124
135
|
bindings['^J'] = partial(accept_input_if_subset, value=topitem[value_key])
|
125
136
|
else:
|
126
137
|
bindings['^J'] = partial(accept_input, value=topitem[value_key])
|
127
138
|
elif item and i == tabitem:
|
139
|
+
if not item: return
|
128
140
|
if multiple:
|
129
141
|
bindings['^J'] = partial(accept_input_if_subset, value=item[value_key])
|
130
142
|
bindings[' '] = partial(add_to_input, value=item[value_key])
|
@@ -160,12 +172,12 @@ def inputLongname(sheet):
|
|
160
172
|
prompt = 'command name: '
|
161
173
|
# get set of commands possible in the sheet
|
162
174
|
this_sheets_help = HelpSheet('', source=sheet)
|
163
|
-
this_sheets_help.ensureLoaded()
|
175
|
+
vd.sync(this_sheets_help.ensureLoaded())
|
164
176
|
|
165
177
|
def _fmt_cmdpal_summary(match, row, trigger_key):
|
166
178
|
keystrokes = this_sheets_help.revbinds.get(row.longname, [None])[0] or ' '
|
167
179
|
formatted_longname = match.formatted.get('longname', row.longname) if match else row.longname
|
168
|
-
formatted_name = f'[:
|
180
|
+
formatted_name = f'[:bold][:onclick {row.longname}]{formatted_longname}[/][/]'
|
169
181
|
if vd.options.debug and match:
|
170
182
|
keystrokes = f'[{match.score}]'
|
171
183
|
r = f' [:keystrokes]{keystrokes.rjust(len(prompt)-5)}[/] '
|
@@ -186,6 +198,12 @@ def inputLongname(sheet):
|
|
186
198
|
help=vd.help_longname,
|
187
199
|
type='longname')
|
188
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
|
+
|
189
207
|
|
190
208
|
@BaseSheet.api
|
191
209
|
def exec_longname(sheet, longname):
|
@@ -195,3 +213,4 @@ def exec_longname(sheet, longname):
|
|
195
213
|
|
196
214
|
|
197
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,22 +35,35 @@ 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')
|
41
52
|
Sheet.addCommand('gz_', 'resize-cols-input', 'width = int(input("set width= ", value=cursorCol.width)); Fanout(visibleCols).setWidth(width)', 'adjust widths of all visible columns to N')
|
42
53
|
|
43
|
-
Sheet.addCommand('-', 'hide-col', 'hide_col(cursorCol)', '
|
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
|
-
Sheet.addCommand('gv', 'unhide-cols', 'unhide_cols(columns, visibleRows)', '
|
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')
|
48
60
|
Sheet.addCommand('zv', 'resize-height-input', 'Fanout(visibleCols).height=int(input("set height for all columns to: ", value=max(c.height for c in sheet.visibleCols)))', 'resize row height to N')
|
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('''
|
visidata/features/slide.py
CHANGED
@@ -58,30 +58,28 @@ def moveVisibleCol(sheet, fromVisColIdx, toVisColIdx):
|
|
58
58
|
return fromVisColIdx
|
59
59
|
|
60
60
|
|
61
|
-
Sheet.addCommand('H', 'slide-left', 'sheet.cursorVisibleColIndex = slide_col(cursorVisibleColIndex, cursorVisibleColIndex-1) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, cursorCol.keycol-1)', 'slide current column left')
|
62
|
-
Sheet.addCommand('L', 'slide-right', 'sheet.cursorVisibleColIndex = slide_col(cursorVisibleColIndex, cursorVisibleColIndex+1) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, cursorCol.keycol+1)', 'slide current column right')
|
61
|
+
Sheet.addCommand('H', 'slide-left', 'sheet.cursorVisibleColIndex = slide_col(cursorVisibleColIndex, cursorVisibleColIndex-1) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, cursorCol.keycol-1)', 'slide the current column left **one position**')
|
62
|
+
Sheet.addCommand('L', 'slide-right', 'sheet.cursorVisibleColIndex = slide_col(cursorVisibleColIndex, cursorVisibleColIndex+1) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, cursorCol.keycol+1)', 'slide the current column right **one position**')
|
63
63
|
Sheet.addCommand('J', 'slide-down', 'sheet.cursorRowIndex = slide_row(cursorRowIndex, cursorRowIndex+1)', 'slide current row down')
|
64
64
|
Sheet.addCommand('K', 'slide-up', 'sheet.cursorRowIndex = slide_row(cursorRowIndex, cursorRowIndex-1)', 'slide current row up')
|
65
|
-
Sheet.addCommand('gH', 'slide-leftmost', 'slide_col(cursorVisibleColIndex, len(keyCols) + 0) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, 1)', 'slide current column all the way to the left of
|
66
|
-
Sheet.addCommand('gL', 'slide-rightmost', 'slide_col(cursorVisibleColIndex, nVisibleCols-1) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, len(keyCols))', 'slide current column all the way to the right of
|
65
|
+
Sheet.addCommand('gH', 'slide-leftmost', 'slide_col(cursorVisibleColIndex, len(keyCols) + 0) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, 1)', 'slide the current column **all the way** to the left of its section')
|
66
|
+
Sheet.addCommand('gL', 'slide-rightmost', 'slide_col(cursorVisibleColIndex, nVisibleCols-1) if not cursorCol.keycol else slide_keycol(cursorCol.keycol, len(keyCols))', 'slide the current column **all the way** to the right of its section')
|
67
67
|
Sheet.addCommand('gJ', 'slide-bottom', 'slide_row(cursorRowIndex, nRows)', 'slide current row all the way to the bottom of sheet')
|
68
68
|
Sheet.addCommand('gK', 'slide-top', 'slide_row(cursorRowIndex, 0)', 'slide current row to top of sheet')
|
69
|
-
Sheet.addCommand('zH', 'slide-left-n', 'slide_col(cursorVisibleColIndex, cursorVisibleColIndex-int(input("slide col left n=", value=1)))', 'slide current column N positions to the left')
|
70
|
-
Sheet.addCommand('zL', 'slide-right-n', 'slide_col(cursorVisibleColIndex, cursorVisibleColIndex+int(input("slide col left n=", value=1)))', 'slide current column N positions to the right')
|
69
|
+
Sheet.addCommand('zH', 'slide-left-n', 'slide_col(cursorVisibleColIndex, cursorVisibleColIndex-int(input("slide col left n=", value=1)))', 'slide current column **N positions** to the left')
|
70
|
+
Sheet.addCommand('zL', 'slide-right-n', 'slide_col(cursorVisibleColIndex, cursorVisibleColIndex+int(input("slide col left n=", value=1)))', 'slide current column **N positions** to the right')
|
71
71
|
Sheet.addCommand('zJ', 'slide-down-n', 'slide_row(cursorRowIndex, cursorRowIndex+int(input("slide row down n=", value=1)))', 'slide current row N positions down')
|
72
72
|
Sheet.addCommand('zK', 'slide-up-n', 'slide_row(cursorRowIndex, cursorRowIndex-int(input("slide row up n=", value=1)))', 'slide current row N positions up')
|
73
73
|
|
74
|
-
Sheet.bindkey('
|
75
|
-
Sheet.bindkey('
|
76
|
-
Sheet.bindkey('
|
77
|
-
Sheet.bindkey('
|
78
|
-
|
79
|
-
Sheet.bindkey('
|
80
|
-
|
81
|
-
Sheet.bindkey('
|
82
|
-
Sheet.bindkey('
|
83
|
-
Sheet.bindkey('gkUP', 'slide-top')
|
84
|
-
Sheet.bindkey('gKEY_SRIGHT', 'slide-rightmost')
|
74
|
+
Sheet.bindkey('Shift+Left', 'slide-left')
|
75
|
+
Sheet.bindkey('Shift+Up', 'slide-up')
|
76
|
+
Sheet.bindkey('Shift+Down', 'slide-down')
|
77
|
+
Sheet.bindkey('Shift+Right', 'slide-right')
|
78
|
+
|
79
|
+
Sheet.bindkey('g Shift+Left', 'slide-leftmost')
|
80
|
+
Sheet.bindkey('g Shift+Down', 'slide-bottom')
|
81
|
+
Sheet.bindkey('g Shift+Up', 'slide-top')
|
82
|
+
Sheet.bindkey('g Shift+Right', 'slide-rightmost')
|
85
83
|
|
86
84
|
vd.addMenuItems('''
|
87
85
|
Edit > Slide > Row > up > slide-up
|
@@ -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