visidata 3.0.2__py3-none-any.whl → 3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- visidata/__init__.py +12 -10
- visidata/_input.py +208 -202
- visidata/_open.py +4 -1
- visidata/_types.py +4 -3
- visidata/aggregators.py +88 -39
- visidata/apps/vdsql/_ibis.py +7 -11
- visidata/apps/vdsql/clickhouse.py +2 -2
- visidata/apps/vdsql/snowflake.py +1 -1
- visidata/apps/vgit/status.py +1 -1
- visidata/basesheet.py +11 -4
- visidata/canvas.py +54 -20
- visidata/clipboard.py +13 -6
- visidata/cliptext.py +7 -6
- visidata/cmdlog.py +40 -27
- visidata/column.py +14 -49
- visidata/ddw/regex.ddw +3 -2
- visidata/deprecated.py +14 -2
- visidata/desktop/visidata.desktop +2 -2
- visidata/editor.py +1 -0
- visidata/errors.py +1 -1
- visidata/experimental/sort_selected.py +54 -0
- visidata/expr.py +69 -18
- visidata/features/change_precision.py +1 -3
- visidata/features/cmdpalette.py +17 -2
- visidata/features/colorsheet.py +1 -1
- visidata/features/dedupe.py +3 -3
- visidata/features/go_col.py +71 -0
- visidata/features/graph_seaborn.py +1 -1
- visidata/features/join.py +20 -10
- visidata/features/layout.py +16 -3
- visidata/features/ping.py +16 -12
- visidata/features/regex.py +5 -5
- visidata/features/status_source.py +3 -1
- visidata/features/sysedit.py +1 -1
- visidata/features/transpose.py +2 -1
- visidata/features/type_ipaddr.py +2 -4
- visidata/features/unfurl.py +1 -0
- visidata/form.py +2 -2
- visidata/freqtbl.py +16 -11
- visidata/fuzzymatch.py +1 -0
- visidata/graph.py +163 -12
- visidata/guide.py +57 -24
- visidata/guides/ClipboardGuide.md +48 -0
- visidata/guides/ColumnsGuide.md +52 -0
- visidata/guides/CommandsSheet.md +28 -0
- visidata/guides/DirSheet.md +34 -0
- visidata/guides/ErrorsSheet.md +17 -0
- visidata/guides/FrequencyTable.md +42 -0
- visidata/guides/GrepSheet.md +28 -0
- visidata/guides/JsonSheet.md +38 -0
- visidata/guides/MacrosSheet.md +19 -0
- visidata/guides/MeltGuide.md +52 -0
- visidata/guides/MemorySheet.md +7 -0
- visidata/guides/MenuGuide.md +26 -0
- visidata/guides/ModifyGuide.md +38 -0
- visidata/guides/PivotGuide.md +71 -0
- visidata/guides/RegexGuide.md +107 -0
- visidata/guides/SelectionGuide.md +44 -0
- visidata/guides/SlideGuide.md +26 -0
- visidata/guides/SortGuide.md +0 -0
- visidata/guides/SplitpaneGuide.md +15 -0
- visidata/guides/TypesSheet.md +43 -0
- visidata/guides/XsvGuide.md +36 -0
- visidata/help.py +6 -6
- visidata/hint.py +2 -1
- visidata/indexsheet.py +2 -2
- visidata/interface.py +13 -14
- visidata/keys.py +4 -1
- visidata/loaders/api_airtable.py +1 -1
- visidata/loaders/archive.py +1 -1
- visidata/loaders/csv.py +9 -5
- visidata/loaders/eml.py +11 -6
- visidata/loaders/f5log.py +1 -0
- visidata/loaders/fec.py +18 -42
- visidata/loaders/fixed_width.py +19 -3
- visidata/loaders/grep.py +121 -0
- visidata/loaders/html.py +1 -0
- visidata/loaders/http.py +6 -1
- visidata/loaders/json.py +22 -4
- visidata/loaders/jsonla.py +8 -2
- visidata/loaders/mailbox.py +1 -0
- visidata/loaders/markdown.py +25 -6
- visidata/loaders/msgpack.py +19 -0
- visidata/loaders/npy.py +0 -1
- visidata/loaders/odf.py +18 -4
- visidata/loaders/orgmode.py +1 -1
- visidata/loaders/rec.py +6 -4
- visidata/loaders/sas.py +11 -4
- visidata/loaders/scrape.py +0 -1
- visidata/loaders/texttables.py +2 -0
- visidata/loaders/tsv.py +24 -7
- visidata/loaders/unzip_http.py +127 -3
- visidata/loaders/vds.py +4 -0
- visidata/loaders/vdx.py +1 -1
- visidata/loaders/xlsx.py +5 -0
- visidata/loaders/xml.py +2 -1
- visidata/macros.py +14 -31
- visidata/main.py +14 -13
- visidata/mainloop.py +14 -6
- visidata/man/vd.1 +72 -39
- visidata/man/vd.txt +72 -41
- visidata/memory.py +15 -4
- visidata/menu.py +14 -3
- visidata/metasheets.py +5 -6
- visidata/modify.py +4 -4
- visidata/mouse.py +2 -0
- visidata/movement.py +14 -28
- visidata/optionssheet.py +3 -5
- visidata/path.py +59 -37
- visidata/pivot.py +8 -5
- visidata/pyobj.py +63 -9
- visidata/save.py +16 -9
- visidata/search.py +4 -4
- visidata/selection.py +10 -56
- visidata/settings.py +37 -35
- visidata/sheets.py +186 -108
- visidata/shell.py +22 -12
- visidata/sidebar.py +71 -16
- visidata/sort.py +21 -6
- visidata/statusbar.py +42 -5
- visidata/stored_list.py +5 -2
- visidata/tests/conftest.py +1 -0
- visidata/tests/test_commands.py +9 -1
- visidata/tests/test_completer.py +18 -0
- visidata/tests/test_edittext.py +3 -2
- visidata/text_source.py +7 -4
- visidata/textsheet.py +20 -6
- visidata/themes/ascii8.py +9 -6
- visidata/themes/asciimono.py +14 -4
- visidata/threads.py +13 -3
- visidata/tuiwin.py +5 -1
- visidata/type_currency.py +1 -2
- visidata/type_date.py +6 -1
- visidata/undo.py +10 -5
- visidata/utils.py +9 -3
- visidata/vdobj.py +21 -1
- visidata/wrappers.py +9 -1
- {visidata-3.0.2.data → visidata-3.1.data}/data/share/applications/visidata.desktop +2 -2
- {visidata-3.0.2.data → visidata-3.1.data}/data/share/man/man1/vd.1 +72 -39
- {visidata-3.0.2.data → visidata-3.1.data}/data/share/man/man1/visidata.1 +72 -39
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/METADATA +24 -6
- visidata-3.1.dist-info/RECORD +284 -0
- visidata-3.0.2.dist-info/RECORD +0 -258
- {visidata-3.0.2.data → visidata-3.1.data}/scripts/vd +0 -0
- {visidata-3.0.2.data → visidata-3.1.data}/scripts/vd2to3.vdx +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/LICENSE.gpl3 +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/WHEEL +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/entry_points.txt +0 -0
- {visidata-3.0.2.dist-info → visidata-3.1.dist-info}/top_level.txt +0 -0
visidata/metasheets.py
CHANGED
@@ -29,10 +29,9 @@ Some new commands on this sheet operate on all selected columns on the source sh
|
|
29
29
|
- {help.commands.type_int_selected}
|
30
30
|
- or `g` with any standard type to set type of selected source columns to that type
|
31
31
|
|
32
|
-
|
32
|
+
Other commands (not specific to Columns Sheet):
|
33
33
|
|
34
34
|
- {help.commands.setcol_input}
|
35
|
-
{sheet.help_columns}
|
36
35
|
'''
|
37
36
|
|
38
37
|
class ValueColumn(Column):
|
@@ -47,15 +46,15 @@ Some new commands on this sheet operate on all selected columns on the source sh
|
|
47
46
|
ColumnAttr('name', help='rename the column on the source sheet'),
|
48
47
|
ColumnAttr('keycol', type=int, width=0),
|
49
48
|
ColumnAttr('width', type=int, help='set the column width (`0` to hide completely)'),
|
50
|
-
ColumnAttr('height', type=int,
|
49
|
+
ColumnAttr('height', type=int, disp_expert=1, help='set a maximum height for the row, if this column will fill it'),
|
51
50
|
ColumnAttr('hoffset', type=int, width=0),
|
52
51
|
ColumnAttr('voffset', type=int, width=0),
|
53
52
|
ColumnAttr('type', 'typestr', help='convert all values to a specific type'),
|
54
53
|
ColumnAttr('fmtstr', help='use a custom format string, either C-style (`%0.4f`) or Python-style (`{{:0.4f}}`)'),
|
55
|
-
ColumnAttr('formatter',
|
56
|
-
ColumnAttr('displayer',
|
54
|
+
ColumnAttr('formatter', disp_expert=1, help='use a custom format function (**{col.help_formatters}**)'),
|
55
|
+
ColumnAttr('displayer', disp_expert=1, help='use a custom display function (**{col.help_displayers}**)'),
|
57
56
|
ValueColumn('value', help='change the value of this cell on the source sheet'),
|
58
|
-
ColumnAttr('expr',
|
57
|
+
ColumnAttr('expr', disp_expert=1, help='change the main column parameter'),
|
59
58
|
ColumnAttr('ncalcs', type=int, width=0, cache=False),
|
60
59
|
ColumnAttr('maxtime', type=float, width=0, cache=False),
|
61
60
|
ColumnAttr('totaltime', type=float, width=0, cache=False),
|
visidata/modify.py
CHANGED
@@ -65,7 +65,7 @@ def rowAdded(self, row):
|
|
65
65
|
self._deferredAdds[self.rowid(row)] = row
|
66
66
|
def _undoRowAdded(sheet, row):
|
67
67
|
if sheet.rowid(row) not in sheet._deferredAdds:
|
68
|
-
vd.warning('cannot undo
|
68
|
+
vd.warning('cannot undo row addition after a commit')
|
69
69
|
return
|
70
70
|
del sheet._deferredAdds[sheet.rowid(row)]
|
71
71
|
vd.addUndo(_undoRowAdded, self, row)
|
@@ -87,7 +87,7 @@ def cellChanged(col, row, val):
|
|
87
87
|
if oldval == col.getSourceValue(row):
|
88
88
|
# if we have reached the original value, remove from defermods entirely
|
89
89
|
if col.sheet.rowid(row) not in col.sheet._deferredMods:
|
90
|
-
vd.warning('cannot undo
|
90
|
+
vd.warning('cannot undo cell change after a commit')
|
91
91
|
return
|
92
92
|
del col.sheet._deferredMods[col.sheet.rowid(row)]
|
93
93
|
else:
|
@@ -105,7 +105,7 @@ def rowDeleted(self, row):
|
|
105
105
|
self.unselectRow(row)
|
106
106
|
def _undoRowDeleted(sheet, row):
|
107
107
|
if sheet.rowid(row) not in sheet._deferredDels:
|
108
|
-
vd.warning('cannot undo
|
108
|
+
vd.warning('cannot undo row deletion after a commit')
|
109
109
|
return
|
110
110
|
del sheet._deferredDels[sheet.rowid(row)]
|
111
111
|
vd.addUndo(_undoRowDeleted, self, row)
|
@@ -330,7 +330,7 @@ def new_rows(sheet, n):
|
|
330
330
|
return [sheet.newRow() for i in range(n)]
|
331
331
|
|
332
332
|
Sheet.addCommand('a', 'add-row', 'addRows([newRow()], index=cursorRowIndex); cursorDown(1)', 'append a blank row')
|
333
|
-
Sheet.addCommand('ga', 'add-rows', 'n=int(input("add rows: ", value=1)); addRows(new_rows(n), index=cursorRowIndex); cursorDown(1)', 'append N blank rows')
|
333
|
+
Sheet.addCommand('ga', 'add-rows', 'n=int(input("add # rows: ", value=1)); addRows(new_rows(n), index=cursorRowIndex); cursorDown(1)', 'append N blank rows')
|
334
334
|
Sheet.addCommand('za', 'addcol-new', 'addColumnAtCursor(SettableColumn(input("column name: ")))', 'append an empty column')
|
335
335
|
Sheet.addCommand('gza', 'addcol-bulk', 'addColumnAtCursor(*(SettableColumn() for c in range(int(input("add columns: ")))))', 'append N empty columns')
|
336
336
|
|
visidata/mouse.py
CHANGED
@@ -62,6 +62,8 @@ def parseMouse(vd, **kwargs):
|
|
62
62
|
keystroke = clicktype + curses.mouseEvents.get(bstate, str(bstate))
|
63
63
|
ret = AttrDict(keystroke=keystroke, y=y, x=x, found=[])
|
64
64
|
for winname, winscr in kwargs.items():
|
65
|
+
if not winscr:
|
66
|
+
continue
|
65
67
|
px, py = vd.getrootxy(winscr)
|
66
68
|
mh, mw = winscr.getmaxyx()
|
67
69
|
if py <= y < py+mh and px <= x < px+mw:
|
visidata/movement.py
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
import itertools
|
2
|
-
import re
|
3
2
|
|
4
|
-
from visidata import vd,
|
3
|
+
from visidata import vd, BaseSheet, Sheet, Column, Progress, ALT, asyncthread
|
5
4
|
|
6
5
|
|
7
6
|
def rotateRange(n, idx, reverse=False):
|
8
|
-
'Wraps an iter starting from idx. Yields indices from idx to n and then 0 to idx.'
|
7
|
+
'Wraps an iter starting from idx. Yields indices from idx+1 to n and then 0 to idx, or from idx-1 to 0 and n-1 to idx.'
|
9
8
|
if n == 0: return []
|
10
9
|
if reverse:
|
11
10
|
rng = range(idx-1, -1, -1)
|
@@ -81,18 +80,6 @@ def moveToNextRow(vs, func, reverse=False, msg='no different value up this colum
|
|
81
80
|
vd.status(msg)
|
82
81
|
|
83
82
|
|
84
|
-
@Sheet.api
|
85
|
-
def nextColRegex(sheet, colregex):
|
86
|
-
'Go to first visible column after the cursor matching `colregex`.'
|
87
|
-
pivot = sheet.cursorVisibleColIndex
|
88
|
-
for i in itertools.chain(range(pivot+1, len(sheet.visibleCols)), range(0, pivot+1)):
|
89
|
-
c = sheet.visibleCols[i]
|
90
|
-
if re.search(colregex, c.name, sheet.regex_flags()):
|
91
|
-
return i
|
92
|
-
|
93
|
-
vd.fail('no column name matches /%s/' % colregex)
|
94
|
-
|
95
|
-
|
96
83
|
@Column.property
|
97
84
|
def visibleWidth(self):
|
98
85
|
'Width of column as is displayed in terminal'
|
@@ -102,20 +89,18 @@ def visibleWidth(self):
|
|
102
89
|
return self.sheet._visibleColLayout[vcolidx][1]
|
103
90
|
|
104
91
|
|
105
|
-
Sheet.addCommand(None, 'go-left', 'cursorRight(-1)', 'go left')
|
106
|
-
Sheet.addCommand(None, 'go-down', 'cursorDown(+1)', 'go down')
|
107
|
-
Sheet.addCommand(None, 'go-up', 'cursorDown(-1)', 'go up')
|
108
|
-
Sheet.addCommand(None, 'go-right', 'cursorRight(+1)', 'go right')
|
109
|
-
Sheet.addCommand(None, 'go-pagedown', 'cursorDown(nScreenRows-1); sheet.topRowIndex = bottomRowIndex', 'scroll one page forward')
|
110
|
-
Sheet.addCommand(None, 'go-pageup', 'cursorDown(-nScreenRows+1); sheet.bottomRowIndex = topRowIndex', 'scroll one page backward')
|
92
|
+
Sheet.addCommand(None, 'go-left', 'cursorRight(-1)', 'go left', replay=False)
|
93
|
+
Sheet.addCommand(None, 'go-down', 'cursorDown(+1)', 'go down', replay=False)
|
94
|
+
Sheet.addCommand(None, 'go-up', 'cursorDown(-1)', 'go up', replay=False)
|
95
|
+
Sheet.addCommand(None, 'go-right', 'cursorRight(+1)', 'go right', replay=False)
|
96
|
+
Sheet.addCommand(None, 'go-pagedown', 'cursorDown(nScreenRows-1); sheet.topRowIndex = bottomRowIndex', 'scroll one page forward', replay=False)
|
97
|
+
Sheet.addCommand(None, 'go-pageup', 'cursorDown(-nScreenRows+1); sheet.bottomRowIndex = topRowIndex', 'scroll one page backward', replay=False)
|
111
98
|
|
112
99
|
Sheet.addCommand(None, 'go-leftmost', 'sheet.cursorVisibleColIndex = sheet.leftVisibleColIndex = 0', 'go all the way to the left of sheet')
|
113
100
|
Sheet.addCommand(None, 'go-top', 'sheet.cursorRowIndex = sheet.topRowIndex = 0', 'go all the way to the top of sheet')
|
114
|
-
Sheet.addCommand(None, 'go-bottom', 'sheet.cursorRowIndex =
|
101
|
+
Sheet.addCommand(None, 'go-bottom', 'sheet.cursorRowIndex = sheet.bottomRowIndex = len(rows)-1', 'go all the way to the bottom of sheet')
|
115
102
|
Sheet.addCommand(None, 'go-rightmost', 'sheet.leftVisibleColIndex = len(visibleCols)-1; pageLeft(); sheet.cursorVisibleColIndex = len(visibleCols)-1', 'go all the way to the right of sheet')
|
116
103
|
|
117
|
-
Sheet.addCommand('c', 'go-col-regex', 'sheet.cursorVisibleColIndex=nextColRegex(inputRegex("column name regex: ", type="regex-col", defaultLast=True))', 'go to next column with name matching regex')
|
118
|
-
Sheet.addCommand('zc', 'go-col-number', 'sheet.cursorVisibleColIndex = int(input("move to column number: "))', 'go to given column number (0-based)')
|
119
104
|
Sheet.addCommand('zr', 'go-row-number', 'sheet.cursorRowIndex = int(input("move to row number: "))', 'go to the given row number (0-based)')
|
120
105
|
|
121
106
|
|
@@ -130,6 +115,9 @@ Sheet.addCommand('z>', 'go-next-null', 'moveToNextRow(lambda row,col=cursorCol,i
|
|
130
115
|
for i in range(1, 11):
|
131
116
|
BaseSheet.addCommand(ALT+str(i)[-1], 'jump-sheet-'+str(i), f'vd.push(*(list(s for s in allSheets if s.shortcut==str({i})) or fail("no sheet")))', f'jump to sheet {i}')
|
132
117
|
|
118
|
+
for i in range(11, 21):
|
119
|
+
BaseSheet.addCommand('', 'jump-sheet-'+str(i), f'vd.push(*(list(s for s in allSheets if s.shortcut==str({i})) or fail("no sheet")))', f'jump to sheet {i}')
|
120
|
+
|
133
121
|
BaseSheet.bindkey('KEY_LEFT', 'go-left')
|
134
122
|
BaseSheet.bindkey('KEY_DOWN', 'go-down')
|
135
123
|
BaseSheet.bindkey('KEY_UP', 'go-up')
|
@@ -150,8 +138,8 @@ Sheet.bindkey('BUTTON3_PRESSED', 'go-mouse')
|
|
150
138
|
# vim-style scrolling with the 'z' prefix
|
151
139
|
Sheet.addCommand('zz', 'scroll-middle', 'sheet.topRowIndex = cursorRowIndex-int(nScreenRows/2)', 'scroll current row to center of screen')
|
152
140
|
|
153
|
-
Sheet.addCommand('kRIT5', 'go-right-page', 'sheet.cursorVisibleColIndex = sheet.leftVisibleColIndex = rightVisibleColIndex', 'scroll cursor one page right')
|
154
|
-
Sheet.addCommand('kLFT5', 'go-left-page', 'pageLeft()', 'scroll cursor one page left')
|
141
|
+
Sheet.addCommand('kRIT5', 'go-right-page', 'sheet.cursorVisibleColIndex = sheet.leftVisibleColIndex = rightVisibleColIndex', 'scroll cursor one page right', replay=False)
|
142
|
+
Sheet.addCommand('kLFT5', 'go-left-page', 'pageLeft()', 'scroll cursor one page left', replay=False)
|
155
143
|
Sheet.addCommand(None, 'scroll-left', 'sheet.cursorVisibleColIndex -= options.scroll_incr', 'scroll one column left')
|
156
144
|
Sheet.addCommand(None, 'scroll-right', 'sheet.cursorVisibleColIndex += options.scroll_incr', 'scroll one column right')
|
157
145
|
Sheet.addCommand(None, 'scroll-leftmost', 'sheet.leftVisibleColIndex = cursorVisibleColIndex', 'scroll sheet to leftmost column')
|
@@ -207,8 +195,6 @@ vd.addGlobals({'rotateRange': rotateRange})
|
|
207
195
|
vd.addMenuItems('''
|
208
196
|
View > Other sheet > previous sheet > jump-prev
|
209
197
|
View > Other sheet > first sheet > jump-first
|
210
|
-
Column > Goto > by number > go-col-number
|
211
|
-
Column > Goto > by name > go-col-regex
|
212
198
|
Row > Goto > top > go-top
|
213
199
|
Row > Goto > bottom > go-bottom
|
214
200
|
Row > Goto > previous > page > go-pageup
|
visidata/optionssheet.py
CHANGED
@@ -17,14 +17,14 @@ class OptionsSheet(Sheet):
|
|
17
17
|
precious = False
|
18
18
|
columns = (
|
19
19
|
Column('option', getter=lambda col,row: row.name),
|
20
|
-
Column('module', getter=lambda col,row: row.module,
|
20
|
+
Column('module', getter=lambda col,row: row.module, disp_expert=1),
|
21
21
|
Column('value',
|
22
22
|
getter=lambda col,row: col.sheet.diffOption(row.name),
|
23
|
-
setter=lambda col,row,val:
|
23
|
+
setter=lambda col,row,val: vd.options.set(row.name, val, col.sheet.source)
|
24
24
|
),
|
25
25
|
Column('default', getter=lambda col,row: vd.options.getdefault(row.name)),
|
26
26
|
Column('description', width=40, getter=lambda col,row: vd.options._get(row.name, 'default').helpstr),
|
27
|
-
AttrColumn('replayable',
|
27
|
+
AttrColumn('replayable', disp_expert=1),
|
28
28
|
)
|
29
29
|
colorizers = [
|
30
30
|
CellColorizer(3, None, lambda s,c,r,v: v.value if r and c in s.columns[2:4] and r.name.startswith('color_') else None),
|
@@ -73,8 +73,6 @@ class OptionsSheet(Sheet):
|
|
73
73
|
def iterload(self):
|
74
74
|
for k in vd.options.keys():
|
75
75
|
v = vd.options._get(k)
|
76
|
-
# if vd.options.disp_help > v.max_help:
|
77
|
-
# continue
|
78
76
|
if v.sheettype in [None, BaseSheet]:
|
79
77
|
yield v
|
80
78
|
elif self.source != 'global' and v.sheettype in self.source.superclasses():
|
visidata/path.py
CHANGED
@@ -111,6 +111,7 @@ class FileProgress:
|
|
111
111
|
|
112
112
|
# track Progress on original fp
|
113
113
|
self.fp_orig_read = self.fp.read
|
114
|
+
self.fp_orig_readline = self.fp.readline
|
114
115
|
self.fp_orig_close = self.fp.close
|
115
116
|
|
116
117
|
self.fp.read = self.read
|
@@ -132,6 +133,12 @@ class FileProgress:
|
|
132
133
|
self.prog.addProgress(len(r))
|
133
134
|
return r
|
134
135
|
|
136
|
+
def readline(self, size=-1):
|
137
|
+
r = self.fp_orig_readline(size)
|
138
|
+
if self.prog:
|
139
|
+
self.prog.addProgress(len(r))
|
140
|
+
return r
|
141
|
+
|
135
142
|
def __getattr__(self, k):
|
136
143
|
return getattr(self.fp, k)
|
137
144
|
|
@@ -172,10 +179,6 @@ class Path(os.PathLike):
|
|
172
179
|
'Filename without any extensions. Not the same as pathlib.Path.'
|
173
180
|
return self.base_stem
|
174
181
|
|
175
|
-
@lru_cache()
|
176
|
-
def stat(self, force=False):
|
177
|
-
return self._path.stat()
|
178
|
-
|
179
182
|
@property
|
180
183
|
def given(self):
|
181
184
|
'The path as given to the constructor.'
|
@@ -222,7 +225,7 @@ class Path(os.PathLike):
|
|
222
225
|
return self._path.__fspath__()
|
223
226
|
|
224
227
|
def __lt__(self, a):
|
225
|
-
if isinstance(a,
|
228
|
+
if isinstance(a, Path):
|
226
229
|
return self._path.__lt__(a._path)
|
227
230
|
return self._path.__lt__(a)
|
228
231
|
|
@@ -234,27 +237,50 @@ class Path(os.PathLike):
|
|
234
237
|
return bool(self.fp or self.fptext)
|
235
238
|
|
236
239
|
def open(self, mode='rt', encoding=None, encoding_errors=None, newline=None):
|
237
|
-
'
|
240
|
+
if 'b' in mode:
|
241
|
+
return self.open_bytes(mode)
|
242
|
+
|
243
|
+
return self.open_text(mode=mode, encoding=encoding, encoding_errors=encoding_errors, newline=newline)
|
244
|
+
|
245
|
+
def open_bytes(self, mode='rb'):
|
246
|
+
'Open the file pointed by this path and return a file object in binary mode.'
|
247
|
+
if self.rfile:
|
248
|
+
raise ValueError('a RepeatFile holds text and cannot be reopened in binary mode')
|
249
|
+
|
250
|
+
if 'b' not in mode:
|
251
|
+
mode += 'b'
|
252
|
+
|
253
|
+
if self.given == '-':
|
254
|
+
if 'r' in mode:
|
255
|
+
return os.fdopen(vd._stdin.fileno(), 'rb')
|
256
|
+
elif 'w' in mode or 'a' in mode:
|
257
|
+
# convert 'a' to 'w' for stdout: https://bugs.python.org/issue27805
|
258
|
+
return os.dup(vd._stdout.fileno())
|
259
|
+
else:
|
260
|
+
vd.error('invalid mode "%s" for Path.open()' % mode)
|
261
|
+
return sys.stderr
|
262
|
+
|
263
|
+
return self._open(mode=mode)
|
264
|
+
|
265
|
+
def open_text(self, mode='rt', encoding=None, encoding_errors=None, newline=None):
|
266
|
+
'Open path in text mode, using options.encoding and options.encoding_errors. Return open file-pointer or file-pointer-like.'
|
238
267
|
# rfile makes a single-access fp reusable
|
239
268
|
|
269
|
+
if 't' not in mode:
|
270
|
+
mode += 't'
|
271
|
+
|
240
272
|
if self.rfile:
|
241
|
-
if 'b' in mode:
|
242
|
-
raise ValueError('a RepeatFile holds text and cannot be reopened in binary mode')
|
243
273
|
return self.rfile.reopen()
|
244
274
|
|
245
|
-
if self.fp:
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
errors=encoding_errors or vd.options.encoding_errors)
|
275
|
+
if self.fp and not self.fptext:
|
276
|
+
self.fptext = codecs.iterdecode(self.fp,
|
277
|
+
encoding=encoding or vd.options.encoding,
|
278
|
+
errors=encoding_errors or vd.options.encoding_errors)
|
250
279
|
|
251
280
|
if self.fptext:
|
252
281
|
self.rfile = RepeatFile(self.fptext)
|
253
282
|
return self.rfile
|
254
283
|
|
255
|
-
if 't' not in mode and 'b' not in mode:
|
256
|
-
mode += 't'
|
257
|
-
|
258
284
|
if self.given == '-':
|
259
285
|
if 'r' in mode:
|
260
286
|
return vd._stdin
|
@@ -265,10 +291,7 @@ class Path(os.PathLike):
|
|
265
291
|
vd.error('invalid mode "%s" for Path.open()' % mode)
|
266
292
|
return sys.stderr
|
267
293
|
|
268
|
-
|
269
|
-
return self._open(mode=mode)
|
270
|
-
else:
|
271
|
-
return self._open(mode=mode, encoding=encoding or vd.options.encoding, errors=vd.options.encoding_errors, newline=newline)
|
294
|
+
return self._open(mode=mode, encoding=encoding or vd.options.encoding, errors=vd.options.encoding_errors, newline=newline)
|
272
295
|
|
273
296
|
@wraps(pathlib.Path.read_text)
|
274
297
|
def read_text(self, *args, **kwargs):
|
@@ -282,6 +305,8 @@ class Path(os.PathLike):
|
|
282
305
|
return RepeatFile(self.lines).read()
|
283
306
|
elif self.fp:
|
284
307
|
return self.fp.read()
|
308
|
+
elif self.fptext:
|
309
|
+
return self.fptext.read()
|
285
310
|
else:
|
286
311
|
return self._path.read_text(*args, **kwargs)
|
287
312
|
|
@@ -324,12 +349,6 @@ class Path(os.PathLike):
|
|
324
349
|
prog.addProgress(len(line))
|
325
350
|
yield line.rstrip('\n')
|
326
351
|
|
327
|
-
def open_bytes(self, mode='rb'):
|
328
|
-
'Open the file pointed by this path and return a file object in binary mode.'
|
329
|
-
if 'b' not in mode:
|
330
|
-
mode += 'b'
|
331
|
-
return self.open(mode=mode) #1880
|
332
|
-
|
333
352
|
def read_bytes(self):
|
334
353
|
'Return the entire binary contents of the pointed-to file as a bytes object.'
|
335
354
|
with self.open(mode='rb') as fp:
|
@@ -337,12 +356,12 @@ class Path(os.PathLike):
|
|
337
356
|
|
338
357
|
@wraps(pathlib.Path.is_fifo)
|
339
358
|
def is_fifo(self):
|
340
|
-
'Return True if the path is a
|
359
|
+
'Return True if the path is a fifo.'
|
341
360
|
return self._path.is_fifo()
|
342
361
|
|
343
362
|
def is_local(self):
|
344
363
|
'Return True if self.filename refers to a file on the local disk.'
|
345
|
-
return not bool(self.fp) and not bool(self.fptext)
|
364
|
+
return not bool(self.is_url()) and not bool(self.fp) and not bool(self.fptext)
|
346
365
|
|
347
366
|
def is_url(self):
|
348
367
|
'Return True if the given path appears to be a URL.'
|
@@ -406,21 +425,24 @@ class RepeatFile:
|
|
406
425
|
return RepeatFile(self.iter_lines, lines=self.lines)
|
407
426
|
|
408
427
|
def read(self, n=None):
|
409
|
-
|
428
|
+
'''Returns a string or bytes object. Unlike the standard read() function, when *n* is given, more than *n* characters/bytes can be returned, and often will.'''
|
410
429
|
if n is None:
|
411
430
|
n = 10**12 # some too huge number
|
412
|
-
|
431
|
+
r = []
|
432
|
+
size = 0
|
433
|
+
output_type = str; eol = '\n'; joiner = ''
|
434
|
+
while not r or size < n:
|
413
435
|
try:
|
414
436
|
s = next(self.iter)
|
415
|
-
if r
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
437
|
+
if not r and isinstance(s, bytes):
|
438
|
+
output_type = bytes; eol = b'\n'; joiner = b''
|
439
|
+
assert isinstance(s, output_type), (s, output_type)
|
440
|
+
r.append(s)
|
441
|
+
r.append(eol)
|
442
|
+
size += len(s) + len(eol)
|
421
443
|
except StopIteration:
|
422
444
|
break # end of file
|
423
|
-
return r
|
445
|
+
return joiner.join(r)
|
424
446
|
|
425
447
|
def write(self, s):
|
426
448
|
return self.iter_lines.write(s)
|
visidata/pivot.py
CHANGED
@@ -53,7 +53,7 @@ class AggrColumn(Column):
|
|
53
53
|
def calcValue(col, row):
|
54
54
|
if col.sheet.loading:
|
55
55
|
return visidata.INPROGRESS
|
56
|
-
return col.aggregator(col.origCol, row.sourcerows)
|
56
|
+
return col.aggregator.aggregate(col.origCol, row.sourcerows)
|
57
57
|
|
58
58
|
|
59
59
|
def makeAggrColumn(aggcol, aggregator):
|
@@ -175,13 +175,13 @@ class PivotSheet(Sheet):
|
|
175
175
|
c = Column(colname,
|
176
176
|
type=aggregator.type or aggcol.type,
|
177
177
|
aggvalue=value,
|
178
|
-
getter=lambda col,row,aggcol=aggcol,agg=aggregator: agg(aggcol, row.pivotrows.get(col.aggvalue, [])))
|
178
|
+
getter=lambda col,row,aggcol=aggcol,agg=aggregator: agg.aggregate(aggcol, row.pivotrows.get(col.aggvalue, [])))
|
179
179
|
self.addColumn(c)
|
180
180
|
|
181
181
|
# if aggregator.name != 'count': # already have count above
|
182
182
|
# c = Column('Total_' + aggcol.name,
|
183
183
|
# type=aggregator.type or aggcol.type,
|
184
|
-
# getter=lambda col,row,aggcol=aggcol,agg=aggregator: agg(aggcol, row.sourcerows))
|
184
|
+
# getter=lambda col,row,aggcol=aggcol,agg=aggregator: agg.aggregate(aggcol, row.sourcerows))
|
185
185
|
# self.addColumn(c)
|
186
186
|
|
187
187
|
@asyncthread
|
@@ -291,8 +291,11 @@ class PivotSheet(Sheet):
|
|
291
291
|
@PivotSheet.api
|
292
292
|
def addcol_aggr(sheet, col):
|
293
293
|
hasattr(col, 'origCol') or vd.fail('not an aggregation column')
|
294
|
-
for
|
295
|
-
|
294
|
+
for agg_choice in vd.chooseAggregators():
|
295
|
+
agg_or_list = vd.aggregators[agg_choice]
|
296
|
+
aggs = agg_or_list if isinstance(agg_or_list, list) else [agg_or_list]
|
297
|
+
for agg in aggs:
|
298
|
+
sheet.addColumnAtCursor(makeAggrColumn(col.origCol, vd.aggregators[agg]))
|
296
299
|
|
297
300
|
|
298
301
|
Sheet.addCommand('W', 'pivot', 'vd.push(makePivot(sheet, keyCols, [cursorCol]))', 'open Pivot Table: group rows by key column and summarize current column')
|
visidata/pyobj.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
from typing import Mapping
|
2
2
|
import inspect
|
3
3
|
import math
|
4
|
+
import numbers
|
4
5
|
|
5
6
|
from visidata import vd, asyncthread, ENTER, deduceType
|
6
|
-
from visidata import Sheet, Column, VisiData, ColumnItem, TableSheet, BaseSheet, Progress, ColumnAttr, SuspendCurses, TextSheet
|
7
|
+
from visidata import Sheet, Column, VisiData, ColumnItem, TableSheet, BaseSheet, Progress, ColumnAttr, SuspendCurses, TextSheet, setitem
|
7
8
|
import visidata
|
8
9
|
|
9
10
|
vd.option('visibility', 0, 'visibility level')
|
@@ -14,8 +15,30 @@ vd.option('fmt_expand_list', '%s[%s]', 'format str to use for names of columns e
|
|
14
15
|
|
15
16
|
class PythonSheet(Sheet):
|
16
17
|
def openRow(self, row):
|
17
|
-
return PyobjSheet("%s[%s]" % (self.name, self.
|
18
|
+
return PyobjSheet("%s[%s]" % (self.name, self.rowname(row)), source=row)
|
18
19
|
|
20
|
+
class PythonAtomSheet(PythonSheet):
|
21
|
+
'''a sheet to display one Python object that does not offer deeper inspection,
|
22
|
+
like None, a bool, or an int/float'''
|
23
|
+
rowtype = 'object' #singular, because it should only ever hold one
|
24
|
+
columns = [
|
25
|
+
Column('value', getter=lambda col,row: row,
|
26
|
+
setter=lambda c,r,v: None)
|
27
|
+
]
|
28
|
+
def loader(self):
|
29
|
+
self.rows = [self.source]
|
30
|
+
self.column('value').type = deduceType(self.source)
|
31
|
+
|
32
|
+
def openRow(self, row):
|
33
|
+
vd.fail('cannot dive deeper on this object')
|
34
|
+
def openCell(self, col, row, rowidx=None):
|
35
|
+
vd.fail('cannot dive deeper on this object')
|
36
|
+
def openRowPyobj(self, rowidx):
|
37
|
+
vd.fail('cannot dive deeper on this object')
|
38
|
+
def openCellPyobj(self, col, rowidx):
|
39
|
+
vd.fail('cannot dive deeper on this object')
|
40
|
+
def newRow(self):
|
41
|
+
vd.fail('adding rows to this sheet is not supported')
|
19
42
|
|
20
43
|
#### generic list/dict/object browsing
|
21
44
|
@VisiData.global_api
|
@@ -152,17 +175,23 @@ class PyobjSheet(PythonSheet):
|
|
152
175
|
def __new__(cls, *names, **kwargs):
|
153
176
|
'Return Sheet object of appropriate type for given sources in `args`.'
|
154
177
|
pyobj=kwargs.get('source', object())
|
155
|
-
if
|
178
|
+
if pyobj is None:
|
179
|
+
return None
|
180
|
+
elif isinstance(pyobj, numbers.Number):
|
181
|
+
return PythonAtomSheet(*names, source=pyobj)
|
182
|
+
elif isinstance(pyobj, (list, tuple)):
|
156
183
|
if getattr(pyobj, '_fields', None): # list of namedtuple
|
157
184
|
return SheetNamedTuple(*names, **kwargs)
|
158
185
|
else:
|
159
186
|
return SheetList(*names, **kwargs)
|
187
|
+
elif isinstance(pyobj, set):
|
188
|
+
return ListOfPyobjSheet(*names, source=list(pyobj))
|
160
189
|
elif isinstance(pyobj, Mapping):
|
161
190
|
return SheetDict(*names, **kwargs)
|
162
191
|
elif isinstance(pyobj, str):
|
163
192
|
return TextSheet(*names, source=pyobj.splitlines())
|
164
193
|
elif isinstance(pyobj, bytes):
|
165
|
-
return TextSheet(*names, source=pyobj.decode(options.encoding).splitlines())
|
194
|
+
return TextSheet(*names, source=pyobj.decode(cls.options.encoding).splitlines())
|
166
195
|
elif isinstance(pyobj, object):
|
167
196
|
obj = super().__new__(cls) #, *names, **kwargs)
|
168
197
|
return obj
|
@@ -173,6 +202,10 @@ class PyobjSheet(PythonSheet):
|
|
173
202
|
self.rows = []
|
174
203
|
vislevel = self.options.visibility
|
175
204
|
for r in dir(self.source):
|
205
|
+
# reading these attributes can cause distracting fail() messages
|
206
|
+
if r in ('onlySelectedRows', 'someSelectedRows'):
|
207
|
+
vd.warning(f'skipping attribute: {r}')
|
208
|
+
continue
|
176
209
|
try:
|
177
210
|
if vislevel <= 2 and r.startswith('__'): continue
|
178
211
|
if vislevel <= 1 and r.startswith('_'): continue
|
@@ -191,8 +224,9 @@ class PyobjSheet(PythonSheet):
|
|
191
224
|
@TableSheet.api
|
192
225
|
def openRow(sheet, row, rowidx=None):
|
193
226
|
'Return Sheet diving into *row*.'
|
227
|
+
if row is None or sheet.nRows == 0: vd.fail('no row to dive into')
|
194
228
|
if rowidx is None:
|
195
|
-
k = sheet.
|
229
|
+
k = sheet.rowname(row) or str(sheet.cursorRowIndex)
|
196
230
|
else:
|
197
231
|
k = rowidx
|
198
232
|
|
@@ -209,21 +243,40 @@ def openRow(sheet, row, rowidx=None):
|
|
209
243
|
@TableSheet.api
|
210
244
|
def openCell(sheet, col, row, rowidx=None):
|
211
245
|
'Return Sheet diving into cell at *row* in *col*.'
|
246
|
+
if col is None or row is None or sheet.nRows == 0: vd.fail('no cell to dive into')
|
212
247
|
if rowidx is None:
|
213
|
-
k = sheet.
|
248
|
+
k = sheet.rowname(row) or str(sheet.cursorRowIndex)
|
214
249
|
else:
|
215
250
|
k = rowidx
|
216
251
|
name = f'{sheet.name}[{k}].{col.name}'
|
217
252
|
return PyobjSheet(name, source=col.getTypedValue(row))
|
218
253
|
|
254
|
+
@Sheet.api
|
255
|
+
@asyncthread
|
256
|
+
def openRows(sheet, rows):
|
257
|
+
for r in Progress(rows):
|
258
|
+
vd.push(sheet.openRow(r))
|
259
|
+
|
260
|
+
vd.sync()
|
261
|
+
|
262
|
+
@Sheet.api
|
263
|
+
@asyncthread
|
264
|
+
def openCells(sheet, col, rows):
|
265
|
+
for r in Progress(rows):
|
266
|
+
vd.push(openCell(col, r))
|
267
|
+
|
268
|
+
vd.sync()
|
269
|
+
|
219
270
|
@TableSheet.api
|
220
271
|
def openRowPyobj(sheet, rowidx):
|
221
272
|
'Return Sheet of raw Python object of row.'
|
273
|
+
if sheet.nRows == 0: vd.fail('no row to dive into')
|
222
274
|
return PyobjSheet("%s[%s]" % (sheet.name, rowidx), source=sheet.rows[rowidx])
|
223
275
|
|
224
276
|
@TableSheet.api
|
225
277
|
def openCellPyobj(sheet, col, rowidx):
|
226
278
|
'Return Sheet of raw Python object of cell.'
|
279
|
+
if col is None or sheet.nRows == 0: vd.fail('no cell to dive into')
|
227
280
|
name = f'{sheet.name}[{rowidx}].{col.name}'
|
228
281
|
return PyobjSheet(name, source=col.getValue(sheet.rows[rowidx]))
|
229
282
|
|
@@ -249,10 +302,11 @@ Sheet.addCommand('z^Y', 'pyobj-cell', 'status(type(cursorValue).__name__); vd.pu
|
|
249
302
|
BaseSheet.addCommand('g^Y', 'pyobj-sheet', 'status(type(sheet).__name__); vd.push(PyobjSheet(sheet.name+"_sheet", source=sheet))', 'open current sheet as Python object')
|
250
303
|
|
251
304
|
Sheet.addCommand('', 'open-row-basic', 'vd.push(TableSheet.openRow(sheet, cursorRow))', 'dive into current row as basic table (ignoring subsheet dive)')
|
252
|
-
Sheet.addCommand(ENTER, 'open-row', 'vd.push(openRow(cursorRow))', 'open current row with sheet-specific dive')
|
305
|
+
Sheet.addCommand(ENTER, 'open-row', 'vd.push(openRow(cursorRow)) if cursorRow else vd.fail("no row to open")', 'open current row with sheet-specific dive')
|
253
306
|
Sheet.addCommand('z'+ENTER, 'open-cell', 'vd.push(openCell(cursorCol, cursorRow))', 'open sheet with copies of rows referenced in current cell')
|
254
|
-
|
255
|
-
Sheet.addCommand('
|
307
|
+
openRows
|
308
|
+
Sheet.addCommand('g'+ENTER, 'dive-selected', 'openRows(selectedRows)', 'open all selected rows')
|
309
|
+
Sheet.addCommand('gz'+ENTER, 'dive-selected-cells', 'openCells(cursorCol, selectedRows)', 'open all selected cells')
|
256
310
|
|
257
311
|
PyobjSheet.addCommand('v', 'visibility', 'sheet.options.visibility = 0 if sheet.options.visibility else 2; reload()', 'toggle show/hide for methods and hidden properties')
|
258
312
|
PyobjSheet.addCommand('gv', 'show-hidden', 'sheet.options.visibility = 2; reload()', 'show methods and hidden properties')
|
visidata/save.py
CHANGED
@@ -3,7 +3,7 @@ import os
|
|
3
3
|
from copy import copy
|
4
4
|
|
5
5
|
from visidata import vd
|
6
|
-
from visidata import Sheet, BaseSheet, VisiData, IndexSheet, Path, Progress, TypedExceptionWrapper
|
6
|
+
from visidata import Sheet, BaseSheet, VisiData, IndexSheet, Path, Progress, TypedExceptionWrapper, TypedWrapper, UNLOADED
|
7
7
|
|
8
8
|
vd.option('safe_error', '#ERR', 'error string to use while saving', replay=True)
|
9
9
|
vd.option('save_encoding', 'utf-8', 'encoding passed to codecs.open when saving a file', replay=True, help=vd.help_encoding)
|
@@ -13,12 +13,15 @@ def safe_trdict(vs):
|
|
13
13
|
'returns string.translate dictionary for replacing tabs and newlines'
|
14
14
|
if vs.options.safety_first:
|
15
15
|
delim = vs.options.delimiter
|
16
|
-
|
16
|
+
trdict = {
|
17
17
|
0: '', # strip NUL completely
|
18
|
-
ord(delim): vs.options.tsv_safe_tab, # \t
|
19
18
|
10: vs.options.tsv_safe_newline, # \n
|
20
19
|
13: vs.options.tsv_safe_newline, # \r
|
21
20
|
}
|
21
|
+
if not delim or ord(delim) in trdict:
|
22
|
+
vd.fail(f'cannot use delimiter {repr(delim)} with safety_first')
|
23
|
+
trdict[ord(delim)] = vs.options.tsv_safe_tab # \t
|
24
|
+
return trdict
|
22
25
|
return {}
|
23
26
|
|
24
27
|
|
@@ -29,12 +32,12 @@ def iterdispvals(sheet, *cols, format=False):
|
|
29
32
|
cols = sheet.visibleCols
|
30
33
|
|
31
34
|
transformers = collections.OrderedDict() # list of transformers for each column in order
|
35
|
+
trdict = sheet.safe_trdict()
|
32
36
|
for col in cols:
|
33
37
|
transformers[col] = [ col.type ]
|
34
38
|
if format:
|
35
39
|
formatMaker = getattr(col, 'formatter_'+(col.formatter or sheet.options.disp_formatter))
|
36
40
|
transformers[col].append(formatMaker(col._formatdict))
|
37
|
-
trdict = sheet.safe_trdict()
|
38
41
|
if trdict:
|
39
42
|
transformers[col].append(lambda v,trdict=trdict: v.translate(trdict))
|
40
43
|
|
@@ -46,7 +49,6 @@ def iterdispvals(sheet, *cols, format=False):
|
|
46
49
|
dispval = col.getValue(r)
|
47
50
|
|
48
51
|
except Exception as e:
|
49
|
-
vd.exceptionCaught(e)
|
50
52
|
dispval = options_safe_error or str(e)
|
51
53
|
|
52
54
|
try:
|
@@ -56,6 +58,9 @@ def iterdispvals(sheet, *cols, format=False):
|
|
56
58
|
elif isinstance(dispval, TypedExceptionWrapper):
|
57
59
|
dispval = options_safe_error or str(dispval)
|
58
60
|
break
|
61
|
+
elif isinstance(dispval, TypedWrapper):
|
62
|
+
dispval = ''
|
63
|
+
break
|
59
64
|
else:
|
60
65
|
dispval = t(dispval)
|
61
66
|
|
@@ -89,7 +94,7 @@ def getDefaultSaveName(sheet):
|
|
89
94
|
|
90
95
|
|
91
96
|
@VisiData.api
|
92
|
-
def
|
97
|
+
def saveCols(vd, cols):
|
93
98
|
sheet = cols[0].sheet
|
94
99
|
vs = copy(sheet)
|
95
100
|
vs.columns = list(cols)
|
@@ -109,8 +114,10 @@ def saveSheets(vd, givenpath, *vsheets, confirm_overwrite=True):
|
|
109
114
|
if not vsheets: # blank tuple
|
110
115
|
vd.warning('no sheets to save')
|
111
116
|
return
|
117
|
+
unloaded = [ vs for vs in vsheets if vs.rows is UNLOADED ]
|
118
|
+
vd.sync(*vd.ensureLoaded(unloaded))
|
112
119
|
|
113
|
-
filetypes = [givenpath.ext, vd.options.save_filetype]
|
120
|
+
filetypes = [givenpath.ext.lower(), vd.options.save_filetype.lower()]
|
114
121
|
|
115
122
|
vd.clearCaches()
|
116
123
|
|
@@ -203,8 +210,8 @@ BaseSheet.addCommand('', 'save-sheet-really', 'vd.saveSheets(Path(getDefaultSave
|
|
203
210
|
BaseSheet.addCommand('', 'save-source', 'vd.saveSheets(rootSheet().source, rootSheet())', 'save root sheet to its source')
|
204
211
|
BaseSheet.addCommand('g^S', 'save-all', 'vd.saveSheets(inputPath("save all sheets to: "), *vd.stackedSheets)', 'save all sheets to given file or directory)')
|
205
212
|
IndexSheet.addCommand('g^S', 'save-selected', 'vd.saveSheets(inputPath("save %d sheets to: " % nSelectedRows, value="_".join(getattr(vs, "name", None) or "blank" for vs in selectedRows)), *selectedRows)', 'save all selected sheets to given file or directory')
|
206
|
-
Sheet.addCommand('', 'save-col', '
|
207
|
-
Sheet.addCommand('', 'save-col-keys', '
|
213
|
+
Sheet.addCommand('', 'save-col', 'saveCols([cursorCol])', 'save current column only to filename in format determined by extension (default .tsv)')
|
214
|
+
Sheet.addCommand('', 'save-col-keys', 'saveCols(keyCols + [cursorCol])', 'save key columns and current column to filename in format determined by extension (default .tsv)')
|
208
215
|
|
209
216
|
vd.addMenuItems('''
|
210
217
|
File > Save > current sheet > save-sheet
|