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/graph.py
CHANGED
@@ -72,6 +72,8 @@ class InvertedCanvas(Canvas):
|
|
72
72
|
|
73
73
|
# provides axis labels, legend
|
74
74
|
class GraphSheet(InvertedCanvas):
|
75
|
+
rowtype = 'points'
|
76
|
+
|
75
77
|
def __init__(self, *names, **kwargs):
|
76
78
|
self.ylabel_maxw = 0
|
77
79
|
super().__init__(*names, **kwargs)
|
@@ -145,7 +147,7 @@ class GraphSheet(InvertedCanvas):
|
|
145
147
|
for char_x in range(0, self.plotwidth//2):
|
146
148
|
has_x_line = char_x in self.reflines_char_x.keys()
|
147
149
|
if has_x_line or has_y_line:
|
148
|
-
cattr = colors.
|
150
|
+
cattr = colors.color_graph_refline
|
149
151
|
if has_x_line:
|
150
152
|
ch = self.reflines_char_x[char_x]
|
151
153
|
# where two lines cross, draw the vertical line, not the horizontal one
|
@@ -268,11 +270,11 @@ class GraphSheet(InvertedCanvas):
|
|
268
270
|
txt = tick + txt
|
269
271
|
else:
|
270
272
|
right_margin = self.plotwidth - 1 - self.plotviewBox.xmax
|
271
|
-
if (
|
273
|
+
if (dispwidth(txt)+dispwidth(tick))*2 <= right_margin:
|
272
274
|
txt = tick + txt
|
273
275
|
else:
|
274
276
|
# shift rightmost label to be left of its tick
|
275
|
-
x -=
|
277
|
+
x -= dispwidth(txt)*2
|
276
278
|
if len(tick) == 0:
|
277
279
|
x += 1
|
278
280
|
txt = txt + tick
|
@@ -0,0 +1,84 @@
|
|
1
|
+
---
|
2
|
+
sheet: AggregatorSheet
|
3
|
+
---
|
4
|
+
# Aggregations like sum, mean, and distinct
|
5
|
+
|
6
|
+
Aggregators provide summary statistics for grouped rows.
|
7
|
+
|
8
|
+
The current aggregators include:
|
9
|
+
|
10
|
+
min smallest value in the group
|
11
|
+
max largest value in the group
|
12
|
+
avg/mean average value of the group
|
13
|
+
mode most frequently appearing value in group
|
14
|
+
median median value in the group
|
15
|
+
q3/q4/q5/q10 add quantile aggregators to group (e.g. q4 adds p25, p50, p75)
|
16
|
+
sum total summation of all numbers in the group
|
17
|
+
distinct number of distinct values in the group
|
18
|
+
count number of values in the group
|
19
|
+
keymax key of the row with the largest value in the group
|
20
|
+
list gathers values in column into a list
|
21
|
+
stdev standard deviation of values
|
22
|
+
|
23
|
+
## View a one-off aggregation of a column
|
24
|
+
|
25
|
+
- {help.commands.memo-aggregate}
|
26
|
+
|
27
|
+
## Create an aggregator column
|
28
|
+
|
29
|
+
Aggregated columns appear in the **Frequency Table** and **Pivot Table** (grouped sheets). Aggregated values will also appear at the bottom of their columns in the source sheet.
|
30
|
+
|
31
|
+
- {help.commands.aggregate-col}
|
32
|
+
|
33
|
+
Then aggregate the sheet with one of the grouping commands:
|
34
|
+
|
35
|
+
- {help.commands.freq-col}
|
36
|
+
- {help.commands.pivot}
|
37
|
+
|
38
|
+
Aggregators can be viewed and modified on the **Columns Sheet** in the `aggregators` column.
|
39
|
+
|
40
|
+
- {help.commands.columns-sheet}
|
41
|
+
|
42
|
+
## The Describe Sheet
|
43
|
+
|
44
|
+
To get a predefined set of summary statistics for every column in the sheet, use the **Describe Sheet* :
|
45
|
+
|
46
|
+
- {help.commands.describe-all}
|
47
|
+
|
48
|
+
## Examples
|
49
|
+
|
50
|
+
Sample input sheet **sales**:
|
51
|
+
|
52
|
+
date color price
|
53
|
+
---------- ----- -----
|
54
|
+
2024-09-01 R 30
|
55
|
+
2024-09-02 B 28
|
56
|
+
2024-09-03 R 100
|
57
|
+
2024-09-03 B 33
|
58
|
+
2024-09-03 B 99
|
59
|
+
|
60
|
+
1. Move to the `price` column
|
61
|
+
2. Set it to currency: [:keys]$[/key]
|
62
|
+
3. Quickly show average price
|
63
|
+
- [:keys]z+[/] (`memo-aggregate`) then enter 'avg'
|
64
|
+
4. Add an `sum` aggregator column:
|
65
|
+
- Press [:keys]+[/] (`aggregate-column`) then enter 'sum'
|
66
|
+
5. Move to the date column [:keys]gh[/]
|
67
|
+
6. Generate a **Frequency Table** by `date`
|
68
|
+
- [:keys]Shift-F[/] (`freq`)
|
69
|
+
|
70
|
+
|
71
|
+
date count price_sum
|
72
|
+
---------- ----- ---------
|
73
|
+
2024-09-03 3 232.00
|
74
|
+
2024-09-01 1 30.00
|
75
|
+
2024-09-02 1 28.00
|
76
|
+
|
77
|
+
## Creating new aggregator functions
|
78
|
+
|
79
|
+
To add a new aggregator to compute the range of the grouped values (max - min), add the following to `.visidatarc`:
|
80
|
+
|
81
|
+
[:code]vd.aggregator('range', lambda values: max(values) - min(values), 'range of values')[/]
|
82
|
+
|
83
|
+
The `values` parameter is a list of typed values from the column, with the function returning the aggregated value.
|
84
|
+
The new aggregator will now be available the next time VisiData is started.
|
visidata/guides/MacrosSheet.md
CHANGED
@@ -6,7 +6,7 @@ The basic usage is:
|
|
6
6
|
2. Execute a series of commands.
|
7
7
|
3. `m` again to complete the recording, and prompt for the keystroke or longname to bind it to.
|
8
8
|
|
9
|
-
The macro will then be executed
|
9
|
+
The macro will then be executed every time the provided keystroke or longname are used. Note: the Alt+keys and the function keys are left unbound; overriding other keys may conflict with existing bindings, now or in the future.
|
10
10
|
|
11
11
|
Executing a macro will the series of commands starting on the current row and column on the current sheet.
|
12
12
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Ranking
|
2
|
+
|
3
|
+
Ranking assigns numeric ranks to rows based on column values. VisiData provides two ranking approaches: sheet-wide ranking and group-based ranking.
|
4
|
+
|
5
|
+
## Sheet-wide ranking
|
6
|
+
|
7
|
+
[:keys]addcol-sheetrank[/] ranks all rows across the entire sheet.
|
8
|
+
|
9
|
+
Navigate to the column to rank by and execute [:keys]addcol-sheetrank[/]. A new column appears with ranks, where 1 indicates the best value.
|
10
|
+
|
11
|
+
**Example:**
|
12
|
+
```
|
13
|
+
Name | Salary | Salary_rank
|
14
|
+
Alice | 95000 | 1
|
15
|
+
Bob | 85000 | 2
|
16
|
+
Carol | 70000 | 3
|
17
|
+
```
|
18
|
+
|
19
|
+
## Group-based ranking
|
20
|
+
|
21
|
+
[:keys]addcol-aggregate[/] with [:code]rank[/] aggregator ranks rows within groups defined by key columns.
|
22
|
+
|
23
|
+
1. Set key columns with [:keys]![/] (defines groups)
|
24
|
+
2. Navigate to the column to rank by
|
25
|
+
3. Execute [:keys]addcol-aggregate[/]
|
26
|
+
4. Select [:code]rank[/] aggregator
|
27
|
+
|
28
|
+
**Example with Department as key column:**
|
29
|
+
```
|
30
|
+
Name | Department | Salary | Salary_rank
|
31
|
+
Alice | Engineering | 95000 | 1
|
32
|
+
Bob | Engineering | 85000 | 2
|
33
|
+
Carol | Sales | 70000 | 1
|
34
|
+
Dave | Sales | 65000 | 2
|
35
|
+
```
|
36
|
+
|
37
|
+
Alice and Carol both receive rank 1 as the highest earners in their respective departments.
|
38
|
+
|
39
|
+
## Sort direction
|
40
|
+
|
41
|
+
Ranking follows the current sort direction of the column:
|
42
|
+
- Ascending sort: lower values get better ranks
|
43
|
+
- Descending sort: higher values get better ranks
|
44
|
+
|
45
|
+
## Usage patterns
|
46
|
+
|
47
|
+
**Global comparison:** Use [:keys]addcol-sheetrank[/] to find overall leaders across all data.
|
48
|
+
|
49
|
+
**Category comparison:** Use [:keys]addcol-aggregate[/] + [:code]rank[/] to find leaders within each group defined by key columns.
|
50
|
+
|
51
|
+
**Multiple groupings:** Set multiple key columns before group ranking for complex categorization.
|
visidata/guides/TypesSheet.md
CHANGED
@@ -17,7 +17,7 @@ VisiData pre-set defaults for formatting types:
|
|
17
17
|
- `currency` removes non-numeric characters and parses the remainder as `float`.
|
18
18
|
- `date` parses dates into date object (shown as ISO8601).
|
19
19
|
- `vlen` formats the cell value to the length of the content
|
20
|
-
- `float` uses the decimal
|
20
|
+
- `float` uses the decimal separator, keeping two significant digits.
|
21
21
|
|
22
22
|
Change float precision with:
|
23
23
|
- {help.commands.setcol-precision-less}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
---
|
2
|
+
sheet: Sheet
|
3
|
+
---
|
4
|
+
# Create a window over consecutive rows
|
5
|
+
|
6
|
+
Window functions enable computations that relate the current window to surrounding rows, like cumulative sum, rolling averages or lead/lag computations.
|
7
|
+
|
8
|
+
{help.commands.addcol-window}
|
9
|
+
|
10
|
+
With large window sizes, [:code]g'[/] (`freeze-sheet`) to calculate all cells and copy the entire sheet into a new source sheet, which will conserve CPU.
|
11
|
+
|
12
|
+
## Examples
|
13
|
+
|
14
|
+
date color price
|
15
|
+
---------- ----- -----
|
16
|
+
2024-09-01 R 30
|
17
|
+
2024-09-02 B 28
|
18
|
+
2024-09-03 R 100
|
19
|
+
2024-09-03 B 33
|
20
|
+
2024-09-03 B 99
|
21
|
+
|
22
|
+
|
23
|
+
1. [:keys]#[/] (`type-int`) on the **price** column to type as int.
|
24
|
+
2. [:keys]w[/] (`addcol-window`) on the **price** column, followed by `1 2`, to create a window consisting of 4 rows: 1 row before the current row, and 2 rows after.
|
25
|
+
3. To create a moving average of the values in the window, add a new column with a python expression: [:keys]=[/] (`addcol-expr`)
|
26
|
+
followed by `sum(price_window)/len(price_window)`
|
27
|
+
|
28
|
+
date color price price_window sum(price_window)/len(price_window)
|
29
|
+
---------- ----- ----- ------------------- -----------------------------------
|
30
|
+
2024-09-01 R 38 [4] ; 38; 28; 100 41.5
|
31
|
+
2024-09-02 B 28 [4] 38; 28; 100; 33 49.75
|
32
|
+
2024-09-03 R 100 [4] 28; 100; 33; 99 65.0
|
33
|
+
2024-09-03 B 33 [4] 100; 33; 99; 58.0
|
34
|
+
2024-09-03 B 99 [4] 33; 99; ; 33.0
|
35
|
+
|
36
|
+
|
37
|
+
## Workflows
|
38
|
+
|
39
|
+
### Create a cumulative sum
|
40
|
+
|
41
|
+
1. Set the before window size to the total number of rows in the table, and the after rows to 0. In the above example that would be `w 5 0` (`addcol-window`).
|
42
|
+
2. Add an expression ([:keys]=[/] (`addcol-expr`) of `sum(window)` where `window` is the name of the window function column.
|
43
|
+
|
44
|
+
### Compute the change between rows
|
45
|
+
|
46
|
+
1. `w 1 0` on the `foo` column to create a window function of size 1 before and 0 after.
|
47
|
+
2. Add a python expression. The window function column is 'foo_window':
|
48
|
+
`=foo_window[1] - foo_window[0] if len(foo_window) > 1 else None`
|
49
|
+
|
visidata/help.py
CHANGED
@@ -135,7 +135,7 @@ class HelpPane:
|
|
135
135
|
self.scr.erase()
|
136
136
|
self.scr.box()
|
137
137
|
self.amgr.draw(self.scr, y=1, x=2, **kwargs)
|
138
|
-
self.scr.
|
138
|
+
self.scr.noutrefresh()
|
139
139
|
|
140
140
|
|
141
141
|
@VisiData.api
|
@@ -176,13 +176,12 @@ BaseSheet.bindkey('gKEY_BACKSPACE', 'sysopen-help')
|
|
176
176
|
HelpSheet.addCommand(None, 'exec-command', 'quit(sheet); draw_all(); activeStack[0].execCommand(cursorRow.longname)', 'execute command on undersheet')
|
177
177
|
BaseSheet.addCommand(None, 'open-tutorial-visidata', 'launchBrowser("https://jsvine.github.io/intro-to-visidata/")', 'open https://jsvine.github.io/intro-to-visidata/')
|
178
178
|
|
179
|
-
vd.addMenuItem("Help", "VisiData tutorial", 'open-tutorial-visidata')
|
180
|
-
vd.addMenuItem("Help", 'Sheet commands', 'help-commands')
|
181
|
-
vd.addMenuItem("Help", 'All commands', 'help-commands-all')
|
182
179
|
|
183
180
|
vd.addGlobals(HelpSheet=HelpSheet)
|
184
181
|
|
185
182
|
vd.addMenuItems('''
|
183
|
+
Help > VisiData tutorial > open-tutorial-visidata
|
184
|
+
Help > All commands > help-commands-all
|
186
185
|
Help > Quick reference > sysopen-help
|
187
186
|
Help > Command list > help-commands
|
188
187
|
''')
|
visidata/indexsheet.py
CHANGED
@@ -99,7 +99,7 @@ BaseSheet.addCommand('g<', 'open-source-prev', 'vd.replace(openSource(source.nex
|
|
99
99
|
IndexSheet.addCommand('g^R', 'reload-selected', 'reloadSheets(selectedRows or rows)', 'reload all selected sheets')
|
100
100
|
|
101
101
|
# when diving into a sheet, remove the index unless it is precious
|
102
|
-
|
102
|
+
IndexSheet.addCommand('gC', 'columns-selected', 'vd.push(ColumnsSheet("all_columns", source=selectedRows))', 'open Columns Sheet with all visible columns from selected sheets')
|
103
103
|
IndexSheet.addCommand('^C', 'cancel-row', 'cancelThread(*cursorRow.currentThreads)', 'abort async thread for current sheet')
|
104
104
|
IndexSheet.addCommand('gz^C', 'cancel-rows', 'for vs in selectedRows: cancelThread(*vs.currentThreads)', 'abort async threads for selected sheets')
|
105
105
|
SheetsSheet.addCommand('Enter', 'open-row', 'dest=cursorRow; vd.sheets.remove(sheet) if not sheet.precious else None; vd.push(openRow(dest))', 'open sheet referenced in current row')
|
visidata/loaders/_pandas.py
CHANGED
@@ -28,7 +28,7 @@ def save_dta(vd, p, *sheets):
|
|
28
28
|
vs = sheets[0]
|
29
29
|
|
30
30
|
columns = [col.name for col in vs.visibleCols]
|
31
|
-
|
31
|
+
|
32
32
|
# Get data types
|
33
33
|
types = list()
|
34
34
|
dispvals = next(vs.iterdispvals(format=True))
|
@@ -154,6 +154,8 @@ class PandasSheet(Sheet):
|
|
154
154
|
readfunc = self.read_tsv
|
155
155
|
elif filetype == 'jsonl':
|
156
156
|
readfunc = partial(pd.read_json, lines=True)
|
157
|
+
elif filetype == 'hdf5':
|
158
|
+
readfunc = partial(pd.read_hdf, lines=True)
|
157
159
|
else:
|
158
160
|
readfunc = getattr(pd, 'read_'+filetype) or vd.error('no pandas.read_'+filetype)
|
159
161
|
# readfunc() handles binary and text open()
|
visidata/loaders/archive.py
CHANGED
@@ -5,18 +5,21 @@ import datetime
|
|
5
5
|
from visidata.loaders import unzip_http
|
6
6
|
|
7
7
|
from visidata import vd, VisiData, asyncthread, Sheet, Progress, Menu, options
|
8
|
-
from visidata import ColumnAttr, Column, Path
|
8
|
+
from visidata import ColumnAttr, Column, Path, filesize
|
9
9
|
from visidata.type_date import date
|
10
10
|
|
11
11
|
@VisiData.api
|
12
12
|
def guess_zip(vd, p):
|
13
13
|
if not p.is_url() and zipfile.is_zipfile(p.open_bytes()):
|
14
|
-
return dict(filetype='zip')
|
14
|
+
return dict(filetype='zip', _likelihood=10)
|
15
15
|
|
16
16
|
@VisiData.api
|
17
17
|
def guess_tar(vd, p):
|
18
|
+
# an empty file will pass is_tarfile(), but can't be opened by tarfile.open()
|
19
|
+
if filesize(p) == 0:
|
20
|
+
return None
|
18
21
|
if tarfile.is_tarfile(p.open_bytes()):
|
19
|
-
return dict(filetype='tar')
|
22
|
+
return dict(filetype='tar', _likelihood=10)
|
20
23
|
|
21
24
|
@VisiData.api
|
22
25
|
def open_zip(vd, p):
|
visidata/loaders/csv.py
CHANGED
@@ -26,7 +26,11 @@ def guess_csv(vd, p):
|
|
26
26
|
|
27
27
|
for csvopt in dir(dialect):
|
28
28
|
if not csvopt.startswith('_'):
|
29
|
-
|
29
|
+
v = getattr(dialect, csvopt)
|
30
|
+
optname = 'csv_'+csvopt
|
31
|
+
r[optname] = v
|
32
|
+
if vd.options.get(optname) != v:
|
33
|
+
vd.warning(f'guessed option {optname}={v}')
|
30
34
|
|
31
35
|
return r
|
32
36
|
|
visidata/loaders/eml.py
CHANGED
visidata/loaders/f5log.py
CHANGED
@@ -14,7 +14,7 @@ Regex: (?:/Common/)(?P<site>[^-]+)-(?P<vstype>[^-]+)-(?P<application>[^-]+)
|
|
14
14
|
|
15
15
|
/Common/newyork-www-banking1
|
16
16
|
|
17
|
-
... | site | vstype |
|
17
|
+
... | site | vstype | application | ...
|
18
18
|
... | newyork | www | banking1 | ...
|
19
19
|
|
20
20
|
Adding to .visidatarc
|
@@ -313,7 +313,7 @@ class F5LogSheet(Sheet):
|
|
313
313
|
cmd_data = msg[cmd_data_loc + 1 :]
|
314
314
|
# split the message and the command
|
315
315
|
msg, cmd = msg[:cmd_data_loc].rsplit(" ", maxsplit=1)
|
316
|
-
# strip off the
|
316
|
+
# strip off the trailing " -" from the message
|
317
317
|
msg = msg[:-2]
|
318
318
|
object = cmd_data.split('"', maxsplit=2)
|
319
319
|
if len(object) == 3:
|
visidata/loaders/fec.py
CHANGED
@@ -106,22 +106,19 @@ class DiveSheet(Sheet):
|
|
106
106
|
self.addRow(item)
|
107
107
|
|
108
108
|
except Exception as e:
|
109
|
-
vd.warning("Can't dive on lists with
|
109
|
+
vd.warning("Can't dive on lists with heterogeneous item types.")
|
110
110
|
return False
|
111
111
|
|
112
112
|
def openRow(self, row):
|
113
113
|
if self.is_keyvalue:
|
114
114
|
cell = row["value"]
|
115
|
-
name = vd.joinSheetnames(self.name, row["key"])
|
116
|
-
|
117
115
|
if isinstance(cell, (list, dict)):
|
118
|
-
vs = self.__class__(name, source = cell)
|
116
|
+
vs = self.__class__(self.name, row["key"], source = cell)
|
119
117
|
else:
|
120
118
|
vd.warning("Nothing to dive into.")
|
121
119
|
return
|
122
120
|
else:
|
123
|
-
|
124
|
-
vs = self.__class__(name, source = self.row)
|
121
|
+
vs = self.__class__(self.name, "row", source = self.row)
|
125
122
|
|
126
123
|
success = vs.reload()
|
127
124
|
if success == False:
|
@@ -174,7 +171,7 @@ class FECScheduleSheet(Sheet):
|
|
174
171
|
|
175
172
|
for schedule_name in self.source.keys():
|
176
173
|
vs = FECItemizationSheet(
|
177
|
-
|
174
|
+
self.name, schedule_name,
|
178
175
|
schedule_name = schedule_name,
|
179
176
|
source = self.source[schedule_name],
|
180
177
|
size = len(self.source[schedule_name]),
|
@@ -225,7 +222,7 @@ class FECFiling(Sheet):
|
|
225
222
|
] else dict
|
226
223
|
|
227
224
|
vs = cls(
|
228
|
-
|
225
|
+
self.name, component_name,
|
229
226
|
component_name = component_name,
|
230
227
|
source = source_cls(),
|
231
228
|
size = 0,
|
@@ -270,7 +267,7 @@ class FECFiling(Sheet):
|
|
270
267
|
if form_type not in sheet_row.source:
|
271
268
|
sheet_row.source[form_type] = [ ]
|
272
269
|
subsheet = FECItemizationSheet(
|
273
|
-
|
270
|
+
sheet_row.name, form_type,
|
274
271
|
schedule_name = form_type,
|
275
272
|
source = [ ],
|
276
273
|
size = 0,
|
visidata/loaders/fixed_width.py
CHANGED
@@ -110,3 +110,5 @@ def save_fixed(vd, p, *vsheets):
|
|
110
110
|
for col, val in dispvals.items():
|
111
111
|
fp.write(('{0:%s%s.%s} ' % ('>' if vd.isNumeric(col) else '<', widths[col], widths[col])).format(val))
|
112
112
|
fp.write('\n')
|
113
|
+
|
114
|
+
FixedWidthColumnsSheet.options.null_value = '' # the file format cannot contain None, so use empty string instead
|
visidata/loaders/hdf5.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
from visidata import VisiData, vd, Sheet, Path, Column, ItemColumn, BaseSheet
|
1
|
+
from visidata import VisiData, vd, Sheet, Path, Column, ItemColumn, BaseSheet, anytype
|
2
|
+
from itertools import chain
|
2
3
|
|
3
4
|
@VisiData.api
|
4
5
|
def open_h5(vd, p):
|
@@ -6,8 +7,11 @@ def open_h5(vd, p):
|
|
6
7
|
|
7
8
|
VisiData.open_hdf5 = VisiData.open_h5
|
8
9
|
|
10
|
+
vd.option('hdf5_matrix_enumerate', False, 'enumerate matrix rows and columns')
|
11
|
+
|
9
12
|
class Hdf5ObjSheet(Sheet):
|
10
13
|
'Support sheets in HDF5 format.'
|
14
|
+
|
11
15
|
def iterload(self):
|
12
16
|
h5py = vd.importExternal('h5py')
|
13
17
|
source = self.source
|
@@ -26,25 +30,39 @@ class Hdf5ObjSheet(Sheet):
|
|
26
30
|
for k, v in source.items():
|
27
31
|
yield Hdf5ObjSheet(self.name, k, source=v)
|
28
32
|
elif isinstance(source, h5py.Dataset):
|
29
|
-
if len(source.shape)
|
33
|
+
if len(source.shape)==1:
|
30
34
|
if source.dtype.names:
|
31
|
-
for i, colname in enumerate(source.dtype.
|
32
|
-
|
35
|
+
for i, (colname, fmt, *_) in enumerate(source.dtype.descr):
|
36
|
+
if not colname:
|
37
|
+
colname = f"col{i}"
|
38
|
+
ctype = _guess_type(fmt)
|
39
|
+
self.addColumn(ItemColumn(colname, i, type=ctype))
|
33
40
|
yield from source # copy
|
34
41
|
else:
|
35
42
|
self.addColumn(ItemColumn(source.name, 0))
|
36
43
|
for v in source:
|
37
44
|
yield [v]
|
38
|
-
elif len(source.shape)
|
45
|
+
elif len(source.shape)==2:
|
46
|
+
matrix_enumerate = bool(self.options.hdf5_matrix_enumerate)
|
47
|
+
|
39
48
|
ncols = source.shape[1]
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
ctype = _guess_type(source.dtype.descr[0][1])
|
50
|
+
|
51
|
+
if matrix_enumerate:
|
52
|
+
self.addColumn(ItemColumn("row", 0, width=8, keycol=1, type=int), index=0)
|
53
|
+
for i in range(ncols):
|
54
|
+
self.addColumn(ItemColumn(f'col{i}', i+1, width=8, type=ctype), index=i+1)
|
55
|
+
self.recalc()
|
56
|
+
yield from list(list((chain((i,), row))) for i, row in enumerate(source))
|
57
|
+
else:
|
58
|
+
for i in range(ncols):
|
59
|
+
self.addColumn(ItemColumn('', i, width=8, type=ctype), index=i)
|
60
|
+
self.recalc()
|
61
|
+
yield from source # copy
|
44
62
|
else:
|
45
63
|
vd.fail('too many dimensions in shape %s' % str(source.shape))
|
46
64
|
else:
|
47
|
-
vd.fail(
|
65
|
+
vd.fail(f"too many dimensions in shape {source.shape}")
|
48
66
|
|
49
67
|
|
50
68
|
def openRow(self, row):
|
@@ -59,5 +77,11 @@ class Hdf5ObjSheet(Sheet):
|
|
59
77
|
if isinstance(row, numpy.ndarray):
|
60
78
|
return NpySheet(None, npy=row)
|
61
79
|
|
80
|
+
def _guess_type(fmt):
|
81
|
+
if 'i' in fmt or 'u' in fmt:
|
82
|
+
return int
|
83
|
+
elif 'f' in fmt:
|
84
|
+
return float
|
85
|
+
return anytype
|
62
86
|
|
63
87
|
Hdf5ObjSheet.addCommand('A', 'dive-metadata', 'vd.push(SheetDict(cursorRow.name + "_attrs", source=cursorRow.attrs))', 'open metadata sheet for object referenced in current row')
|
visidata/loaders/npy.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
from visidata import VisiData, vd, Sheet, date, anytype, options, Column,
|
1
|
+
from visidata import VisiData, vd, Sheet, date, anytype, options, Column, ItemColumn, Progress, vlen, PyobjSheet, TypedWrapper
|
2
|
+
from itertools import chain
|
2
3
|
|
3
4
|
'Loaders for .npy and .npz. Save to .npy. Depends on the zip loader.'
|
4
5
|
|
@@ -11,44 +12,74 @@ def open_npz(vd, p):
|
|
11
12
|
return NpzSheet(p.base_stem, source=p)
|
12
13
|
|
13
14
|
vd.option('npy_allow_pickle', False, 'numpy allow unpickling objects (unsafe)')
|
15
|
+
vd.option('npy_matrix_enumerate', False, 'enumerate matrix rows and columns')
|
14
16
|
|
15
17
|
class NpySheet(Sheet):
|
16
18
|
def iterload(self):
|
17
19
|
numpy = vd.importExternal('numpy')
|
18
20
|
if not hasattr(self, 'npy'):
|
19
|
-
self.npy = numpy.load(str(self.source), encoding='bytes',
|
21
|
+
self.npy = numpy.load(str(self.source), encoding='bytes', allow_pickle=bool(self.options.npy_allow_pickle))
|
20
22
|
self.reloadCols()
|
21
|
-
|
23
|
+
transpose = len(self.npy.shape)==1 and not bool(self.npy.dtype.names)
|
24
|
+
if transpose:
|
25
|
+
source = self.npy[:,None]
|
26
|
+
else:
|
27
|
+
source = self.npy
|
28
|
+
|
29
|
+
nrows = len(self.npy)
|
30
|
+
|
31
|
+
if self.options.npy_matrix_enumerate:
|
32
|
+
source = list(list((chain((i,), row))) for i, row in enumerate(source))
|
33
|
+
|
34
|
+
yield from Progress(source, nrows)
|
35
|
+
|
22
36
|
|
23
37
|
def reloadCols(self):
|
24
38
|
self.columns = []
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
39
|
+
if len(self.npy.shape)==1:
|
40
|
+
for i, (colname, fmt, *shape) in enumerate(self.npy.dtype.descr):
|
41
|
+
if not colname:
|
42
|
+
colname = f"col{i}"
|
43
|
+
ctype = _guess_type(shape, fmt)
|
44
|
+
if ctype=="time":
|
45
|
+
self.addColumn(Column(colname, type=date, getter=lambda c,r,i=i: str(r[i])))
|
46
|
+
continue
|
47
|
+
self.addColumn(ItemColumn(colname, i, type=ctype))
|
48
|
+
elif len(self.npy.shape)==2:
|
49
|
+
ncols = self.npy.shape[1]
|
50
|
+
ctype = _guess_type(None, self.npy.dtype.descr[0][1])
|
51
|
+
|
52
|
+
if self.options.npy_matrix_enumerate:
|
53
|
+
self.addColumn(ItemColumn("row", 0, width=8, keycol=1, type=int), index=0)
|
54
|
+
for i in range(ncols):
|
55
|
+
self.addColumn(ItemColumn(f'col{i}', i+1, width=8, type=ctype), index=i+1)
|
37
56
|
else:
|
38
|
-
|
39
|
-
|
40
|
-
|
57
|
+
for i in range(ncols):
|
58
|
+
self.addColumn(ItemColumn('', i, width=8, type=ctype), index=i)
|
59
|
+
else:
|
60
|
+
vd.fail(f"too many dimensions in shape {self.npy.shape}")
|
61
|
+
|
62
|
+
def _guess_type(shape, fmt):
|
63
|
+
if shape:
|
64
|
+
return anytype
|
65
|
+
elif 'M' in fmt:
|
66
|
+
return "time"
|
67
|
+
elif 'i' in fmt or 'u' in fmt:
|
68
|
+
return int
|
69
|
+
elif 'f' in fmt:
|
70
|
+
return float
|
71
|
+
return anytype
|
41
72
|
|
42
73
|
class NpzSheet(vd.ZipSheet):
|
43
74
|
# rowdef: tuple(tablename, table)
|
44
75
|
columns = [
|
45
|
-
|
46
|
-
|
76
|
+
ItemColumn('name', 0),
|
77
|
+
ItemColumn('length', 1, type=vlen),
|
47
78
|
]
|
48
79
|
|
49
80
|
def iterload(self):
|
50
81
|
numpy = vd.importExternal('numpy')
|
51
|
-
self.npz = numpy.load(str(self.source), encoding='bytes',
|
82
|
+
self.npz = numpy.load(str(self.source), encoding='bytes', allow_pickle=bool(self.options.npy_allow_pickle))
|
52
83
|
yield from Progress(self.npz.items())
|
53
84
|
|
54
85
|
def openRow(self, row):
|
@@ -74,7 +105,7 @@ def save_npy(vd, p, sheet):
|
|
74
105
|
elif col.type in vd.numericTypes:
|
75
106
|
dt = 'f8'
|
76
107
|
else: # if col.type in (str, anytype):
|
77
|
-
width = col.
|
108
|
+
width = col.getMaxDataWidth(sheet.rows)
|
78
109
|
dt = 'U'+str(width)
|
79
110
|
dtype.append((col.name, dt))
|
80
111
|
|
@@ -94,4 +125,4 @@ def save_npy(vd, p, sheet):
|
|
94
125
|
|
95
126
|
arr = np.array(data, dtype=dtype)
|
96
127
|
with p.open_bytes(mode='w') as outf:
|
97
|
-
np.save(outf, arr,
|
128
|
+
np.save(outf, arr, allow_pickle=bool(sheet.options.npy_allow_pickle))
|
visidata/loaders/orgmode.py
CHANGED
@@ -55,8 +55,8 @@ def encode_date(dt=None):
|
|
55
55
|
|
56
56
|
|
57
57
|
class OrgContentsColumn(Column):
|
58
|
-
def setValue(self, row, v):
|
59
|
-
super().setValue(row, v)
|
58
|
+
def setValue(self, row, v, setModified=True):
|
59
|
+
super().setValue(row, v, setModified=setModified)
|
60
60
|
orgmode_parse_into(row, v)
|
61
61
|
|
62
62
|
def putValue(self, row, v):
|
@@ -88,6 +88,7 @@ def sectionize(lines):
|
|
88
88
|
|
89
89
|
def orgmode_parse(all_lines):
|
90
90
|
root = parent = OrgSheet().newRow()
|
91
|
+
root.orig_contents = ''
|
91
92
|
for linenum, lines in sectionize(all_lines):
|
92
93
|
section = OrgSheet().newRow()
|
93
94
|
|
@@ -83,6 +83,10 @@ class PandasFreqTableSheet(PivotSheet):
|
|
83
83
|
self.source._selectByILoc(row.sourcerows.mask_iloc, selected=False)
|
84
84
|
return super().unselectRow(row)
|
85
85
|
|
86
|
+
def addUndoSelection(self):
|
87
|
+
self.source.addUndoSelection()
|
88
|
+
super().addUndoSelection()
|
89
|
+
|
86
90
|
def updateLargest(self, grouprow):
|
87
91
|
self.largest = max(self.largest, len(grouprow.sourcerows))
|
88
92
|
|