visidata 2.11.1__py3-none-any.whl → 3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. visidata/__init__.py +72 -91
  2. visidata/_input.py +259 -42
  3. visidata/_open.py +84 -29
  4. visidata/_types.py +21 -3
  5. visidata/_urlcache.py +17 -4
  6. visidata/aggregators.py +65 -25
  7. visidata/apps/__init__.py +0 -0
  8. visidata/apps/vdsql/__about__.py +8 -0
  9. visidata/apps/vdsql/__init__.py +5 -0
  10. visidata/apps/vdsql/__main__.py +27 -0
  11. visidata/apps/vdsql/_ibis.py +748 -0
  12. visidata/apps/vdsql/bigquery.py +61 -0
  13. visidata/apps/vdsql/clickhouse.py +53 -0
  14. visidata/apps/vdsql/setup.py +40 -0
  15. visidata/apps/vdsql/snowflake.py +67 -0
  16. visidata/apps/vgit/__init__.py +13 -0
  17. {vgit → visidata/apps/vgit}/blame.py +5 -2
  18. {vgit → visidata/apps/vgit}/branch.py +31 -16
  19. {vgit → visidata/apps/vgit}/config.py +3 -3
  20. visidata/apps/vgit/diff.py +169 -0
  21. visidata/apps/vgit/gitsheet.py +161 -0
  22. {vgit → visidata/apps/vgit}/grep.py +6 -5
  23. visidata/apps/vgit/log.py +81 -0
  24. {vgit → visidata/apps/vgit}/main.py +18 -5
  25. {vgit → visidata/apps/vgit}/remote.py +8 -4
  26. visidata/apps/vgit/repos.py +71 -0
  27. {vgit → visidata/apps/vgit}/setup.py +6 -4
  28. visidata/apps/vgit/stash.py +69 -0
  29. visidata/apps/vgit/status.py +204 -0
  30. {vgit → visidata/apps/vgit}/statusbar.py +2 -0
  31. visidata/basesheet.py +59 -50
  32. visidata/canvas.py +208 -93
  33. visidata/choose.py +6 -6
  34. visidata/clean_names.py +29 -0
  35. visidata/clipboard.py +73 -17
  36. visidata/cliptext.py +220 -46
  37. visidata/cmdlog.py +88 -114
  38. visidata/color.py +142 -56
  39. visidata/column.py +121 -129
  40. visidata/ddw/input.ddw +74 -79
  41. visidata/ddw/regex.ddw +57 -0
  42. visidata/ddwplay.py +33 -14
  43. visidata/deprecated.py +77 -3
  44. visidata/desktop/visidata.desktop +7 -0
  45. visidata/editor.py +12 -6
  46. visidata/errors.py +5 -1
  47. visidata/experimental/__init__.py +0 -0
  48. visidata/experimental/diff_sheet.py +29 -0
  49. visidata/experimental/digit_autoedit.py +6 -0
  50. visidata/experimental/gdrive.py +89 -0
  51. visidata/experimental/google.py +37 -0
  52. visidata/experimental/gsheets.py +79 -0
  53. visidata/experimental/live_search.py +37 -0
  54. visidata/experimental/liveupdate.py +45 -0
  55. visidata/experimental/mark.py +133 -0
  56. visidata/experimental/noahs_tapestry/__init__.py +1 -0
  57. visidata/experimental/noahs_tapestry/tapestry.py +147 -0
  58. visidata/experimental/rownum.py +73 -0
  59. visidata/experimental/slide_cells.py +26 -0
  60. visidata/expr.py +8 -4
  61. visidata/extensible.py +30 -5
  62. visidata/features/__init__.py +0 -0
  63. visidata/features/addcol_audiometadata.py +42 -0
  64. visidata/features/addcol_histogram.py +34 -0
  65. visidata/features/canvas_save_svg.py +69 -0
  66. visidata/features/change_precision.py +46 -0
  67. visidata/features/cmdpalette.py +163 -0
  68. visidata/features/colorbrewer.py +363 -0
  69. visidata/{colorsheet.py → features/colorsheet.py} +17 -16
  70. visidata/features/command_server.py +105 -0
  71. visidata/features/currency_to_usd.py +70 -0
  72. visidata/{customdate.py → features/customdate.py} +2 -0
  73. visidata/features/dedupe.py +132 -0
  74. visidata/{describe.py → features/describe.py} +17 -15
  75. visidata/features/errors_guide.py +26 -0
  76. visidata/features/expand_cols.py +202 -0
  77. visidata/{fill.py → features/fill.py} +3 -1
  78. visidata/{freeze.py → features/freeze.py} +11 -6
  79. visidata/features/graph_seaborn.py +79 -0
  80. visidata/features/helloworld.py +10 -0
  81. visidata/features/hint_types.py +17 -0
  82. visidata/{incr.py → features/incr.py} +5 -0
  83. visidata/{join.py → features/join.py} +107 -53
  84. visidata/features/known_cols.py +21 -0
  85. visidata/features/layout.py +62 -0
  86. visidata/{melt.py → features/melt.py} +32 -21
  87. visidata/features/normcol.py +118 -0
  88. visidata/features/open_config.py +7 -0
  89. visidata/features/open_syspaste.py +18 -0
  90. visidata/features/ping.py +157 -0
  91. visidata/features/procmgr.py +208 -0
  92. visidata/features/random_sample.py +6 -0
  93. visidata/{regex.py → features/regex.py} +47 -31
  94. visidata/features/reload_every.py +55 -0
  95. visidata/features/rename_col_cascade.py +30 -0
  96. visidata/features/scroll_context.py +60 -0
  97. visidata/features/select_equal_selected.py +11 -0
  98. visidata/features/setcol_fake.py +65 -0
  99. visidata/{slide.py → features/slide.py} +75 -21
  100. visidata/features/sparkline.py +48 -0
  101. visidata/features/status_source.py +20 -0
  102. visidata/{sysedit.py → features/sysedit.py} +2 -1
  103. visidata/features/sysopen_mailcap.py +46 -0
  104. visidata/features/term_extras.py +13 -0
  105. visidata/{transpose.py → features/transpose.py} +5 -4
  106. visidata/features/type_ipaddr.py +73 -0
  107. visidata/features/type_url.py +11 -0
  108. visidata/{unfurl.py → features/unfurl.py} +9 -9
  109. visidata/{window.py → features/window.py} +2 -2
  110. visidata/form.py +50 -21
  111. visidata/freqtbl.py +81 -33
  112. visidata/fuzzymatch.py +414 -0
  113. visidata/graph.py +105 -33
  114. visidata/guide.py +180 -0
  115. visidata/help.py +75 -44
  116. visidata/hint.py +39 -0
  117. visidata/indexsheet.py +109 -0
  118. visidata/input_history.py +55 -0
  119. visidata/interface.py +58 -0
  120. visidata/keys.py +17 -16
  121. visidata/loaders/__init__.py +9 -0
  122. visidata/loaders/_pandas.py +61 -21
  123. visidata/loaders/api_airtable.py +70 -0
  124. visidata/loaders/api_bitio.py +102 -0
  125. visidata/loaders/api_matrix.py +148 -0
  126. visidata/loaders/api_reddit.py +306 -0
  127. visidata/loaders/api_zulip.py +249 -0
  128. visidata/loaders/archive.py +41 -7
  129. visidata/loaders/arrow.py +7 -7
  130. visidata/loaders/conll.py +49 -0
  131. visidata/loaders/csv.py +25 -7
  132. visidata/loaders/eml.py +3 -4
  133. visidata/loaders/f5log.py +1204 -0
  134. visidata/loaders/fec.py +325 -0
  135. visidata/loaders/fixed_width.py +2 -4
  136. visidata/loaders/frictionless.py +3 -3
  137. visidata/loaders/geojson.py +8 -5
  138. visidata/loaders/google.py +48 -0
  139. visidata/loaders/graphviz.py +4 -4
  140. visidata/loaders/hdf5.py +4 -4
  141. visidata/loaders/html.py +48 -10
  142. visidata/loaders/http.py +84 -30
  143. visidata/loaders/imap.py +20 -10
  144. visidata/loaders/jrnl.py +52 -0
  145. visidata/loaders/json.py +83 -29
  146. visidata/loaders/jsonla.py +74 -0
  147. visidata/loaders/lsv.py +15 -11
  148. visidata/loaders/mailbox.py +40 -0
  149. visidata/loaders/markdown.py +1 -3
  150. visidata/loaders/mbtiles.py +4 -5
  151. visidata/loaders/mysql.py +11 -13
  152. visidata/loaders/npy.py +7 -7
  153. visidata/loaders/odf.py +4 -1
  154. visidata/loaders/orgmode.py +428 -0
  155. visidata/loaders/pandas_freqtbl.py +14 -20
  156. visidata/loaders/parquet.py +62 -6
  157. visidata/loaders/pcap.py +3 -3
  158. visidata/loaders/pdf.py +4 -3
  159. visidata/loaders/png.py +19 -13
  160. visidata/loaders/postgres.py +9 -8
  161. visidata/loaders/rec.py +7 -3
  162. visidata/loaders/s3.py +342 -0
  163. visidata/loaders/sas.py +5 -5
  164. visidata/loaders/scrape.py +186 -0
  165. visidata/loaders/shp.py +6 -5
  166. visidata/loaders/spss.py +5 -6
  167. visidata/loaders/sqlite.py +68 -28
  168. visidata/loaders/texttables.py +1 -1
  169. visidata/loaders/toml.py +60 -0
  170. visidata/loaders/tsv.py +61 -19
  171. visidata/loaders/ttf.py +19 -7
  172. visidata/loaders/unzip_http.py +6 -5
  173. visidata/loaders/usv.py +1 -1
  174. visidata/loaders/vcf.py +16 -16
  175. visidata/loaders/vds.py +10 -7
  176. visidata/loaders/vdx.py +30 -5
  177. visidata/loaders/xlsb.py +8 -1
  178. visidata/loaders/xlsx.py +145 -25
  179. visidata/loaders/xml.py +6 -3
  180. visidata/loaders/xword.py +4 -4
  181. visidata/loaders/yaml.py +15 -5
  182. visidata/macros.py +129 -42
  183. visidata/main.py +119 -94
  184. visidata/mainloop.py +101 -155
  185. visidata/man/parse_options.py +2 -2
  186. visidata/man/vd.1 +301 -148
  187. visidata/man/vd.txt +290 -153
  188. visidata/memory.py +3 -3
  189. visidata/menu.py +104 -423
  190. visidata/metasheets.py +59 -141
  191. visidata/modify.py +78 -23
  192. visidata/motd.py +3 -3
  193. visidata/mouse.py +137 -0
  194. visidata/movement.py +43 -35
  195. visidata/optionssheet.py +99 -0
  196. visidata/path.py +113 -32
  197. visidata/pivot.py +73 -47
  198. visidata/plugins.py +65 -192
  199. visidata/pyobj.py +50 -201
  200. visidata/rename_col.py +20 -0
  201. visidata/save.py +37 -20
  202. visidata/search.py +54 -10
  203. visidata/selection.py +84 -5
  204. visidata/settings.py +162 -25
  205. visidata/sheets.py +229 -257
  206. visidata/shell.py +51 -21
  207. visidata/sidebar.py +162 -0
  208. visidata/sort.py +11 -4
  209. visidata/statusbar.py +113 -104
  210. visidata/stored_list.py +43 -0
  211. visidata/stored_prop.py +38 -0
  212. visidata/tests/conftest.py +3 -3
  213. visidata/tests/test_cliptext.py +39 -0
  214. visidata/tests/test_commands.py +62 -7
  215. visidata/tests/test_edittext.py +2 -2
  216. visidata/tests/test_features.py +17 -0
  217. visidata/tests/test_menu.py +14 -0
  218. visidata/tests/test_path.py +13 -4
  219. visidata/text_source.py +53 -0
  220. visidata/textsheet.py +10 -3
  221. visidata/theme.py +44 -0
  222. visidata/themes/__init__.py +0 -0
  223. visidata/themes/ascii8.py +84 -0
  224. visidata/themes/asciimono.py +84 -0
  225. visidata/themes/light.py +17 -0
  226. visidata/threads.py +87 -39
  227. visidata/tuiwin.py +22 -0
  228. visidata/type_currency.py +22 -3
  229. visidata/type_date.py +31 -9
  230. visidata/type_floatsi.py +5 -1
  231. visidata/undo.py +17 -5
  232. visidata/utils.py +106 -23
  233. visidata/vdobj.py +28 -17
  234. visidata/windows.py +10 -0
  235. visidata/wrappers.py +9 -3
  236. visidata-3.0.data/data/share/applications/visidata.desktop +7 -0
  237. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/vd.1 +301 -148
  238. {visidata-2.11.1.data → visidata-3.0.data}/data/share/man/man1/visidata.1 +301 -148
  239. visidata-3.0.data/scripts/vd2to3.vdx +9 -0
  240. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/METADATA +12 -8
  241. visidata-3.0.dist-info/RECORD +257 -0
  242. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/WHEEL +1 -1
  243. vgit/__init__.py +0 -1
  244. vgit/gitsheet.py +0 -164
  245. visidata/layout.py +0 -44
  246. visidata/misc.py +0 -5
  247. visidata-2.11.1.data/scripts/vgit +0 -9
  248. visidata-2.11.1.dist-info/RECORD +0 -155
  249. {vgit → visidata/apps/vgit}/__main__.py +0 -0
  250. {vgit → visidata/apps/vgit}/abort.py +0 -0
  251. /visidata/{repeat.py → features/repeat.py} +0 -0
  252. {visidata-2.11.1.data → visidata-3.0.data}/scripts/vd +0 -0
  253. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/LICENSE.gpl3 +0 -0
  254. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/entry_points.txt +0 -0
  255. {visidata-2.11.1.dist-info → visidata-3.0.dist-info}/top_level.txt +0 -0
