visidata 3.1.1__py3-none-any.whl → 3.2__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 +2 -2
- visidata/_input.py +70 -36
- visidata/_open.py +9 -6
- visidata/_types.py +2 -2
- visidata/aggregators.py +125 -16
- visidata/apps/vdsql/_ibis.py +8 -13
- visidata/basesheet.py +4 -1
- visidata/canvas.py +11 -7
- visidata/clipboard.py +11 -2
- visidata/cliptext.py +65 -23
- visidata/cmdlog.py +5 -1
- visidata/column.py +6 -2
- visidata/ddwplay.py +2 -2
- visidata/deprecated.py +91 -63
- visidata/errors.py +41 -5
- visidata/{features → experimental}/helloworld.py +1 -1
- visidata/expr.py +1 -0
- visidata/extensible.py +4 -0
- visidata/features/cmdpalette.py +3 -3
- visidata/features/describe.py +2 -2
- visidata/features/expand_cols.py +8 -5
- visidata/features/freeze.py +14 -2
- visidata/features/go_col.py +2 -1
- visidata/features/graph_zoom_y.py +47 -0
- visidata/features/incr.py +7 -3
- visidata/features/join.py +23 -12
- visidata/features/layout.py +8 -3
- visidata/features/melt.py +1 -0
- visidata/features/rank.py +103 -0
- visidata/features/reload_every.py +9 -6
- visidata/features/sysedit.py +14 -4
- visidata/features/transpose.py +1 -0
- visidata/features/window.py +12 -0
- visidata/form.py +4 -4
- visidata/freqtbl.py +47 -3
- visidata/fuzzymatch.py +8 -5
- visidata/graph.py +5 -3
- visidata/guides/AggregatorsSheet.md +84 -0
- visidata/guides/MacrosSheet.md +1 -1
- visidata/guides/RankGuide.md +51 -0
- visidata/guides/TypesSheet.md +1 -1
- visidata/guides/WindowFunctionGuide.md +49 -0
- visidata/help.py +3 -4
- visidata/indexsheet.py +1 -1
- visidata/loaders/_pandas.py +3 -1
- visidata/loaders/archive.py +6 -3
- visidata/loaders/csv.py +5 -1
- visidata/loaders/eml.py +2 -0
- visidata/loaders/f5log.py +2 -2
- visidata/loaders/fec.py +6 -9
- visidata/loaders/fixed_width.py +2 -0
- visidata/loaders/hdf5.py +34 -10
- visidata/loaders/npy.py +54 -23
- visidata/loaders/orgmode.py +3 -2
- visidata/loaders/pandas_freqtbl.py +4 -0
- visidata/loaders/psv.py +13 -0
- visidata/loaders/sqlite.py +1 -1
- visidata/loaders/vds.py +3 -4
- visidata/macros.py +4 -3
- visidata/main.py +11 -5
- visidata/mainloop.py +7 -4
- visidata/man/parse_options.py +3 -2
- visidata/man/vd.1 +26 -14
- visidata/man/vd.txt +25 -14
- visidata/menu.py +9 -9
- visidata/metasheets.py +3 -3
- visidata/mouse.py +1 -0
- visidata/pyobj.py +17 -9
- visidata/save.py +5 -1
- visidata/selection.py +29 -18
- visidata/settings.py +2 -2
- visidata/sheets.py +52 -24
- visidata/shell.py +2 -2
- visidata/sidebar.py +4 -2
- visidata/sort.py +89 -11
- visidata/statusbar.py +10 -9
- visidata/tests/test_cliptext.py +151 -0
- visidata/tests/test_commands.py +5 -2
- visidata/tests/test_menu.py +1 -1
- visidata/textsheet.py +34 -8
- visidata/themes/ascii8.py +2 -2
- visidata/themes/light.py +5 -0
- visidata/threads.py +16 -8
- visidata/undo.py +1 -1
- visidata/vendor/__init__.py +0 -0
- {visidata-3.1.1.data → visidata-3.2.data}/data/share/man/man1/vd.1 +26 -14
- {visidata-3.1.1.data → visidata-3.2.data}/data/share/man/man1/visidata.1 +26 -14
- {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/METADATA +62 -15
- {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/RECORD +95 -89
- {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/WHEEL +1 -1
- {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/entry_points.txt +1 -0
- visidata-3.1.1.data/scripts/vd +0 -6
- {visidata-3.1.1.data → visidata-3.2.data}/data/share/applications/visidata.desktop +0 -0
- {visidata-3.1.1.data → visidata-3.2.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.1.1.dist-info → visidata-3.2.dist-info}/top_level.txt +0 -0
visidata/loaders/sqlite.py
CHANGED
visidata/loaders/vds.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
import json
|
4
4
|
|
5
|
-
from visidata import VisiData, JsonSheet, Progress, IndexSheet, SettableColumn, ItemColumn, ExprColumn
|
5
|
+
from visidata import vd, VisiData, JsonSheet, Progress, IndexSheet, SettableColumn, ItemColumn, ExprColumn
|
6
6
|
|
7
7
|
|
8
8
|
NL='\n'
|
@@ -80,11 +80,10 @@ class VdsSheet(JsonSheet):
|
|
80
80
|
classname = 'ItemColumn'
|
81
81
|
d['expr'] = d['name']
|
82
82
|
|
83
|
-
c =
|
83
|
+
c = vd.getGlobals()[classname](d.pop('name'), sheet=self)
|
84
84
|
self.addColumn(c)
|
85
85
|
self.colnames[c.name] = c
|
86
|
-
|
87
|
-
setattr(c, k, v)
|
86
|
+
c.__setstate__(d) # must happen after addColumn sets .sheet
|
88
87
|
|
89
88
|
line = fp.readline()
|
90
89
|
|
visidata/macros.py
CHANGED
@@ -107,9 +107,10 @@ def afterExecSheet(cmdlog, sheet, escaped, err):
|
|
107
107
|
if not vd.activeCommand: return
|
108
108
|
if vd.activeCommand.longname == 'macro-record': return
|
109
109
|
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
if vd.activeCommand.replayable:
|
111
|
+
cmd = copy(vd.activeCommand)
|
112
|
+
cmd.sheet = ''
|
113
|
+
vd.macroMode.addRow(cmd)
|
113
114
|
|
114
115
|
|
115
116
|
@CommandLogJsonl.api
|
visidata/main.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# Usage: $0 [<options>] [<input> ...]
|
3
3
|
# $0 [<options>] --play <cmdlog> [--batch] [-w <waitsecs>] [-o <output>] [field=value ...]
|
4
4
|
|
5
|
-
__version__ = '3.
|
5
|
+
__version__ = '3.2'
|
6
6
|
__version_info__ = 'saul.pw/VisiData v' + __version__
|
7
7
|
|
8
8
|
from copy import copy
|
@@ -16,7 +16,7 @@ import signal
|
|
16
16
|
import warnings
|
17
17
|
import builtins # to override print
|
18
18
|
|
19
|
-
from visidata import vd, options, run, BaseSheet, AttrDict
|
19
|
+
from visidata import vd, options, run, BaseSheet, AttrDict, stacktrace
|
20
20
|
from visidata import Path
|
21
21
|
from visidata.settings import _get_config_file
|
22
22
|
import visidata
|
@@ -189,7 +189,7 @@ def main_vd():
|
|
189
189
|
vd.warning(e)
|
190
190
|
|
191
191
|
warnings.showwarning = vd.warning
|
192
|
-
vd.
|
192
|
+
vd.printerr = lambda *args: builtins.print(*args, file=sys.stderr)
|
193
193
|
|
194
194
|
flPipedInput = not sys.stdin.isatty()
|
195
195
|
flPipedOutput = not sys.stdout.isatty()
|
@@ -344,6 +344,8 @@ def main_vd():
|
|
344
344
|
run(vd.sheets[0])
|
345
345
|
else:
|
346
346
|
if args.play == '-':
|
347
|
+
if vd.stdinSource.fptext.isatty():
|
348
|
+
vd.fail('replay commands must come by pipe, not by terminal')
|
347
349
|
vdfile = vd.stdinSource
|
348
350
|
else:
|
349
351
|
vdfile = Path(args.play)
|
@@ -357,6 +359,7 @@ def main_vd():
|
|
357
359
|
return 1
|
358
360
|
|
359
361
|
if vd.options.interactive:
|
362
|
+
vd.options.batch = False #2639
|
360
363
|
vd.execAsync = lambda *args, vd=vd, **kwargs: visidata.VisiData.execAsync(vd, *args, **kwargs)
|
361
364
|
run()
|
362
365
|
else:
|
@@ -369,7 +372,7 @@ def main_vd():
|
|
369
372
|
|
370
373
|
saver_threads = [t for t in vd.unfinishedThreads if t.name.startswith('save_')]
|
371
374
|
if saver_threads:
|
372
|
-
vd.
|
375
|
+
vd.printerr('finishing %d savers' % len(saver_threads))
|
373
376
|
vd.sync(*saver_threads)
|
374
377
|
|
375
378
|
vd._stdout.flush()
|
@@ -386,9 +389,12 @@ def vd_cli():
|
|
386
389
|
if vd.options.debug:
|
387
390
|
raise
|
388
391
|
except FileNotFoundError as e:
|
389
|
-
print(e)
|
392
|
+
print(e, file=sys.stderr)
|
390
393
|
if options.debug:
|
391
394
|
raise
|
395
|
+
except Exception as e:
|
396
|
+
for l in stacktrace(): #show the stack trace without carets
|
397
|
+
print(l, file=sys.stderr)
|
392
398
|
|
393
399
|
sys.stderr.flush()
|
394
400
|
sys.stdout.flush()
|
visidata/mainloop.py
CHANGED
@@ -64,6 +64,7 @@ def setWindows(vd, scr, pct=None):
|
|
64
64
|
disp_menu = getattr(vd, 'menuRunning', None) or vd.options.disp_menu
|
65
65
|
topmenulines = 1 if disp_menu else 0
|
66
66
|
h, w = scr.getmaxyx()
|
67
|
+
if h == 1: topmenulines = 0
|
67
68
|
|
68
69
|
n = 0
|
69
70
|
if pct:
|
@@ -71,6 +72,7 @@ def setWindows(vd, scr, pct=None):
|
|
71
72
|
n = abs(pct)*h//100
|
72
73
|
n = min(n, h-topmenulines-3)
|
73
74
|
n = max(3, n)
|
75
|
+
if n > h: n = 0
|
74
76
|
|
75
77
|
desiredConfig = dict(pct=pct, n=n, h=h-topmenulines, w=w)
|
76
78
|
|
@@ -251,13 +253,14 @@ def mainloop(vd, scr):
|
|
251
253
|
vd.checkForFinishedThreads()
|
252
254
|
vd.callNoExceptions(sheet.checkCursor)
|
253
255
|
|
254
|
-
# no idle redraw unless background threads are running
|
255
256
|
time.sleep(0) # yield to other threads which may not have started yet
|
256
257
|
if vd._nextCommands:
|
257
|
-
if vd.
|
258
|
-
|
259
|
-
else:
|
258
|
+
if vd.unfinishedThreads: #2369 #2635
|
259
|
+
# while running a bg thread for a command, schedule infrequent redraws
|
260
260
|
vd.curses_timeout = nonidle_timeout
|
261
|
+
else:
|
262
|
+
# otherwise, schedule the next redraw and command (immediately, for default replay_wait)
|
263
|
+
vd.curses_timeout = int(vd.options.replay_wait*1000)
|
261
264
|
elif vd.unfinishedThreads:
|
262
265
|
vd.curses_timeout = nonidle_timeout
|
263
266
|
else:
|
visidata/man/parse_options.py
CHANGED
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
import sys
|
6
6
|
import visidata
|
7
|
+
dispwidth = visidata.dispwidth
|
7
8
|
|
8
9
|
|
9
10
|
fncli, fnopts = sys.argv[1:]
|
@@ -31,7 +32,7 @@ with open(fncli, 'w') as cliOut:
|
|
31
32
|
optkeys = visidata.options.keys()
|
32
33
|
optvalues = [visidata.options._opts._get(optname) for optname in optkeys]
|
33
34
|
|
34
|
-
widestoptwidth, widestopt = sorted((
|
35
|
+
widestoptwidth, widestopt = sorted((dispwidth(opt.name)+dispwidth(str(opt.value)), opt.name) for opt in optvalues)[-1]
|
35
36
|
print('widest option+default is "%s", width %d' % (widestopt, widestoptwidth))
|
36
37
|
widestoptwidth = 35
|
37
38
|
menuOut.write('.Bl -tag -width %s -compact\n' % ('X'*(widestoptwidth+3)))
|
@@ -51,7 +52,7 @@ with open(fncli, 'w') as cliOut:
|
|
51
52
|
else:
|
52
53
|
cli_optname=opt.name.replace('_', '-')
|
53
54
|
cli_type=type(opt.value).__name__
|
54
|
-
optlen =
|
55
|
+
optlen = dispwidth(cli_optname)+dispwidth(cli_type)+1
|
55
56
|
if cli_type != 'bool' or visidata.options.getdefault(opt.name):
|
56
57
|
cliOut.write(options_cli_skel.format(cli_optname=cli_optname,
|
57
58
|
optname = opt.name,
|
visidata/man/vd.1
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
.Dd
|
1
|
+
.Dd June 13, 2025
|
2
2
|
.Dt vd \&1 "Quick Reference Guide"
|
3
3
|
.Os Linux/MacOS
|
4
4
|
.
|
@@ -193,6 +193,8 @@ adjust widths of all visible columns to Ar number
|
|
193
193
|
.Pp
|
194
194
|
.It Ic " -" Ns " (hyphen)"
|
195
195
|
hide current column
|
196
|
+
.It Ic "g-" Ns " (hyphen)"
|
197
|
+
hide any column that has multiple rows but only one distinct value
|
196
198
|
.It Ic "z-" Ns
|
197
199
|
reduce width of current column by half
|
198
200
|
.It Ic "gv" Ns
|
@@ -247,10 +249,10 @@ add/reset cache for current/all visible column(s)
|
|
247
249
|
.No add new columns from capture groups of Ar regex No (also requires example row)
|
248
250
|
.It Ic "z" Ns Ic "\&;" Ar expr
|
249
251
|
.No create new column from bash Ar expr Ns , with Sy $ Ns columnNames as variables
|
250
|
-
.It Ic " *" Ar
|
251
|
-
.No add column derived from current column, replacing Ar
|
252
|
-
.It Ic "g* gz*" Ar
|
253
|
-
.No modify selected rows in current/all visible column(s), replacing Ar
|
252
|
+
.It Ic " *" Ar search No Sy Tab No Ar replace
|
253
|
+
.No add column derived from current column, replacing Ar search No regex with Ar replace No (may include Sy \e1 No backrefs)
|
254
|
+
.It Ic "g* gz*" Ar search No Sy Tab No Ar replace
|
255
|
+
.No modify selected rows in current/all visible column(s), replacing Ar search No with Ar replace No (may include Sy \e1 No backrefs)
|
254
256
|
.Pp
|
255
257
|
.It Ic " ( g("
|
256
258
|
.No expand current/all visible column(s) of lists (e.g. Sy [3] Ns ) or dicts (e.g. Sy {3} Ns ) one level
|
@@ -300,7 +302,7 @@ sort ascending/descending by current column; replace any existing sort criteria
|
|
300
302
|
.It Ic " g[ g]"
|
301
303
|
sort ascending/descending by all key columns; replace any existing sort criteria
|
302
304
|
.It Ic " z[ z]"
|
303
|
-
sort ascending/descending by current column;
|
305
|
+
sort ascending/descending by current column; keep higher priority sort criteria
|
304
306
|
.It Ic "gz[ gz]"
|
305
307
|
sort ascending/descending by all key columns; add to existing sort criteria
|
306
308
|
.It Ic " \&""
|
@@ -316,6 +318,8 @@ open duplicate sheet with deepcopy of selected rows
|
|
316
318
|
The rows in these duplicated sheets (except deepcopy) are references to rows on the original source sheets, and so edits to the filtered rows will naturally be reflected in the original rows. Use
|
317
319
|
.Ic "g'"
|
318
320
|
to freeze sheet contents in a deliberate copy.
|
321
|
+
.Ic "z'"
|
322
|
+
replace current column with a frozen copy, with all cells evaluated
|
319
323
|
.
|
320
324
|
.Ss Editing Rows and Cells
|
321
325
|
.
|
@@ -352,6 +356,8 @@ fill null cells in current column with contents of non-null cells up the current
|
|
352
356
|
.
|
353
357
|
.It Ic " e" Ar text
|
354
358
|
edit contents of current cell
|
359
|
+
.It Ic " ^O"
|
360
|
+
.No edit contents of current cell in external Sy EDITOR
|
355
361
|
.It Ic " ge" Ar text
|
356
362
|
.No set contents of current column for selected rows to Ar text
|
357
363
|
.
|
@@ -443,6 +449,8 @@ increase/decrease zoom level, centered on cursor
|
|
443
449
|
zoom to fit full extent
|
444
450
|
.It Ic "z_" No (underbar)
|
445
451
|
set aspect ratio
|
452
|
+
.It Ic "g_" No (underbar)
|
453
|
+
Zoom y-axis to fit all visible data points
|
446
454
|
.It Ic " x" Ar xmin xmax
|
447
455
|
.No set Ar xmin Ns / Ns Ar xmax No on graph
|
448
456
|
.It Ic " y" Ar ymin ymax
|
@@ -782,7 +790,7 @@ abort replay
|
|
782
790
|
.It Ic ^T
|
783
791
|
.No open global Sy Threads Sheet No for all asynchronous threads running
|
784
792
|
.It Ic z^T
|
785
|
-
.No open current sheet's Sy Threads Sheet
|
793
|
+
.No open current sheet's Sy Threads Sheet
|
786
794
|
.El
|
787
795
|
.Bl -inset -compact
|
788
796
|
.It (sheet-specific commands)
|
@@ -791,7 +799,7 @@ abort replay
|
|
791
799
|
.It Ic " ^C"
|
792
800
|
abort thread at current row
|
793
801
|
.It Ic "g^C"
|
794
|
-
.No abort all threads on current Sy Threads Sheet
|
802
|
+
.No abort all threads on current Sy Threads Sheet
|
795
803
|
.El
|
796
804
|
.
|
797
805
|
.Ss DERIVED SHEETS
|
@@ -912,7 +920,7 @@ disable loading .visidatarc and plugin addons
|
|
912
920
|
.
|
913
921
|
.El
|
914
922
|
.Bl -tag -width XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX -compact
|
915
|
-
.It Sy --visidata-dir Ns = Ns Ar "str " No "
|
923
|
+
.It Sy --visidata-dir Ns = Ns Ar "str " No "/home/anja/.config/visidata"
|
916
924
|
directory to load and store additional files
|
917
925
|
.It Sy --debug No " False"
|
918
926
|
exit on error and display stacktrace
|
@@ -1055,7 +1063,7 @@ device ID associated with matrix login
|
|
1055
1063
|
client_id for reddit api
|
1056
1064
|
.It Sy --reddit-client-secret Ns = Ns Ar "str " No ""
|
1057
1065
|
client_secret for reddit api
|
1058
|
-
.It Sy --reddit-user-agent Ns = Ns Ar "str " No "3.
|
1066
|
+
.It Sy --reddit-user-agent Ns = Ns Ar "str " No "3.2"
|
1059
1067
|
user_agent for reddit api
|
1060
1068
|
.It Sy --zulip-batch-size Ns = Ns Ar "int " No "-100"
|
1061
1069
|
number of messages to fetch per call (<0 to fetch before anchor)
|
@@ -1100,6 +1108,8 @@ max number of fixed-width columns to create (0 is no max)
|
|
1100
1108
|
whether to include edge labels on graphviz diagrams
|
1101
1109
|
.It Sy --grep-base-dir Ns = Ns Ar "NoneType " No "None"
|
1102
1110
|
base directory for relative paths opened with sysopen-row
|
1111
|
+
.It Sy --hdf5-matrix-enumerate No " False"
|
1112
|
+
enumerate matrix rows and columns
|
1103
1113
|
.It Sy --html-title Ns = Ns Ar "str " No "<h2>{sheet.name}</h2>"
|
1104
1114
|
table header when saving to html
|
1105
1115
|
.It Sy --http-max-next Ns = Ns Ar "int " No "0"
|
@@ -1110,6 +1120,8 @@ http headers to send to requests
|
|
1110
1120
|
verify host and certificates for https
|
1111
1121
|
.It Sy --npy-allow-pickle No " False"
|
1112
1122
|
numpy allow unpickling objects (unsafe)
|
1123
|
+
.It Sy --npy-matrix-enumerate No " False"
|
1124
|
+
enumerate matrix rows and columns
|
1113
1125
|
.It Sy --pcap-internet Ns = Ns Ar "str " No "n"
|
1114
1126
|
(y/s/n) if save_dot includes all internet hosts separately (y), combined (s), or does not include the internet (n)
|
1115
1127
|
.It Sy --pdf-tables No " False"
|
@@ -1144,8 +1156,6 @@ API Key for api.apilayer.com/fixer
|
|
1144
1156
|
Cache days for currency conversions
|
1145
1157
|
.It Sy --describe-aggrs Ns = Ns Ar "str " No "mean stdev"
|
1146
1158
|
numeric aggregators to calculate on Describe sheet
|
1147
|
-
.It Sy --hello-world Ns = Ns Ar "str " No "\[u00A1]Hola mundo!"
|
1148
|
-
shown by the hello-world command
|
1149
1159
|
.It Sy --incr-base Ns = Ns Ar "float " No "1.0"
|
1150
1160
|
start value for column increments
|
1151
1161
|
.It Sy --ping-count Ns = Ns Ar "int " No "3"
|
@@ -1193,7 +1203,7 @@ command submenu indicator
|
|
1193
1203
|
indicator if command pushes sheet onto sheet stack
|
1194
1204
|
.It Sy "disp_menu_input " No "\[u2026]"
|
1195
1205
|
indicator if input required for command
|
1196
|
-
.It Sy "disp_menu_fmt " No "| VisiData {vd.version} | {vd.
|
1206
|
+
.It Sy "disp_menu_fmt " No "| VisiData {vd.version} | {vd.motd}"
|
1197
1207
|
right-side menu format string
|
1198
1208
|
.It Sy "disp_float_fmt " No "{:.02f}"
|
1199
1209
|
default fmtstr to format float values
|
@@ -1305,6 +1315,8 @@ replace whitespace with spaces in multiline
|
|
1305
1315
|
multiline string to indicate truncation
|
1306
1316
|
.It Sy "disp_multiline_focus" No "True"
|
1307
1317
|
only multiline cursor row
|
1318
|
+
.It Sy "color_multiline_bottom" No ""
|
1319
|
+
color of bottom line of multiline rows
|
1308
1320
|
.It Sy "color_aggregator " No "bold 255 white on 234 black"
|
1309
1321
|
color of aggregator summary on bottom row
|
1310
1322
|
.It Sy "disp_rstatus_fmt " No "{sheet.threadStatus} {sheet.keystrokeStatus} [:longname_status]{sheet.longname}[/] {sheet.nRows:9d} {sheet.rowtype} {sheet.modifiedStatus}{sheet.selectedStatus}{vd.replayStatus}{vd.sidebarStatus}"
|
@@ -1365,7 +1377,7 @@ histogram element character
|
|
1365
1377
|
show axes and legend on graph
|
1366
1378
|
.It Sy "disp_canvas_charset" No "\[u2800]\[u2801]\[u2802]\[u2803]\[u2804]\[u2805]\[u2806]\[u2807]\[u2808]\[u2809]\[u280A]\[u280B]\[u280C]\[u280D]\[u280E]\[u280F]\[u2810]\[u2811]\[u2812]\[u2813]\[u2814]\[u2815]\[u2816]\[u2817]\[u2818]\[u2819]\[u281A]\[u281B]\[u281C]\[u281D]\[u281E]\[u281F]\[u2820]\[u2821]\[u2822]\[u2823]\[u2824]\[u2825]\[u2826]\[u2827]\[u2828]\[u2829]\[u282A]\[u282B]\[u282C]\[u282D]\[u282E]\[u282F]\[u2830]\[u2831]\[u2832]\[u2833]\[u2834]\[u2835]\[u2836]\[u2837]\[u2838]\[u2839]\[u283A]\[u283B]\[u283C]\[u283D]\[u283E]\[u283F]\[u2840]\[u2841]\[u2842]\[u2843]\[u2844]\[u2845]\[u2846]\[u2847]\[u2848]\[u2849]\[u284A]\[u284B]\[u284C]\[u284D]\[u284E]\[u284F]\[u2850]\[u2851]\[u2852]\[u2853]\[u2854]\[u2855]\[u2856]\[u2857]\[u2858]\[u2859]\[u285A]\[u285B]\[u285C]\[u285D]\[u285E]\[u285F]\[u2860]\[u2861]\[u2862]\[u2863]\[u2864]\[u2865]\[u2866]\[u2867]\[u2868]\[u2869]\[u286A]\[u286B]\[u286C]\[u286D]\[u286E]\[u286F]\[u2870]\[u2871]\[u2872]\[u2873]\[u2874]\[u2875]\[u2876]\[u2877]\[u2878]\[u2879]\[u287A]\[u287B]\[u287C]\[u287D]\[u287E]\[u287F]\[u2880]\[u2881]\[u2882]\[u2883]\[u2884]\[u2885]\[u2886]\[u2887]\[u2888]\[u2889]\[u288A]\[u288B]\[u288C]\[u288D]\[u288E]\[u288F]\[u2890]\[u2891]\[u2892]\[u2893]\[u2894]\[u2895]\[u2896]\[u2897]\[u2898]\[u2899]\[u289A]\[u289B]\[u289C]\[u289D]\[u289E]\[u289F]\[u28A0]\[u28A1]\[u28A2]\[u28A3]\[u28A4]\[u28A5]\[u28A6]\[u28A7]\[u28A8]\[u28A9]\[u28AA]\[u28AB]\[u28AC]\[u28AD]\[u28AE]\[u28AF]\[u28B0]\[u28B1]\[u28B2]\[u28B3]\[u28B4]\[u28B5]\[u28B6]\[u28B7]\[u28B8]\[u28B9]\[u28BA]\[u28BB]\[u28BC]\[u28BD]\[u28BE]\[u28BF]\[u28C0]\[u28C1]\[u28C2]\[u28C3]\[u28C4]\[u28C5]\[u28C6]\[u28C7]\[u28C8]\[u28C9]\[u28CA]\[u28CB]\[u28CC]\[u28CD]\[u28CE]\[u28CF]\[u28D0]\[u28D1]\[u28D2]\[u28D3]\[u28D4]\[u28D5]\[u28D6]\[u28D7]\[u28D8]\[u28D9]\[u28DA]\[u28DB]\[u28DC]\[u28DD]\[u28DE]\[u28DF]\[u28E0]\[u28E1]\[u28E2]\[u28E3]\[u28E4]\[u28E5]\[u28E6]\[u28E7]\[u28E8]\[u28E9]\[u28EA]\[u28EB]\[u28EC]\[u28ED]\[u28EE]\[u28EF]\[u28F0]\[u28F1]\[u28F2]\[u28F3]\[u28F4]\[u28F5]\[u28F6]\[u28F7]\[u28F8]\[u28F9]\[u28FA]\[u28FB]\[u28FC]\[u28FD]\[u28FE]\[u28FF]"
|
1367
1379
|
charset to render 2x4 blocks on canvas
|
1368
|
-
.It Sy "
|
1380
|
+
.It Sy "disp_graph_pixel_random" No "False"
|
1369
1381
|
randomly choose attr from set of pixels instead of most common
|
1370
1382
|
.It Sy "disp_zoom_incr " No "2.0"
|
1371
1383
|
amount to multiply current zoomlevel when zooming
|
visidata/man/vd.txt
CHANGED
@@ -108,6 +108,8 @@ DESCRIPTION
|
|
108
108
|
gz_ number adjust widths of all visible columns to Ar number
|
109
109
|
|
110
110
|
- (hyphen) hide current column
|
111
|
+
g- (hyphen) hide any column that has multiple rows but only one dis‐
|
112
|
+
tinct value
|
111
113
|
z- reduce width of current column by half
|
112
114
|
gv unhide all columns
|
113
115
|
|
@@ -152,11 +154,12 @@ DESCRIPTION
|
|
152
154
|
requires example row)
|
153
155
|
z; expr create new column from bash expr, with $columnNames as
|
154
156
|
variables
|
155
|
-
*
|
156
|
-
|
157
|
-
|
157
|
+
* search Tab replace
|
158
|
+
add column derived from current column, replacing search
|
159
|
+
regex with replace (may include \1 backrefs)
|
160
|
+
g* gz* search Tab replace
|
158
161
|
modify selected rows in current/all visible column(s),
|
159
|
-
replacing
|
162
|
+
replacing search with replace (may include \1 backrefs)
|
160
163
|
|
161
164
|
( g( expand current/all visible column(s) of lists (e.g. [3])
|
162
165
|
or dicts (e.g. {3}) one level
|
@@ -191,8 +194,8 @@ DESCRIPTION
|
|
191
194
|
existing sort criteria
|
192
195
|
g[ g] sort ascending/descending by all key columns; replace
|
193
196
|
any existing sort criteria
|
194
|
-
z[ z] sort ascending/descending by current column;
|
195
|
-
|
197
|
+
z[ z] sort ascending/descending by current column; keep higher
|
198
|
+
priority sort criteria
|
196
199
|
gz[ gz] sort ascending/descending by all key columns; add to ex‐
|
197
200
|
isting sort criteria
|
198
201
|
" open duplicate sheet with only selected rows
|
@@ -202,7 +205,8 @@ DESCRIPTION
|
|
202
205
|
The rows in these duplicated sheets (except deepcopy) are references to
|
203
206
|
rows on the original source sheets, and so edits to the filtered rows
|
204
207
|
will naturally be reflected in the original rows. Use g' to freeze sheet
|
205
|
-
contents in a deliberate copy.
|
208
|
+
contents in a deliberate copy. z' replace current column with a frozen
|
209
|
+
copy, with all cells evaluated
|
206
210
|
|
207
211
|
Editing Rows and Cells
|
208
212
|
a za append blank row/column; appended columns cannot be
|
@@ -232,6 +236,7 @@ DESCRIPTION
|
|
232
236
|
f fill null cells in current column with contents of non-
|
233
237
|
null cells up the current column
|
234
238
|
e text edit contents of current cell
|
239
|
+
^O edit contents of current cell in external EDITOR
|
235
240
|
ge text set contents of current column for selected rows to text
|
236
241
|
|
237
242
|
Commands While Editing Input
|
@@ -292,6 +297,7 @@ DESCRIPTION
|
|
292
297
|
+ - increase/decrease zoom level, centered on cursor
|
293
298
|
_ (underbar) zoom to fit full extent
|
294
299
|
z_ (underbar) set aspect ratio
|
300
|
+
g_ (underbar) Zoom y-axis to fit all visible data points
|
295
301
|
x xmin xmax set xmin/xmax on graph
|
296
302
|
y ymin ymax set ymin/ymax on graph
|
297
303
|
s t u select/toggle/unselect rows on source sheet con‐
|
@@ -582,7 +588,8 @@ COMMANDLINE OPTIONS
|
|
582
588
|
files
|
583
589
|
-N, --nothing=T False disable loading
|
584
590
|
.visidatarc and plugin addons
|
585
|
-
--visidata-dir=str
|
591
|
+
--visidata-dir=str /home/anja/.config/visidata
|
592
|
+
directory to load and
|
586
593
|
store additional files
|
587
594
|
--debug False exit on error and display
|
588
595
|
stacktrace
|
@@ -739,7 +746,7 @@ COMMANDLINE OPTIONS
|
|
739
746
|
--reddit-client-id=str client_id for reddit api
|
740
747
|
--reddit-client-secret=str client_secret for reddit
|
741
748
|
api
|
742
|
-
--reddit-user-agent=str 3.
|
749
|
+
--reddit-user-agent=str 3.2 user_agent for reddit api
|
743
750
|
--zulip-batch-size=int -100 number of messages to
|
744
751
|
fetch per call (<0 to
|
745
752
|
fetch before anchor)
|
@@ -799,6 +806,8 @@ COMMANDLINE OPTIONS
|
|
799
806
|
--grep-base-dir=NoneType None base directory for rela‐
|
800
807
|
tive paths opened with
|
801
808
|
sysopen-row
|
809
|
+
--hdf5-matrix-enumerate False enumerate matrix rows and
|
810
|
+
columns
|
802
811
|
--html-title=str <h2>{sheet.name}</h2>
|
803
812
|
table header when saving
|
804
813
|
to html
|
@@ -810,6 +819,8 @@ COMMANDLINE OPTIONS
|
|
810
819
|
cates for https
|
811
820
|
--npy-allow-pickle False numpy allow unpickling
|
812
821
|
objects (unsafe)
|
822
|
+
--npy-matrix-enumerate False enumerate matrix rows and
|
823
|
+
columns
|
813
824
|
--pcap-internet=str n (y/s/n) if save_dot in‐
|
814
825
|
cludes all internet hosts
|
815
826
|
separately (y), combined
|
@@ -851,8 +862,6 @@ COMMANDLINE OPTIONS
|
|
851
862
|
--describe-aggrs=str mean stdev numeric aggregators to
|
852
863
|
calculate on Describe
|
853
864
|
sheet
|
854
|
-
--hello-world=str ¡Hola mundo! shown by the hello-world
|
855
|
-
command
|
856
865
|
--incr-base=float 1.0 start value for column
|
857
866
|
increments
|
858
867
|
--ping-count=int 3 send this many pings to
|
@@ -897,7 +906,7 @@ COMMANDLINE OPTIONS
|
|
897
906
|
onto sheet stack
|
898
907
|
disp_menu_input … indicator if input required for
|
899
908
|
command
|
900
|
-
disp_menu_fmt | VisiData {vd.version} | {vd.
|
909
|
+
disp_menu_fmt | VisiData {vd.version} | {vd.motd}
|
901
910
|
right-side menu format string
|
902
911
|
disp_float_fmt {:.02f} default fmtstr to format float
|
903
912
|
values
|
@@ -984,6 +993,8 @@ COMMANDLINE OPTIONS
|
|
984
993
|
disp_wrap_placeholder … multiline string to indicate
|
985
994
|
truncation
|
986
995
|
disp_multiline_focus True only multiline cursor row
|
996
|
+
color_multiline_bottom color of bottom line of multiline
|
997
|
+
rows
|
987
998
|
color_aggregator bold 255 white on 234 black
|
988
999
|
color of aggregator summary on
|
989
1000
|
bottom row
|
@@ -1033,7 +1044,7 @@ COMMANDLINE OPTIONS
|
|
1033
1044
|
⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿
|
1034
1045
|
charset to render 2x4 blocks on
|
1035
1046
|
canvas
|
1036
|
-
|
1047
|
+
disp_graph_pixel_random False randomly choose attr from set of
|
1037
1048
|
pixels instead of most common
|
1038
1049
|
disp_zoom_incr 2.0 amount to multiply current zoom‐
|
1039
1050
|
level when zooming
|
@@ -1189,4 +1200,4 @@ SUPPORTED SOURCES
|
|
1189
1200
|
AUTHOR
|
1190
1201
|
VisiData was made by Saul Pwanson <vd@saul.pw>.
|
1191
1202
|
|
1192
|
-
Linux/MacOS
|
1203
|
+
Linux/MacOS June 13, 2025 Linux/MacOS
|
visidata/menu.py
CHANGED
@@ -18,7 +18,7 @@ vd.theme_option('disp_menu_boxchars', '││──┌┐└┘├┤', 'box cha
|
|
18
18
|
vd.theme_option('disp_menu_more', '»', 'command submenu indicator')
|
19
19
|
vd.theme_option('disp_menu_push', '⎘', 'indicator if command pushes sheet onto sheet stack')
|
20
20
|
vd.theme_option('disp_menu_input', '…', 'indicator if input required for command')
|
21
|
-
vd.option('disp_menu_fmt', '| VisiData {vd.version} | {vd.
|
21
|
+
vd.option('disp_menu_fmt', '| VisiData {vd.version} | {vd.motd}', 'right-side menu format string')
|
22
22
|
|
23
23
|
BaseSheet.init('activeMenuItems', list)
|
24
24
|
vd.menuRunning = False
|
@@ -178,9 +178,9 @@ def drawSubmenu(vd, scr, sheet, y, x, menus, level, disp_menu_boxchars=''):
|
|
178
178
|
|
179
179
|
maxbinding = 0
|
180
180
|
if vd.options.disp_menu_keys:
|
181
|
-
maxbinding = max(
|
181
|
+
maxbinding = max(dispwidth(item.binding or '') for item in menus)+1
|
182
182
|
|
183
|
-
w = max(
|
183
|
+
w = max(dispwidth(item.title) for item in menus)+maxbinding+2
|
184
184
|
|
185
185
|
# draw borders before/under submenus
|
186
186
|
if level > 1:
|
@@ -225,11 +225,11 @@ def drawSubmenu(vd, scr, sheet, y, x, menus, level, disp_menu_boxchars=''):
|
|
225
225
|
mainbinding = vd.prettykeys(revbinds[0])
|
226
226
|
|
227
227
|
# actually display the menu item
|
228
|
-
title += ' '*(w-
|
228
|
+
title += ' '*(w-dispwidth(pretitle)-dispwidth(item.title)+1) # padding
|
229
229
|
|
230
230
|
menudraw(scr, y+i, x+1, pretitle+title, attr)
|
231
231
|
if maxbinding and mainbinding:
|
232
|
-
menudraw(scr, y+i, x+1+w-
|
232
|
+
menudraw(scr, y+i, x+1+w-dispwidth(mainbinding), mainbinding, attr.update(colors.keystrokes))
|
233
233
|
menudraw(scr, y+i, x+2+w, titlenote, attr)
|
234
234
|
menudraw(scr, y+i, x+3+w, ls, colors.color_menu)
|
235
235
|
|
@@ -317,7 +317,7 @@ def drawMenu(vd, scr, sheet):
|
|
317
317
|
BUTTON1_RELEASED=vd.nop,
|
318
318
|
BUTTON2_RELEASED=vd.nop,
|
319
319
|
BUTTON3_RELEASED=vd.nop)
|
320
|
-
x +=
|
320
|
+
x += dispwidth(item.title)+2
|
321
321
|
|
322
322
|
rightdisp = sheet.options.disp_menu_fmt.format(sheet=sheet, vd=vd)
|
323
323
|
menudraw(scr, 0, x+4, rightdisp, colors.color_menu)
|
@@ -355,13 +355,13 @@ def drawMenu(vd, scr, sheet):
|
|
355
355
|
|
356
356
|
# cmd.helpstr text
|
357
357
|
for i, line in enumerate(helplines):
|
358
|
-
menudraw(scr, y+i, helpx, ls+' '+line+' '*(helpw-
|
358
|
+
menudraw(scr, y+i, helpx, ls+' '+line+' '*(helpw-dispwidth(line)-3)+rs, helpattr)
|
359
359
|
y += len(helplines)
|
360
360
|
|
361
361
|
if sidelines:
|
362
362
|
menudraw(scr, y, helpx, ls+' '*(helpw-2)+rs, helpattr)
|
363
363
|
for i, line in enumerate(sidelines):
|
364
|
-
menudraw(scr, y+i+1, helpx, ls+' '+line+' '*(helpw-
|
364
|
+
menudraw(scr, y+i+1, helpx, ls+' '+line+' '*(helpw-dispwidth(line)-6)+rs, helpattr)
|
365
365
|
y += len(sidelines)+1
|
366
366
|
|
367
367
|
menudraw(scr, y, helpx, bl+bs*(helpw-2)+br, helpattr)
|
@@ -371,7 +371,7 @@ def drawMenu(vd, scr, sheet):
|
|
371
371
|
menudraw(scr, menuy, helpx+2, rsl, helpattr)
|
372
372
|
ks = vd.prettykeys(mainbinding or '(unbound)')
|
373
373
|
menudraw(scr, menuy, helpx+3, ' '+ks+' ', colors.color_menu_active)
|
374
|
-
menudraw(scr, menuy, helpx+2+
|
374
|
+
menudraw(scr, menuy, helpx+2+dispwidth(ks)+3, lsr, helpattr)
|
375
375
|
menudraw(scr, menuy, helpx+19, ' '+cmd.longname+' ', helpattr)
|
376
376
|
|
377
377
|
vd.onMouse(scr, helpx, menuy, helpw, y-menuy+1,
|
visidata/metasheets.py
CHANGED
@@ -38,8 +38,8 @@ Other commands (not specific to Columns Sheet):
|
|
38
38
|
'passthrough to the value on the source cursorRow'
|
39
39
|
def calcValue(self, srcCol):
|
40
40
|
return srcCol.getDisplayValue(srcCol.sheet.cursorRow)
|
41
|
-
def setValue(self, srcCol, val):
|
42
|
-
srcCol.setValue(srcCol.sheet.cursorRow, val)
|
41
|
+
def setValue(self, srcCol, val, setModified=True):
|
42
|
+
srcCol.setValue(srcCol.sheet.cursorRow, val, setModified=setModified)
|
43
43
|
|
44
44
|
columns = [
|
45
45
|
ColumnAttr('sheet', type=str),
|
@@ -134,7 +134,7 @@ globalCommand('gC', 'columns-all', 'vd.push(vd.allColumnsSheet)', 'open Columns
|
|
134
134
|
Sheet.addCommand('C', 'columns-sheet', 'vd.push(ColumnsSheet(name+"_columns", source=[sheet]))', 'open Columns Sheet: edit column properties for current sheet')
|
135
135
|
|
136
136
|
# used ColumnsSheet, affecting the 'row' (source column)
|
137
|
-
ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', '
|
137
|
+
ColumnsSheet.addCommand('g!', 'key-selected', 'for c in onlySelectedRows: c.sheet.setKeys([c])', 'set selected source columns as key columns')
|
138
138
|
ColumnsSheet.addCommand('gz!', 'key-off-selected', 'for c in onlySelectedRows: c.sheet.unsetKeys([c])', 'unset selected source columns as key columns')
|
139
139
|
|
140
140
|
ColumnsSheet.addCommand('g-', 'hide-selected', 'onlySelectedRows.hide()', 'hide selected source columns')
|
visidata/mouse.py
CHANGED
@@ -15,6 +15,7 @@ BaseSheet.init('mouseY', int)
|
|
15
15
|
def initCurses(vd):
|
16
16
|
curses.MOUSE_ALL = 0xffffffff
|
17
17
|
curses.mousemask(curses.MOUSE_ALL if vd.options.mouse_interval else 0)
|
18
|
+
curses.def_prog_mode()
|
18
19
|
curses.mouseinterval(vd.options.mouse_interval)
|
19
20
|
curses.mouseEvents = {}
|
20
21
|
|
visidata/pyobj.py
CHANGED
@@ -3,7 +3,7 @@ import inspect
|
|
3
3
|
import math
|
4
4
|
import numbers
|
5
5
|
|
6
|
-
from visidata import vd, asyncthread, ENTER, deduceType
|
6
|
+
from visidata import vd, asyncthread, ENTER, deduceType, anytype
|
7
7
|
from visidata import Sheet, Column, VisiData, ColumnItem, TableSheet, BaseSheet, Progress, ColumnAttr, SuspendCurses, TextSheet, setitem
|
8
8
|
import visidata
|
9
9
|
|
@@ -46,14 +46,19 @@ def view(vd, obj):
|
|
46
46
|
vd.run(PyobjSheet(getattr(obj, '__name__', ''), source=obj))
|
47
47
|
|
48
48
|
|
49
|
-
|
50
|
-
def getPublicAttrs(obj):
|
51
|
-
'Return all public attributes (not methods or `_`-prefixed) on object.'
|
52
|
-
return [k for k in dir(obj) if not k.startswith('_') and not callable(getattr(obj, k))]
|
53
|
-
|
54
49
|
def PyobjColumns(obj):
|
55
50
|
'Return columns for each public attribute on an object.'
|
56
|
-
|
51
|
+
cols = []
|
52
|
+
for k in dir(obj):
|
53
|
+
coltype = anytype
|
54
|
+
try:
|
55
|
+
if k.startswith('_') or callable(getattr(obj, k)):
|
56
|
+
continue
|
57
|
+
coltype = deduceType(getattr(obj, k))
|
58
|
+
except AttributeError: #2631 attributes like formatted_help can raise AttributeError
|
59
|
+
pass
|
60
|
+
cols.append(ColumnAttr(k, type=coltype))
|
61
|
+
return cols
|
57
62
|
|
58
63
|
def AttrColumns(attrnames):
|
59
64
|
'Return column names for all elements of list `attrnames`.'
|
@@ -151,8 +156,11 @@ class ColumnSourceAttr(Column):
|
|
151
156
|
'Use row as attribute name on sheet source'
|
152
157
|
def calcValue(self, attrname):
|
153
158
|
return getattr(self.sheet.source, attrname)
|
154
|
-
def setValue(self, attrname, value):
|
155
|
-
|
159
|
+
def setValue(self, attrname, value, setModified=True):
|
160
|
+
ret = setattr(self.sheet.source, attrname, value)
|
161
|
+
if setModified:
|
162
|
+
self.sheet.setModified()
|
163
|
+
return ret
|
156
164
|
|
157
165
|
def docstring(obj, attr):
|
158
166
|
v = getattr(obj, attr)
|
visidata/save.py
CHANGED
@@ -114,6 +114,10 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
|
|
114
114
|
if not vsheets: # blank tuple
|
115
115
|
vd.warning('no sheets to save')
|
116
116
|
return
|
117
|
+
if not givenpath.name:
|
118
|
+
vd.warning('no save path given')
|
119
|
+
return
|
120
|
+
|
117
121
|
unloaded = [ vs for vs in vsheets if vs.rows is UNLOADED ]
|
118
122
|
vd.sync(*vd.ensureLoaded(unloaded))
|
119
123
|
|
@@ -128,7 +132,7 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
|
|
128
132
|
break
|
129
133
|
|
130
134
|
if savefunc is None:
|
131
|
-
vd.fail(f'no function to save as {
|
135
|
+
vd.fail(f'no function to save as {", ".join(filetypes)}')
|
132
136
|
|
133
137
|
if confirm_overwrite:
|
134
138
|
vd.confirmOverwrite(givenpath)
|