visidata 2.11.1__py3-none-any.whl → 3.0.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 +72 -91
- visidata/_input.py +259 -42
- visidata/_open.py +84 -29
- visidata/_types.py +21 -3
- visidata/_urlcache.py +17 -4
- visidata/aggregators.py +78 -25
- visidata/apps/__init__.py +0 -0
- visidata/apps/vdsql/__about__.py +8 -0
- visidata/apps/vdsql/__init__.py +5 -0
- visidata/apps/vdsql/__main__.py +27 -0
- visidata/apps/vdsql/_ibis.py +748 -0
- visidata/apps/vdsql/bigquery.py +61 -0
- visidata/apps/vdsql/clickhouse.py +53 -0
- visidata/apps/vdsql/setup.py +40 -0
- visidata/apps/vdsql/snowflake.py +67 -0
- visidata/apps/vgit/__init__.py +13 -0
- {vgit → visidata/apps/vgit}/blame.py +5 -2
- {vgit → visidata/apps/vgit}/branch.py +31 -16
- {vgit → visidata/apps/vgit}/config.py +3 -3
- visidata/apps/vgit/diff.py +169 -0
- visidata/apps/vgit/gitsheet.py +161 -0
- {vgit → visidata/apps/vgit}/grep.py +6 -5
- visidata/apps/vgit/log.py +81 -0
- {vgit → visidata/apps/vgit}/main.py +18 -5
- {vgit → visidata/apps/vgit}/remote.py +8 -4
- visidata/apps/vgit/repos.py +71 -0
- {vgit → visidata/apps/vgit}/setup.py +6 -4
- visidata/apps/vgit/stash.py +69 -0
- visidata/apps/vgit/status.py +204 -0
- {vgit → visidata/apps/vgit}/statusbar.py +2 -0
- visidata/basesheet.py +63 -51
- visidata/canvas.py +208 -93
- visidata/choose.py +6 -6
- visidata/clean_names.py +29 -0
- visidata/clipboard.py +73 -17
- visidata/cliptext.py +220 -46
- visidata/cmdlog.py +88 -114
- visidata/color.py +142 -56
- visidata/column.py +121 -129
- visidata/ddw/input.ddw +74 -79
- visidata/ddw/regex.ddw +57 -0
- visidata/ddwplay.py +33 -14
- visidata/deprecated.py +77 -3
- visidata/desktop/visidata.desktop +7 -0
- visidata/editor.py +12 -6
- visidata/errors.py +6 -2
- visidata/experimental/__init__.py +0 -0
- visidata/experimental/diff_sheet.py +29 -0
- visidata/experimental/digit_autoedit.py +6 -0
- visidata/experimental/gdrive.py +89 -0
- visidata/experimental/google.py +37 -0
- visidata/experimental/gsheets.py +79 -0
- visidata/experimental/live_search.py +37 -0
- visidata/experimental/liveupdate.py +45 -0
- visidata/experimental/mark.py +133 -0
- visidata/experimental/noahs_tapestry/__init__.py +1 -0
- visidata/experimental/noahs_tapestry/tapestry.py +147 -0
- visidata/experimental/rownum.py +73 -0
- visidata/experimental/slide_cells.py +26 -0
- visidata/expr.py +8 -4
- visidata/extensible.py +22 -4
- visidata/features/__init__.py +0 -0
- visidata/features/addcol_audiometadata.py +42 -0
- visidata/features/addcol_histogram.py +34 -0
- visidata/features/canvas_save_svg.py +69 -0
- visidata/features/change_precision.py +46 -0
- visidata/features/cmdpalette.py +197 -0
- visidata/features/colorbrewer.py +363 -0
- visidata/{colorsheet.py → features/colorsheet.py} +17 -16
- visidata/features/command_server.py +105 -0
- visidata/features/currency_to_usd.py +70 -0
- visidata/{customdate.py → features/customdate.py} +2 -0
- visidata/features/dedupe.py +132 -0
- visidata/{describe.py → features/describe.py} +17 -15
- visidata/features/errors_guide.py +26 -0
- visidata/features/expand_cols.py +202 -0
- visidata/{fill.py → features/fill.py} +3 -1
- visidata/{freeze.py → features/freeze.py} +11 -6
- visidata/features/graph_seaborn.py +79 -0
- visidata/features/helloworld.py +10 -0
- visidata/features/hint_types.py +17 -0
- visidata/{incr.py → features/incr.py} +5 -0
- visidata/{join.py → features/join.py} +107 -53
- visidata/features/known_cols.py +21 -0
- visidata/features/layout.py +62 -0
- visidata/{melt.py → features/melt.py} +32 -21
- visidata/features/normcol.py +118 -0
- visidata/features/open_config.py +7 -0
- visidata/features/open_syspaste.py +18 -0
- visidata/features/ping.py +157 -0
- visidata/features/procmgr.py +208 -0
- visidata/features/random_sample.py +6 -0
- visidata/{regex.py → features/regex.py} +47 -31
- visidata/features/reload_every.py +55 -0
- visidata/features/rename_col_cascade.py +30 -0
- visidata/features/scroll_context.py +60 -0
- visidata/features/select_equal_selected.py +11 -0
- visidata/features/setcol_fake.py +65 -0
- visidata/{slide.py → features/slide.py} +77 -21
- visidata/features/sparkline.py +48 -0
- visidata/features/status_source.py +20 -0
- visidata/{sysedit.py → features/sysedit.py} +2 -1
- visidata/features/sysopen_mailcap.py +46 -0
- visidata/features/term_extras.py +13 -0
- visidata/{transpose.py → features/transpose.py} +5 -4
- visidata/features/type_ipaddr.py +73 -0
- visidata/features/type_url.py +11 -0
- visidata/{unfurl.py → features/unfurl.py} +9 -9
- visidata/{window.py → features/window.py} +2 -2
- visidata/form.py +50 -21
- visidata/freqtbl.py +81 -33
- visidata/fuzzymatch.py +414 -0
- visidata/graph.py +105 -33
- visidata/guide.py +200 -0
- visidata/help.py +75 -44
- visidata/hint.py +39 -0
- visidata/indexsheet.py +109 -0
- visidata/input_history.py +55 -0
- visidata/interface.py +58 -0
- visidata/keys.py +20 -16
- visidata/loaders/__init__.py +9 -0
- visidata/loaders/_pandas.py +61 -21
- visidata/loaders/api_airtable.py +70 -0
- visidata/loaders/api_bitio.py +102 -0
- visidata/loaders/api_matrix.py +148 -0
- visidata/loaders/api_reddit.py +306 -0
- visidata/loaders/api_zulip.py +249 -0
- visidata/loaders/archive.py +41 -7
- visidata/loaders/arrow.py +7 -7
- visidata/loaders/conll.py +49 -0
- visidata/loaders/csv.py +25 -7
- visidata/loaders/eml.py +3 -4
- visidata/loaders/f5log.py +1204 -0
- visidata/loaders/fec.py +325 -0
- visidata/loaders/fixed_width.py +2 -4
- visidata/loaders/frictionless.py +3 -3
- visidata/loaders/geojson.py +8 -5
- visidata/loaders/google.py +48 -0
- visidata/loaders/graphviz.py +4 -4
- visidata/loaders/hdf5.py +4 -4
- visidata/loaders/html.py +54 -12
- visidata/loaders/http.py +84 -30
- visidata/loaders/imap.py +20 -10
- visidata/loaders/jrnl.py +52 -0
- visidata/loaders/json.py +83 -29
- visidata/loaders/jsonla.py +74 -0
- visidata/loaders/lsv.py +15 -11
- visidata/loaders/mailbox.py +40 -0
- visidata/loaders/markdown.py +1 -3
- visidata/loaders/mbtiles.py +4 -5
- visidata/loaders/mysql.py +11 -13
- visidata/loaders/npy.py +7 -7
- visidata/loaders/odf.py +4 -1
- visidata/loaders/orgmode.py +428 -0
- visidata/loaders/pandas_freqtbl.py +14 -20
- visidata/loaders/parquet.py +62 -6
- visidata/loaders/pcap.py +3 -3
- visidata/loaders/pdf.py +4 -3
- visidata/loaders/png.py +19 -13
- visidata/loaders/postgres.py +9 -8
- visidata/loaders/rec.py +7 -3
- visidata/loaders/s3.py +342 -0
- visidata/loaders/sas.py +5 -5
- visidata/loaders/scrape.py +186 -0
- visidata/loaders/shp.py +6 -5
- visidata/loaders/spss.py +5 -6
- visidata/loaders/sqlite.py +68 -28
- visidata/loaders/texttables.py +1 -1
- visidata/loaders/toml.py +60 -0
- visidata/loaders/tsv.py +61 -19
- visidata/loaders/ttf.py +19 -7
- visidata/loaders/unzip_http.py +6 -5
- visidata/loaders/usv.py +1 -1
- visidata/loaders/vcf.py +16 -16
- visidata/loaders/vds.py +10 -7
- visidata/loaders/vdx.py +30 -5
- visidata/loaders/xlsb.py +8 -1
- visidata/loaders/xlsx.py +145 -25
- visidata/loaders/xml.py +6 -3
- visidata/loaders/xword.py +4 -4
- visidata/loaders/yaml.py +15 -5
- visidata/macros.py +129 -42
- visidata/main.py +119 -94
- visidata/mainloop.py +101 -155
- visidata/man/parse_options.py +2 -2
- visidata/man/vd.1 +302 -149
- visidata/man/vd.txt +291 -154
- visidata/memory.py +3 -3
- visidata/menu.py +104 -423
- visidata/metasheets.py +59 -141
- visidata/modify.py +78 -23
- visidata/motd.py +3 -3
- visidata/mouse.py +137 -0
- visidata/movement.py +43 -35
- visidata/optionssheet.py +99 -0
- visidata/path.py +113 -32
- visidata/pivot.py +73 -47
- visidata/plugins.py +65 -192
- visidata/pyobj.py +55 -205
- visidata/rename_col.py +20 -0
- visidata/save.py +37 -20
- visidata/search.py +54 -10
- visidata/selection.py +84 -5
- visidata/settings.py +162 -25
- visidata/sheets.py +239 -260
- visidata/shell.py +51 -21
- visidata/sidebar.py +162 -0
- visidata/sort.py +11 -4
- visidata/statusbar.py +114 -104
- visidata/stored_list.py +43 -0
- visidata/stored_prop.py +38 -0
- visidata/tests/benchmark.csv +52 -0
- visidata/tests/conftest.py +3 -3
- visidata/tests/test_cliptext.py +39 -0
- visidata/tests/test_commands.py +65 -7
- visidata/tests/test_edittext.py +2 -2
- visidata/tests/test_features.py +28 -0
- visidata/tests/test_menu.py +14 -0
- visidata/tests/test_path.py +13 -4
- visidata/text_source.py +53 -0
- visidata/textsheet.py +10 -3
- visidata/theme.py +44 -0
- visidata/themes/__init__.py +0 -0
- visidata/themes/ascii8.py +84 -0
- visidata/themes/asciimono.py +84 -0
- visidata/themes/light.py +17 -0
- visidata/threads.py +89 -40
- visidata/tuiwin.py +22 -0
- visidata/type_currency.py +22 -3
- visidata/type_date.py +31 -9
- visidata/type_floatsi.py +5 -1
- visidata/undo.py +17 -5
- visidata/utils.py +106 -23
- visidata/vdobj.py +28 -17
- visidata/windows.py +10 -0
- visidata/wrappers.py +9 -3
- visidata-3.0.1.data/data/share/applications/visidata.desktop +7 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/vd.1 +302 -149
- {visidata-2.11.1.data → visidata-3.0.1.data}/data/share/man/man1/visidata.1 +302 -149
- visidata-3.0.1.data/scripts/vd2to3.vdx +9 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/METADATA +12 -8
- visidata-3.0.1.dist-info/RECORD +258 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/WHEEL +1 -1
- vgit/__init__.py +0 -1
- vgit/gitsheet.py +0 -164
- visidata/layout.py +0 -44
- visidata/misc.py +0 -5
- visidata-2.11.1.data/scripts/vgit +0 -9
- visidata-2.11.1.dist-info/RECORD +0 -155
- {vgit → visidata/apps/vgit}/__main__.py +0 -0
- {vgit → visidata/apps/vgit}/abort.py +0 -0
- /visidata/{repeat.py → features/repeat.py} +0 -0
- {visidata-2.11.1.data → visidata-3.0.1.data}/scripts/vd +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/entry_points.txt +0 -0
- {visidata-2.11.1.dist-info → visidata-3.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
"""
|
2
|
+
# Usage
|
3
|
+
|
4
|
+
This plugin normalizes column names in any given sheet, so that the names are:
|
5
|
+
|
6
|
+
- Composed only of lowercase letters, numbers, and underscores.
|
7
|
+
|
8
|
+
- Valid Python identifiers. This is mostly handled by the rule above, but also
|
9
|
+
prohibits names beginning with a digit; that is handled by prefixing those
|
10
|
+
names with an underscore.
|
11
|
+
|
12
|
+
- Unique within the sheet. Non-unique names are suffixed with "__" and an
|
13
|
+
integer.
|
14
|
+
|
15
|
+
Unnamed columns are left as such.
|
16
|
+
|
17
|
+
For instance, a sheet with the following columns names:
|
18
|
+
|
19
|
+
- "Genus, Species"
|
20
|
+
- "Height"
|
21
|
+
- "5-score"
|
22
|
+
- "Height"
|
23
|
+
- ""
|
24
|
+
- ""
|
25
|
+
|
26
|
+
... would be converted to have the following column names:
|
27
|
+
|
28
|
+
- "genus_species"
|
29
|
+
- "height__0"
|
30
|
+
- "_5_score"
|
31
|
+
- "height__1"
|
32
|
+
- ""
|
33
|
+
- ""
|
34
|
+
|
35
|
+
## Commands
|
36
|
+
|
37
|
+
- `normalize-col-names` normalizes the names of all *non-hidden* columns in the
|
38
|
+
active sheet, per the approach described above.
|
39
|
+
|
40
|
+
"""
|
41
|
+
|
42
|
+
__author__ = "Jeremy Singer-Vine <jsvine@gmail.com>"
|
43
|
+
|
44
|
+
from visidata import vd, Sheet, asyncthread, Progress
|
45
|
+
from collections import Counter
|
46
|
+
import re
|
47
|
+
import string
|
48
|
+
|
49
|
+
nonalphanum_pat = re.compile(r"[^a-z0-9]+")
|
50
|
+
|
51
|
+
|
52
|
+
def normalize_name(name):
|
53
|
+
"""
|
54
|
+
Given a string, return a normalized string, per the first two rules
|
55
|
+
described above.
|
56
|
+
"""
|
57
|
+
# Lowercase and replace all non-alphanumeric characters with _
|
58
|
+
subbed = re.sub(nonalphanum_pat, "_", name.lower())
|
59
|
+
|
60
|
+
# Remove leading and trailing _s
|
61
|
+
stripped = subbed.strip("_")
|
62
|
+
|
63
|
+
# To ensure it's a valid Python identifier
|
64
|
+
if (stripped or "_")[0] in string.digits:
|
65
|
+
stripped = "_" + stripped
|
66
|
+
|
67
|
+
return stripped
|
68
|
+
|
69
|
+
|
70
|
+
def gen_normalize_names(names):
|
71
|
+
"""
|
72
|
+
Given a list of strings, yield fully-normalized conversions of those
|
73
|
+
strings, ensuring that each is unique.
|
74
|
+
"""
|
75
|
+
base = list(map(normalize_name, names))
|
76
|
+
counts = Counter(base)
|
77
|
+
|
78
|
+
# Append __{i} to non-unique names
|
79
|
+
seen = dict((key, 0) for key in counts.keys())
|
80
|
+
for name in base:
|
81
|
+
if counts[name] == 1 or name == "":
|
82
|
+
norm_name = name
|
83
|
+
else:
|
84
|
+
norm_name = name + "__" + str(seen[name])
|
85
|
+
seen[name] += 1
|
86
|
+
yield norm_name
|
87
|
+
|
88
|
+
|
89
|
+
@Sheet.api
|
90
|
+
@asyncthread
|
91
|
+
def normalize_column_names(sheet):
|
92
|
+
"""
|
93
|
+
Normalize the names of all non-hidden columns on the active sheet.
|
94
|
+
"""
|
95
|
+
|
96
|
+
init_names = {}
|
97
|
+
gen = gen_normalize_names(c.name for c in sheet.visibleCols)
|
98
|
+
prog = Progress(gen, gerund="normalizing", total=sheet.nVisibleCols)
|
99
|
+
|
100
|
+
for i, norm_name in enumerate(prog):
|
101
|
+
col = sheet.visibleCols[i]
|
102
|
+
init_names[col] = col.name # Store for undo
|
103
|
+
col.name = norm_name
|
104
|
+
|
105
|
+
@asyncthread
|
106
|
+
def undo():
|
107
|
+
for c, oldname in init_names.items():
|
108
|
+
c.name = oldname
|
109
|
+
|
110
|
+
vd.addUndo(undo)
|
111
|
+
|
112
|
+
|
113
|
+
# Add longname-commands to VisiData to execute these methods
|
114
|
+
Sheet.addCommand(None, "normalize-col-names", "vd.sheet.normalize_column_names()", "normalize the names of all non-hidden columns")
|
115
|
+
|
116
|
+
vd.addMenuItems('''
|
117
|
+
Column > Rename > normalize all > normalize-col-names
|
118
|
+
''')
|
@@ -0,0 +1,18 @@
|
|
1
|
+
'''
|
2
|
+
Load new table from system clipboard
|
3
|
+
'''
|
4
|
+
|
5
|
+
from visidata import vd, BaseSheet, Path
|
6
|
+
|
7
|
+
|
8
|
+
@BaseSheet.api
|
9
|
+
def open_syspaste(sheet, filetype='tsv'):
|
10
|
+
import io
|
11
|
+
|
12
|
+
v = vd.sysclipValue().strip() or vd.fail('nothing to open')
|
13
|
+
|
14
|
+
p = Path('syspaste'+'.'+filetype, fp=io.BytesIO(v.encode('utf-8')))
|
15
|
+
return vd.openSource(p, filetype=filetype)
|
16
|
+
|
17
|
+
|
18
|
+
BaseSheet.addCommand('gShift+P', 'open-syspaste', 'vd.push(open_syspaste(filetype=vd.input("paste as filetype: ", value="tsv")))', 'open clipboard as filetype')
|
@@ -0,0 +1,157 @@
|
|
1
|
+
# requires shell tools: ping traceroute
|
2
|
+
# these tools aren't inlined as they require root privs
|
3
|
+
|
4
|
+
import re
|
5
|
+
import time
|
6
|
+
from statistics import mean
|
7
|
+
|
8
|
+
from visidata import vd, VisiData, BaseSheet, Sheet, Column, AttrColumn, Progress
|
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
|
+
@VisiData.api
|
16
|
+
def new_ping(vd, p):
|
17
|
+
'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)
|
20
|
+
|
21
|
+
|
22
|
+
class PingStatsSheet(Sheet):
|
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.
|
25
|
+
|
26
|
+
{longname}open-source{} to open the raw ping data.
|
27
|
+
'''
|
28
|
+
rowtype='hosts' # rowdef: PingColumn
|
29
|
+
columns = [
|
30
|
+
Column('hop', type=int, width=5, getter=lambda col,r: col.sheet.source.sources.index(r.ip)+1),
|
31
|
+
Column('hostname', getter=lambda col,r: r.name, width=40),
|
32
|
+
Column('ip', getter=lambda col,r: r.ip, width=17),
|
33
|
+
Column('avg_ms', type=float, fmtstr='%.1f', getter=lambda col,r: mean(r.getValues(col.sheet.source.rows))),
|
34
|
+
Column('max_ms', type=float, fmtstr='%.1f', getter=lambda col,r: max(r.getValues(col.sheet.source.rows))),
|
35
|
+
Column('count', type=int, getter=lambda col,r: len(list(r.getValues(col.sheet.source.rows)))),
|
36
|
+
]
|
37
|
+
def loader(self):
|
38
|
+
sheet = self.source
|
39
|
+
sheet.ensureLoaded()
|
40
|
+
|
41
|
+
self.rows = sheet.columns
|
42
|
+
|
43
|
+
|
44
|
+
def PingColumn(name, ip):
|
45
|
+
return Column(name, ip=ip, getter=lambda c,r: r.get(c.ip), type=float, fmtstr='%0.1f')
|
46
|
+
|
47
|
+
|
48
|
+
class PingSheet(Sheet):
|
49
|
+
rowtype = 'pings' # rowdef: {'time':time.time(), 'hostname': pingtime}
|
50
|
+
def __init__(self, *names, source=None, **kwargs):
|
51
|
+
super().__init__(*names, source=source, **kwargs)
|
52
|
+
self.sources = [source]
|
53
|
+
|
54
|
+
def ping_response(self, row, ip, data):
|
55
|
+
for line in data.splitlines():
|
56
|
+
m = re.search(r'from ([0-9\.]+).* time=(.*) ms', line)
|
57
|
+
if m:
|
58
|
+
row[ip] = m.group(2)
|
59
|
+
# assert ip == m.group(1)
|
60
|
+
|
61
|
+
def traceroute_response(self, ip, data):
|
62
|
+
for line in data.splitlines():
|
63
|
+
m = re.search(r'(\d+) (\S+) \((\S+)\) (.*) ms', line)
|
64
|
+
if m:
|
65
|
+
ttl, hostname, inner_ip, latency_ms = m.groups()
|
66
|
+
self.routes[ip][int(ttl)-1] = inner_ip
|
67
|
+
if inner_ip == ip:
|
68
|
+
return # traceroute work is done, let ping do the rest
|
69
|
+
|
70
|
+
if inner_ip not in self.sources:
|
71
|
+
self.sources.insert(-1, inner_ip)
|
72
|
+
self.addColumn(PingColumn(hostname, inner_ip), index=-1)
|
73
|
+
self.send_trace(ip, int(ttl)+1) # get next hop
|
74
|
+
break
|
75
|
+
|
76
|
+
def ping_error(self, ip, data):
|
77
|
+
if ip in self.sources:
|
78
|
+
self.sources.remove(ip)
|
79
|
+
vd.warning("%s removed: %s" % (ip, data))
|
80
|
+
|
81
|
+
def update_traces(self, row, ip):
|
82
|
+
rtes = self.routes.get(ip)
|
83
|
+
if rtes is None:
|
84
|
+
rtes = []
|
85
|
+
self.routes[ip] = rtes
|
86
|
+
|
87
|
+
if ip in rtes:
|
88
|
+
return
|
89
|
+
|
90
|
+
for i, inner_ip in enumerate(rtes):
|
91
|
+
if inner_ip is None:
|
92
|
+
self.send_trace(ip, i+1)
|
93
|
+
|
94
|
+
self.send_trace(ip, len(rtes)+1) # and one more for the road
|
95
|
+
|
96
|
+
def send_trace(self, ip, n):
|
97
|
+
sh = vd.importExternal('sh')
|
98
|
+
|
99
|
+
while n > len(self.routes[ip]):
|
100
|
+
self.routes[ip].append(None)
|
101
|
+
|
102
|
+
sh.traceroute('--sim-queries=1',
|
103
|
+
'--queries=1',
|
104
|
+
'--first=%s' % n,
|
105
|
+
'--max-hops=%s' % n,
|
106
|
+
ip,
|
107
|
+
_bg=True, _bg_exc=False,
|
108
|
+
_out=lambda data,self=self,ip=ip,a=1: self.traceroute_response(ip, data),
|
109
|
+
_err=lambda data,self=self,ip=ip,a=1: self.ping_error(ip, data))
|
110
|
+
|
111
|
+
def iterload(self):
|
112
|
+
sh = vd.importExternal('sh')
|
113
|
+
|
114
|
+
self.stop = False
|
115
|
+
self.start_time = time.time()
|
116
|
+
self.columns.clear()
|
117
|
+
|
118
|
+
for ip in self.sources:
|
119
|
+
self.addColumn(PingColumn(ip, ip))
|
120
|
+
|
121
|
+
self.routes = {}
|
122
|
+
self.rows = []
|
123
|
+
pings_sent = {}
|
124
|
+
ping_count = self.options.ping_count
|
125
|
+
with Progress(total=ping_count*len(self.sources), gerund='pinging') as prog:
|
126
|
+
while not self.stop:
|
127
|
+
r = {'time':time.time()}
|
128
|
+
yield r
|
129
|
+
|
130
|
+
npings = 0 # this loop
|
131
|
+
for ip in self.sources:
|
132
|
+
self.update_traces(r, ip)
|
133
|
+
pings_sent[ip] = pings_sent.get(ip, 0)+1
|
134
|
+
if ping_count and pings_sent[ip] <= ping_count:
|
135
|
+
sh.ping('-c', '1', ip, _bg=True, _bg_exc=False, _timeout=30,
|
136
|
+
_out=lambda data,self=self,r=r,ip=ip: self.ping_response(r,ip,data),
|
137
|
+
_err=lambda data,self=self,ip=ip,a=1: self.ping_error(ip, data))
|
138
|
+
npings += 1
|
139
|
+
else:
|
140
|
+
r[ip] = None
|
141
|
+
|
142
|
+
if npings == 0:
|
143
|
+
vd.status('no more pings to send')
|
144
|
+
break
|
145
|
+
|
146
|
+
time.sleep(self.options.ping_interval)
|
147
|
+
|
148
|
+
|
149
|
+
vd.option('ping_count', 3, 'send this many pings to each host', sheettype=PingSheet)
|
150
|
+
vd.option('ping_interval', 0.1, 'wait between ping rounds, in seconds', sheettype=PingSheet)
|
151
|
+
|
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')
|
154
|
+
|
155
|
+
vd.addMenuItems('''
|
156
|
+
System > Ping IP/hostname > open-ping
|
157
|
+
''')
|
@@ -0,0 +1,208 @@
|
|
1
|
+
from visidata import *
|
2
|
+
|
3
|
+
|
4
|
+
@VisiData.api
|
5
|
+
def new_top(vd, p):
|
6
|
+
return vd.processes
|
7
|
+
|
8
|
+
|
9
|
+
class CPUStatsSheet(Sheet):
|
10
|
+
rowtype='CPUs' # rowdef = (count, perfect, times, freq) from psutil (see below)
|
11
|
+
columns = [
|
12
|
+
ColumnItem('cpu', 0, type=int, keycol=0),
|
13
|
+
ColumnItem('cpu_pct', 1, type=float)
|
14
|
+
]
|
15
|
+
def iterload(self):
|
16
|
+
psutil = vd.importExternal('psutil')
|
17
|
+
|
18
|
+
for i, k in enumerate(psutil.cpu_times()._fields):
|
19
|
+
self.addColumn(SubColumnItem(2, ColumnItem(k+'_s', i, type=float, sheet=self)))
|
20
|
+
|
21
|
+
for i, k in enumerate(psutil.cpu_freq()._fields):
|
22
|
+
self.addColumn(SubColumnItem(3, ColumnItem(k+'_MHz', i, type=float, sheet=self)))
|
23
|
+
|
24
|
+
for r in zip(range(psutil.cpu_count()),
|
25
|
+
psutil.cpu_percent(percpu=True),
|
26
|
+
psutil.cpu_times(percpu=True),
|
27
|
+
psutil.cpu_freq(percpu=True)):
|
28
|
+
yield r
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
class MemStatsSheet(Sheet):
|
33
|
+
rowtype = '' # rowdef: (name, value)
|
34
|
+
columns = [
|
35
|
+
AttrColumn('t', type=date, fmtstr='%H:%M:%S'),
|
36
|
+
AttrColumn('meminfo'),
|
37
|
+
AttrColumn('virtmem'),
|
38
|
+
AttrColumn('swapmem'),
|
39
|
+
]
|
40
|
+
nKeys = 1
|
41
|
+
|
42
|
+
def iterload(self):
|
43
|
+
psutil = vd.importExternal('psutil')
|
44
|
+
import time
|
45
|
+
|
46
|
+
proc = psutil.Process()
|
47
|
+
|
48
|
+
while True:
|
49
|
+
yield AttrDict(t=time.time(),
|
50
|
+
virtmem=psutil.virtual_memory(),
|
51
|
+
swapmem=psutil.swap_memory(),
|
52
|
+
meminfo=proc.memory_info(),
|
53
|
+
)
|
54
|
+
|
55
|
+
if self.nRows == 1: # first time through
|
56
|
+
for n in 'meminfo virtmem swapmem'.split():
|
57
|
+
c = self.column(n)
|
58
|
+
if not c.hidden:
|
59
|
+
c.expand([self.rows[0]])
|
60
|
+
|
61
|
+
time.sleep(1)
|
62
|
+
|
63
|
+
|
64
|
+
class UsefulProcessesSheet(Sheet):
|
65
|
+
columns = [
|
66
|
+
Column('pid', type=int, getter=lambda c,r: r[0].pid),
|
67
|
+
Column('name', getter=lambda c,r: r[0].name()),
|
68
|
+
Column('status', getter=lambda c,r: r[0].status()),
|
69
|
+
Column('cmdline', getter=lambda c,r: ' '.join(r[0].cmdline())),
|
70
|
+
Column('user', getter=lambda c,r: r[0].username()),
|
71
|
+
Column('real_uid', type=int, width=0, getter=lambda c,r: r[0].uids()[0]),
|
72
|
+
Column('effective_uid', type=int, width=0, getter=lambda c,r: r[0].uids()[1]),
|
73
|
+
Column('mem_uss', type=int, getter=lambda c,r: r[1].mem_uss),
|
74
|
+
Column('user_time_s', type=float, getter=lambda c,r: r[0].cpu_times()[0]),
|
75
|
+
Column('system_time_s', type=float, getter=lambda c,r: r[0].cpu_times()[1]),
|
76
|
+
]
|
77
|
+
def iterload(self):
|
78
|
+
psutil = vd.importExternal('psutil')
|
79
|
+
for pr in psutil.process_iter():
|
80
|
+
yield (pr, pr.memory_full_info())
|
81
|
+
|
82
|
+
|
83
|
+
class ProcessesSheet(Sheet):
|
84
|
+
columns = [
|
85
|
+
Column('pid', type=int, getter=lambda c,r: r.pid),
|
86
|
+
Column('name', getter=lambda c,r: r.name()),
|
87
|
+
Column('status', getter=lambda c,r: r.status()),
|
88
|
+
Column('parent_pid', type=int, getter=lambda c,r: r.ppid()),
|
89
|
+
Column('exe', getter=lambda c,r: r.exe()),
|
90
|
+
Column('cmdline', getter=lambda c,r: ' '.join(r.cmdline())),
|
91
|
+
Column('cwd', getter=lambda c,r: r.cwd()),
|
92
|
+
Column('user', getter=lambda c,r: r.username()),
|
93
|
+
|
94
|
+
Column('real_uid', type=int, width=0, getter=lambda c,r: r.uids()[0]),
|
95
|
+
Column('effective_uid', type=int, width=0, getter=lambda c,r: r.uids()[1]),
|
96
|
+
Column('saved_uid', type=int, width=0, getter=lambda c,r: r.uids()[2]),
|
97
|
+
|
98
|
+
Column('real_gid', type=int, width=0, getter=lambda c,r: r.gids()[0]),
|
99
|
+
Column('effective_gid', type=int, width=0, getter=lambda c,r: r.gids()[1]),
|
100
|
+
Column('saved_gid', type=int, width=0, getter=lambda c,r: r.gids()[2]),
|
101
|
+
|
102
|
+
Column('create_time', type=date, getter=lambda c,r: r.create_time()),
|
103
|
+
Column('cpu_num', type=int, getter=lambda c,r: r.cpu_num()),
|
104
|
+
Column('cpu_percent', type=float, getter=lambda c,r: r.cpu_percent()),
|
105
|
+
|
106
|
+
Column('tty', getter=lambda c,r: r.terminal()),
|
107
|
+
Column('nice', getter=lambda c,r: r.nice()),
|
108
|
+
Column('ioclass', getter=lambda c,r: r.ionice()[0]),
|
109
|
+
Column('ionice', getter=lambda c,r: r.ionice()[1]),
|
110
|
+
|
111
|
+
Column('user_time_s', type=float, getter=lambda c,r: r.cpu_times()[0]),
|
112
|
+
Column('system_time_s', type=float, getter=lambda c,r: r.cpu_times()[1]),
|
113
|
+
Column('children_user_time_s', type=float, width=0, getter=lambda c,r: r.cpu_times()[2]),
|
114
|
+
Column('children_system_time_s', type=float, width=0, getter=lambda c,r: r.cpu_times()[3]),
|
115
|
+
|
116
|
+
Column('read_ops', type=int, getter=lambda c,r: r.io_counters()[0]),
|
117
|
+
Column('write_ops', type=int, getter=lambda c,r: r.io_counters()[1]),
|
118
|
+
Column('read_bytes', type=int, getter=lambda c,r: r.io_counters()[2]),
|
119
|
+
Column('write_bytes', type=int, getter=lambda c,r: r.io_counters()[3]),
|
120
|
+
|
121
|
+
Column('voluntary_ctx_switches', type=int, getter=lambda c,r: r.num_ctx_switches()[0]),
|
122
|
+
Column('involuntary_ctx_switches', type=int, getter=lambda c,r: r.num_ctx_switches()[1]),
|
123
|
+
Column('num_fds', type=int, getter=lambda c,r: r.num_fds()),
|
124
|
+
Column('num_threads', type=int, getter=lambda c,r: r.num_threads()),
|
125
|
+
]
|
126
|
+
nKeys = 2
|
127
|
+
|
128
|
+
def iterload(self):
|
129
|
+
psutil = vd.importExternal('psutil')
|
130
|
+
# self.columns = []
|
131
|
+
# for c in ProcessesSheet.columns:
|
132
|
+
# self.addColumn(c)
|
133
|
+
|
134
|
+
for i,k in enumerate(psutil.Process().memory_full_info()._fields):
|
135
|
+
self.addColumn(Column('mem_'+k, type=int, getter=lambda c,r,i=i: r.memory_full_info()[i], cache=True))
|
136
|
+
# mem_uss is probably the most representative metric for determining how much memory is actually being used by a process. It represents the amount of memory that would be freed if the process was terminated right now.
|
137
|
+
|
138
|
+
yield from psutil.process_iter()
|
139
|
+
# try:
|
140
|
+
# self.addRow((pr, pr.memory_full_info()))
|
141
|
+
# except:
|
142
|
+
# self.addRow((pr, None))
|
143
|
+
|
144
|
+
class RlimitsSheet(Sheet):
|
145
|
+
columns = [
|
146
|
+
ColumnItem('rlimit', 0),
|
147
|
+
Column('soft', type=int, getter=lambda c,r: c.sheet.soft(r), setter=lambda c,r,v: c.sheet.set_soft(r, v)),
|
148
|
+
Column('hard', type=int, getter=lambda c,r: c.sheet.hard(r), setter=lambda c,r,v: c.sheet.set_hard(r, v))
|
149
|
+
]
|
150
|
+
|
151
|
+
def soft(self, r):
|
152
|
+
return self.source.rlimit(r[1])[0]
|
153
|
+
def hard(self, r):
|
154
|
+
return self.source.rlimit(r[1])[1]
|
155
|
+
def set_soft(self, r, v):
|
156
|
+
self.source.rlimit(r[1], (v, self.hard(r)))
|
157
|
+
def set_hard(self, r, v):
|
158
|
+
self.source.rlimit(r[1], (self.soft(r), v))
|
159
|
+
|
160
|
+
def iterload(self):
|
161
|
+
psutil = vd.importExternal('psutil')
|
162
|
+
for r in dir(psutil):
|
163
|
+
if r.startswith('RLIMIT'):
|
164
|
+
yield (r[7:], getattr(psutil, r))
|
165
|
+
|
166
|
+
|
167
|
+
@VisiData.lazy_property
|
168
|
+
def cpuStats(vd):
|
169
|
+
return CPUStatsSheet('cpu_stats')
|
170
|
+
|
171
|
+
@VisiData.lazy_property
|
172
|
+
def memStats(vd):
|
173
|
+
return MemStatsSheet('memory_stats')
|
174
|
+
|
175
|
+
@VisiData.lazy_property
|
176
|
+
def processes(vd):
|
177
|
+
return ProcessesSheet('processes')
|
178
|
+
|
179
|
+
|
180
|
+
BaseSheet.addCommand('', 'open-cpustats', 'vd.push(vd.cpuStats)', 'open CPU usage stats' )
|
181
|
+
BaseSheet.addCommand('', 'open-memstats', 'vd.push(vd.memStats)', 'open memory usage stats')
|
182
|
+
BaseSheet.addCommand('', 'open-processes', 'vd.push(vd.processes)', 'open system process stats')
|
183
|
+
|
184
|
+
@VisiData.api
|
185
|
+
def chooseSignal(vd):
|
186
|
+
import signal
|
187
|
+
d = [{'key': attr[3:]} for attr in dir(signal) if attr.startswith('SIG') and not attr.startswith('SIG_')]
|
188
|
+
return getattr(signal, 'SIG'+vd.chooseOne(d, type='signal'))
|
189
|
+
|
190
|
+
ProcessesSheet.addCommand('d', 'term-process', 'os.kill(cursorRow.pid, signal.SIGTERM)', 'send SIGTERM to process')
|
191
|
+
ProcessesSheet.addCommand('gd', 'term-selected', 'for r in someSelectedRows: os.kill(r.pid, signal.SIGTERM)', 'send SIGTERM to selected processes')
|
192
|
+
ProcessesSheet.addCommand('zd', 'kill-process', 'os.kill(cursorRow.pid, signal.SIGKILL)', 'send SIGKILL to process')
|
193
|
+
ProcessesSheet.addCommand('gzd', 'kill-selected', 'for r in someSelectedRows: os.kill(r.pid, signal.SIGKILL)', 'send SIGKILL to selected processes')
|
194
|
+
|
195
|
+
ProcessesSheet.addCommand('^K', 'signal-process', 'os.kill(cursorRow.pid, chooseSignal())', 'send chosen signal to process')
|
196
|
+
UsefulProcessesSheet.addCommand('^K', 'signal-selected', 'os.kill(cursorRow.pid, chooseSignal())', 'kill(2) send chosen signal to process')
|
197
|
+
UsefulProcessesSheet.addCommand('', 'open-rlimits', 'vd.push(RlimitsSheet(cursorRow.name() + "_rlimits", cursorRow))', 'push rlimits for this process')
|
198
|
+
|
199
|
+
vd.addMenuItem('System', 'Signal', 'current process', 'TERMinate', 'term-process')
|
200
|
+
vd.addMenuItem('System', 'Signal', 'current process', 'KILL', 'kill-process')
|
201
|
+
vd.addMenuItem('System', 'Signal', 'current process', 'choose signal', 'signal-process')
|
202
|
+
vd.addMenuItem('System', 'Signal', 'selected processes', 'TERMinate', 'term-selected')
|
203
|
+
vd.addMenuItem('System', 'Signal', 'selected processes', 'KILL', 'kill-selected')
|
204
|
+
vd.addMenuItem('System', 'Signal', 'selected processes', 'choose signal', 'signal-selected')
|
205
|
+
vd.addMenuItem('System', 'Stats', 'CPU', 'open-cpustats')
|
206
|
+
vd.addMenuItem('System', 'Stats', 'memory', 'open-memstats')
|
207
|
+
vd.addMenuItem('System', 'Stats', 'processes', 'open-processes')
|
208
|
+
vd.addMenuItem('System', 'Stats', 'resource limits', 'open-rlimits')
|
@@ -0,0 +1,6 @@
|
|
1
|
+
from visidata import vd, Sheet
|
2
|
+
|
3
|
+
Sheet.addCommand(None, 'random-rows', 'nrows=int(input("random number to filter: ", value=nRows)); vs=copy(sheet); vs.name=name+"_sample"; vs.rows=sample(rows, nrows or nRows); vd.push(vs)', 'open duplicate sheet with a random population subset of N rows')
|
4
|
+
Sheet.addCommand(None, 'select-random', 'nrows=int(input("random number to select: ", value=nRows)); select(sample(rows, nrows or nRows))', 'select random sample of N rows')
|
5
|
+
|
6
|
+
vd.addMenuItems('Row > Select > random sample > select-random')
|
@@ -5,17 +5,11 @@ from visidata import asyncthread, options, vd
|
|
5
5
|
from visidata import VisiData, BaseSheet, Sheet, Column, Progress
|
6
6
|
|
7
7
|
|
8
|
-
@
|
9
|
-
def
|
10
|
-
|
11
|
-
vd.warning('no %s selected' % sheet.rowtype)
|
12
|
-
return
|
13
|
-
modified = 'column' if len(cols) == 1 else 'columns'
|
14
|
-
rex = vd.input("transform %s by regex: " % modified, type="regex-subst")
|
15
|
-
setValuesFromRegex(cols, rows, rex)
|
8
|
+
@VisiData.lazy_property
|
9
|
+
def help_regex(vd):
|
10
|
+
return vd.getHelpPane('regex', module='visidata')
|
16
11
|
|
17
12
|
|
18
|
-
vd.option('regex_flags', 'I', 'flags to pass to re.compile() [AILMSUX]', replay=True)
|
19
13
|
vd.option('regex_maxsplit', 0, 'maxsplit to pass to regex.split', replay=True)
|
20
14
|
|
21
15
|
@VisiData.api
|
@@ -24,6 +18,8 @@ def makeRegexSplitter(vd, regex, origcol):
|
|
24
18
|
|
25
19
|
@VisiData.api
|
26
20
|
def makeRegexMatcher(vd, regex, origcol):
|
21
|
+
if not regex.groups:
|
22
|
+
vd.fail('specify a capture group') #1778
|
27
23
|
def _regexMatcher(row):
|
28
24
|
m = regex.search(origcol.getDisplayValue(row))
|
29
25
|
if m:
|
@@ -52,12 +48,9 @@ def addRegexColumns(vs, regexMaker, origcol, regexstr):
|
|
52
48
|
cols = {}
|
53
49
|
ncols = 0 # number of new columns added already
|
54
50
|
for r in Progress(vs.getSampleRows()):
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
continue
|
59
|
-
except Exception as e:
|
60
|
-
vd.exceptionCaught(e)
|
51
|
+
m = vd.callNoExceptions(func, r)
|
52
|
+
if not m:
|
53
|
+
continue
|
61
54
|
|
62
55
|
if isinstance(m, dict):
|
63
56
|
for name in m:
|
@@ -74,12 +67,15 @@ def addRegexColumns(vs, regexMaker, origcol, regexstr):
|
|
74
67
|
else:
|
75
68
|
raise TypeError("addRegexColumns() expects a dict, list, or tuple from regexMaker, but got a "+type(m).__name__)
|
76
69
|
|
70
|
+
if not cols:
|
71
|
+
vd.warning("no regex matches found, didn't add column")
|
72
|
+
return
|
73
|
+
|
77
74
|
vs.addColumnAtCursor(*cols.values())
|
78
75
|
|
79
76
|
|
80
77
|
@VisiData.api
|
81
|
-
def regexTransform(vd, origcol,
|
82
|
-
before, after = vd.parse_sed_transform(instr)
|
78
|
+
def regexTransform(vd, origcol, before='', after=''):
|
83
79
|
return lambda col,row,origcol=origcol,before=before,after=after,flags=origcol.sheet.regex_flags(): re.sub(before, after, origcol.getDisplayValue(row), flags=flags)
|
84
80
|
|
85
81
|
|
@@ -104,27 +100,47 @@ def indexWithEscape(s, char, escape_char='\\'):
|
|
104
100
|
return None
|
105
101
|
|
106
102
|
|
103
|
+
@Sheet.api
|
107
104
|
@asyncthread
|
108
|
-
def setValuesFromRegex(cols, rows,
|
109
|
-
transforms = [vd.regexTransform(col,
|
105
|
+
def setValuesFromRegex(sheet, cols, rows, before='', after=''):
|
106
|
+
transforms = [vd.regexTransform(col, before=before, after=after) for col in cols]
|
110
107
|
vd.addUndoSetValues(cols, rows)
|
111
108
|
for r in Progress(rows, 'replacing'):
|
112
109
|
for col, transform in zip(cols, transforms):
|
113
|
-
col.
|
110
|
+
vd.callNoExceptions(col.setValue, r, transform(col, r))
|
111
|
+
|
114
112
|
for col in cols:
|
115
113
|
col.recalc()
|
116
114
|
|
117
115
|
|
118
|
-
@
|
119
|
-
def
|
120
|
-
|
121
|
-
|
116
|
+
@VisiData.api
|
117
|
+
def inputRegex(vd, prompt, type='regex', **kwargs):
|
118
|
+
return vd.input(prompt, type=type, help=vd.help_regex, **kwargs)
|
119
|
+
|
122
120
|
|
123
121
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
122
|
+
@VisiData.api
|
123
|
+
def inputRegexSubst(vd, prompt):
|
124
|
+
'Input regex transform via oneliner (separated with `/`). Return parsed transformer as dict(before=, after=).'
|
125
|
+
return vd.inputMultiple(before=dict(type='regex', prompt='search: ', help=prompt),
|
126
|
+
after=dict(type='regex-replace', prompt='replace: ', help=prompt))
|
127
|
+
|
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')
|
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)')
|
135
|
+
|
136
|
+
|
137
|
+
vd.addMenuItems('''
|
138
|
+
Edit > Modify > selected cells > regex substitution > setcol-regex-subst
|
139
|
+
Column > Add column > capture by regex > addcol-capture
|
140
|
+
Column > Add column > split by regex > addcol-split
|
141
|
+
Column > Add column > subst by regex > addcol-regex-subst
|
142
|
+
Row > Select > by regex > current column > select-col-regex
|
143
|
+
Row > Select > by regex > all columns > select-cols-regex
|
144
|
+
Row > Unselect > by regex > current column > unselect-col-regex
|
145
|
+
Row > Unselect > by regex > all columns > unselect-cols-regex
|
146
|
+
''')
|