@@ -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,7 @@
1
+ from visidata import vd, BaseSheet
2
+
3
+ BaseSheet.addCommand('gO', 'open-config', 'vd.push(open_txt(Path(options.config)))', 'open options.config as text sheet')
4
+
5
+ vd.addMenuItems('''
6
+ File > Options > edit config file > open-config
7
+ ''')
@@ -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
- @Sheet.api
9
- def setSubst(sheet, cols, rows):
10
- if not rows:
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
- try:
56
- m = func(r)
57
- if not m:
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, instr):
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, rex):
109
- transforms = [vd.regexTransform(col, rex) for col in cols]
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.setValueSafe(r, transform(col, r))
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
- @BaseSheet.api
119
- def regex_flags(sheet):
120
- 'Return flags to pass to regex functions from options'
121
- return sum(getattr(re, f.upper()) for f in options.regex_flags)
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
- Sheet.addCommand(':', 'split-col', 'addRegexColumns(makeRegexSplitter, cursorCol, input("split regex: ", type="regex-split"))', 'Add new columns from regex split')
125
- Sheet.addCommand(';', 'capture-col', 'addRegexColumns(makeRegexMatcher, cursorCol, input("capture regex: ", type="regex-capture"))', 'add new column from capture groups of regex; requires example row')
126
- Sheet.addCommand('', 'addcol-split', 'addColumnAtCursor(RegexColumn(makeRegexSplitter, cursorCol, input("split regex: ", type="regex-split")))', 'Add column split by regex')
127
- Sheet.addCommand('', 'addcol-capture', 'addColumnAtCursor(RegexColumn(makeRegexMatcher, cursorCol, input("capture regex: ", type="regex-capture")))', 'Add column captured by regex')
128
- Sheet.addCommand('*', 'addcol-subst', 'addColumnAtCursor(Column(cursorCol.name + "_re", getter=regexTransform(cursorCol, input("transform column by regex: ", type="regex-subst"))))', 'add column derived from current column, replacing regex with subst (may include \1 backrefs)')
129
- Sheet.addCommand('g*', 'setcol-subst', 'setSubst([cursorCol], someSelectedRows)', 'regex/subst - modify selected rows in current column, replacing regex with subst, (may include backreferences \\1 etc)')
130
- Sheet.addCommand('gz*', 'setcol-subst-all', 'setSubst(visibleCols, someSelectedRows)', 'modify selected rows in all visible columns, replacing regex with subst (may include \\1 backrefs)')
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
+ ''')