diff --git a/.gitignore b/.gitignore index ce87fe5..dfa63d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /visidata-1.5.2.tar.gz /visidata-2.0.1.tar.gz +/visidata-2.1.tar.gz diff --git a/0001-Remove-extra-copy-of-man-page.patch b/0001-Remove-extra-copy-of-man-page.patch index 456e3a2..311fc37 100644 --- a/0001-Remove-extra-copy-of-man-page.patch +++ b/0001-Remove-extra-copy-of-man-page.patch @@ -1,4 +1,4 @@ -From 2f76d63824d86ff16bc6fa8255234c12573a1aa3 Mon Sep 17 00:00:00 2001 +From 5b1094075bccbe0928730a47e65441198d5d3f0b Mon Sep 17 00:00:00 2001 From: Elliott Sales de Andrade Date: Wed, 27 Mar 2019 19:13:42 -0400 Subject: [PATCH] Remove extra copy of man page. @@ -7,351 +7,36 @@ It's already installed in /usr/share/man/man1. Signed-off-by: Elliott Sales de Andrade --- - setup.py | 1 - - visidata/data.py | 322 +++++++++++++++++++++++++++++++++++++++++++++++ - 2 files changed, 322 insertions(+), 1 deletion(-) - create mode 100644 visidata/data.py + setup.py | 2 +- + visidata/help.py | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py -index 9833bf4d..61d064da 100755 +index dfdbe23c..f257799e 100755 --- a/setup.py +++ b/setup.py -@@ -25,7 +25,6 @@ setup(name='visidata', +@@ -25,7 +25,7 @@ setup(name='visidata', packages=['visidata', 'visidata.loaders', 'visidata.tests'], include_package_data=True, data_files = [('share/man/man1', ['visidata/man/vd.1'])], -- package_data={'visidata': ['man/vd.1']}, +- package_data={'visidata': ['man/vd.1', 'man/vd.txt']}, ++ package_data={'visidata': ['man/vd.txt']}, license='GPLv3', classifiers=[ 'Development Status :: 5 - Production/Stable', -diff --git a/visidata/data.py b/visidata/data.py -new file mode 100644 -index 00000000..b7d1f527 ---- /dev/null -+++ b/visidata/data.py -@@ -0,0 +1,322 @@ -+import itertools -+import os -+import random -+ -+from visidata import * -+ -+option('confirm_overwrite', True, 'whether to prompt for overwrite confirmation on save') -+replayableOption('safe_error', '#ERR', 'error string to use while saving') -+replayableOption('header', 1, 'parse first N rows of certain formats as column names') -+replayableOption('delimiter', '\t', 'delimiter to use for tsv filetype') -+replayableOption('filetype', '', 'specify file type') -+replayableOption('save_filetype', 'tsv', 'specify default file type to save as') -+replayableOption('tsv_safe_newline', '\u001e', 'replacement for tab character when saving to tsv') -+replayableOption('tsv_safe_tab', '\u001f', 'replacement for newline character when saving to tsv') -+ -+option('color_change_pending', 'reverse yellow', 'color for file attributes pending modification') -+option('color_delete_pending', 'red', 'color for files pending delete') -+ -+ -+Sheet.addCommand('R', 'random-rows', 'nrows=int(input("random number to select: ", value=nRows)); vs=copy(sheet); vs.name=name+"_sample"; vd.push(vs).rows=random.sample(rows, nrows or nRows)') -+ -+Sheet.addCommand('a', 'add-row', 'rows.insert(cursorRowIndex+1, newRow()); cursorDown(1)') -+Sheet.addCommand('ga', 'add-rows', 'addRows(sheet, int(input("add rows: ")), cursorRowIndex+1)') -+Sheet.addCommand('za', 'addcol-new', 'c=addColumn(SettableColumn("", width=options.default_width), cursorColIndex+1); draw(vd.scr); cursorVisibleColIndex=visibleCols.index(c); c.name=editCell(cursorVisibleColIndex, -1); c.width=None') -+Sheet.addCommand('gza', 'addcol-bulk', 'for c in range(int(input("add columns: "))): addColumn(SettableColumn(""), cursorColIndex+1)') -+ -+Sheet.addCommand('f', 'fill-nulls', 'fillNullValues(cursorCol, selectedRows or rows)') -+ -+bindkey('KEY_SLEFT', 'slide-left') -+bindkey('KEY_SR', 'slide-left') -+bindkey('kDN', 'slide-down') -+bindkey('kUP', 'slide-up') -+bindkey('KEY_SRIGHT', 'slide-right') -+bindkey('KEY_SF', 'slide-right') -+ -+bindkey('gKEY_SLEFT', 'slide-leftmost') -+bindkey('gkDN', 'slide-bottom') -+bindkey('gkUP', 'slide-top') -+bindkey('gKEY_SRIGHT', 'slide-rightmost') -+ -+ -+class SettableColumn(Column): -+ def __init__(self, *args, **kwargs): -+ super().__init__(*args, **kwargs) -+ self.cache = {} -+ -+ def setValue(self, row, value): -+ self.cache[id(row)] = value -+ -+ def calcValue(self, row): -+ return self.cache.get(id(row), None) -+ -+Sheet._coltype = SettableColumn -+ -+@asyncthread -+def addRows(sheet, n, idx): -+ for i in Progress(range(n), 'adding'): -+ sheet.addRow(sheet.newRow(), idx+1) -+ -+@asyncthread -+def fillNullValues(col, rows): -+ 'Fill null cells in col with the previous non-null value' -+ lastval = None -+ nullfunc = isNullFunc() -+ n = 0 -+ rowsToFill = list(rows) -+ for r in Progress(col.sheet.rows, 'filling'): # loop over all rows -+ try: -+ val = col.getValue(r) -+ except Exception as e: -+ val = e -+ -+ if nullfunc(val) and r in rowsToFill: -+ if lastval: -+ col.setValue(r, lastval) -+ n += 1 -+ else: -+ lastval = val -+ -+ col.recalc() -+ status("filled %d values" % n) -+ -+ -+def updateColNames(sheet, rows, cols, overwrite=False): -+ for c in cols: -+ if not c._name or overwrite: -+ c.name = "_".join(c.getDisplayValue(r) for r in rows) -+ -+ -+Sheet.addCommand('^', 'rename-col', 'cursorCol.name = editCell(cursorVisibleColIndex, -1)'), -+Sheet.addCommand('z^', 'rename-col-selected', 'updateColNames(sheet, selectedRows or [cursorRow], [sheet.cursorCol], overwrite=True)') -+Sheet.addCommand('g^', 'rename-cols-row', 'updateColNames(sheet, selectedRows or [cursorRow], sheet.visibleCols)') -+Sheet.addCommand('gz^', 'rename-cols-selected', 'updateColNames(sheet, selectedRows or [cursorRow], sheet.visibleCols, overwrite=True)') -+BaseSheet.addCommand(None, 'rename-sheet', 'sheet.name = input("rename sheet to: ", value=sheet.name)') -+# gz^ with no selectedRows is same as z^ -+ -+globalCommand('o', 'open-file', 'vd.push(openSource(inputFilename("open: ")))') -+Sheet.addCommand('^S', 'save-sheet', 'saveSheets(inputFilename("save to: ", value=getDefaultSaveName(sheet)), sheet, confirm_overwrite=options.confirm_overwrite)') -+globalCommand('g^S', 'save-all', 'saveSheets(inputFilename("save all sheets to: "), *vd.sheets, confirm_overwrite=options.confirm_overwrite)') -+Sheet.addCommand('z^S', 'save-col', 'vs = copy(sheet); vs.columns = [cursorCol]; vs.rows = selectedRows or rows; saveSheets(inputFilename("save to: ", value=getDefaultSaveName(vs)), vs, confirm_overwrite=options.confirm_overwrite)') -+ -+Sheet.addCommand('z=', 'show-expr', 'status(evalexpr(inputExpr("show expr="), cursorRow))') -+ -+Sheet.addCommand('gz=', 'setcol-range', 'cursorCol.setValues(selectedRows or rows, *list(itertools.islice(eval(input("set column= ", "expr", completer=CompleteExpr())), len(selectedRows or rows))))') -+ -+globalCommand('A', 'add-sheet', 'vd.push(newSheet(int(input("num columns for new sheet: "))))') -+ -+# in VisiData, ^H refers to the man page -+globalCommand('^H', 'sysopen-help', 'openManPage()') -+bindkey('KEY_F(1)', 'sysopen-help') -+bindkey('KEY_BACKSPACE', 'sysopen-help') -+bindkey('zKEY_F(1)', 'help-commands') -+bindkey('zKEY_BACKSPACE', 'help-commands') -+ -+def openManPage(): -+ import site -+ import sys -+ import sysconfig -+ if '__file__' not in globals(): -+ # Just hope this works. -+ manpage = 'vd.1' -+ elif __file__.startswith(sysconfig.get_path('purelib')): -+ # Global install -+ manpage = os.path.join(sys.prefix, 'share/man/man1/vd.1') -+ if not os.path.exists(manpage): -+ # Definitely a package install and compressed, so don't use a path. -+ manpage = 'vd.1' -+ elif __file__.startswith(site.USER_BASE): -+ # User install -+ manpage = os.path.join(site.USER_BASE, 'share/man/man1/vd.1') -+ else: -+ # Using PYTHONPATH or an editable install; look for file nearby -+ manpage = os.path.join(os.path.dirname(__file__), 'man/vd.1') -+ with SuspendCurses(): -+ os.system(' '.join(['man', manpage])) -+ -+def newSheet(ncols): -+ return Sheet('unnamed', columns=[ColumnItem('', i, width=8) for i in range(ncols)]) -+ -+def inputFilename(prompt, *args, **kwargs): -+ return input(prompt, "filename", *args, completer=completeFilename, **kwargs) -+ -+def completeFilename(val, state): -+ i = val.rfind('/') -+ if i < 0: # no / -+ base = '' -+ partial = val -+ elif i == 0: # root / -+ base = '/' -+ partial = val[1:] -+ else: -+ base = val[:i] -+ partial = val[i+1:] -+ -+ files = [] -+ for f in os.listdir(Path(base or '.').resolve()): -+ if f.startswith(partial): -+ files.append(os.path.join(base, f)) -+ -+ files.sort() -+ return files[state%len(files)] -+ -+def getDefaultSaveName(sheet): -+ src = getattr(sheet, 'source', None) -+ if isinstance(src, Path): -+ return str(src) -+ else: -+ return sheet.name+'.'+getattr(sheet, 'filetype', options.save_filetype) -+ -+def saveSheets(fn, *vsheets, confirm_overwrite=False): -+ 'Save sheet `vs` with given filename `fn`.' -+ givenpath = Path(fn) -+ -+ # determine filetype to save as -+ filetype = '' -+ basename, ext = os.path.splitext(fn) -+ if ext: -+ filetype = ext[1:] -+ -+ filetype = filetype or options.save_filetype -+ -+ if len(vsheets) > 1: -+ if not fn.endswith('/'): # forcibly specify save individual files into directory by ending path with / -+ savefunc = getGlobals().get('multisave_' + filetype, None) -+ if savefunc: -+ # use specific multisave function -+ return savefunc(givenpath, *vsheets) -+ -+ # more than one sheet; either no specific multisave for save filetype, or path ends with / -+ -+ # save as individual files in the givenpath directory -+ if not givenpath.exists(): -+ try: -+ os.makedirs(givenpath.resolve(), exist_ok=True) -+ except FileExistsError: -+ pass -+ -+ assert givenpath.is_dir(), filetype + ' cannot save multiple sheets to non-dir' -+ -+ # get save function to call -+ savefunc = getGlobals().get('save_' + filetype) or fail('no function save_'+filetype) -+ -+ if givenpath.exists(): -+ if confirm_overwrite: -+ confirm('%s already exists. overwrite? ' % fn) -+ -+ status('saving %s sheets to %s' % (len(vsheets), givenpath.fqpn)) -+ for vs in vsheets: -+ p = Path(os.path.join(givenpath.fqpn, vs.name+'.'+filetype)) -+ savefunc(p, vs) -+ else: -+ # get save function to call -+ savefunc = getGlobals().get('save_' + filetype) or fail('no function save_'+filetype) -+ -+ if givenpath.exists(): -+ if confirm_overwrite: -+ confirm('%s already exists. overwrite? ' % fn) -+ -+ status('saving to %s as %s' % (givenpath.fqpn, filetype)) -+ savefunc(givenpath, vsheets[0]) -+ -+ -+class DeferredSetColumn(Column): -+ def __init__(self, *args, **kwargs): -+ super().__init__(*args, **kwargs) -+ self.realsetter = self.setter -+ self.setter = self.deferredSet -+ self._modifiedValues = {} -+ -+ @staticmethod -+ def deferredSet(col, row, val): -+ if col.getValue(row) != val: -+ col._modifiedValues[id(row)] = val -+ -+ def changed(self, row): -+ curval = self.calcValue(row) -+ newval = self._modifiedValues.get(id(row), curval) -+ return self.type(newval) != self.type(curval) -+ -+ def getValue(self, row): -+ if id(row) in self._modifiedValues: -+ return self._modifiedValues.get(id(row)) # overrides cache -+ return Column.getValue(self, row) -+ -+ def __copy__(self): -+ ret = Column.__copy__(self) -+ ret._modifiedValues = collections.OrderedDict() # force a new, unrelated modified set -+ return ret -+ -+ -+def openSource(p, filetype=None): -+ 'calls open_ext(Path) or openurl_scheme(UrlPath, filetype)' -+ if not filetype: -+ filetype = options.filetype -+ if isinstance(p, str): -+ if '://' in p: -+ return openSource(UrlPath(p), filetype) # convert to Path and recurse -+ elif p == '-': -+ return openSource(PathFd('-', vd().stdin), filetype) -+ else: -+ return openSource(Path(p), filetype) # convert to Path and recurse -+ elif isinstance(p, UrlPath): -+ openfunc = 'openurl_' + p.scheme -+ return getGlobals()[openfunc](p, filetype=filetype) -+ elif isinstance(p, Path): -+ if not filetype: -+ filetype = p.suffix or 'txt' -+ -+ if os.path.isdir(p.resolve()): -+ filetype = 'dir' -+ -+ openfunc = 'open_' + filetype.lower() -+ if openfunc not in getGlobals(): -+ warning('no %s function' % openfunc) -+ filetype = 'txt' -+ openfunc = 'open_txt' -+ vs = getGlobals()[openfunc](p) -+ else: # some other object -+ status('unknown object type %s' % type(p)) -+ vs = None -+ -+ if vs: -+ status('opening %s as %s' % (p.name, filetype)) -+ -+ return vs -+ -+#### enable external addons -+def open_vd(p): -+ 'Opens a .vd file as a .tsv file' -+ vs = open_tsv(p) -+ vs.reload() -+ return vs -+ -+def open_txt(p): -+ 'Create sheet from `.txt` file at Path `p`, checking whether it is TSV.' -+ with p.open_text() as fp: -+ if options.delimiter in next(fp): # peek at the first line -+ return open_tsv(p) # TSV often have .txt extension -+ return TextSheet(p.name, p) -+ -+@asyncthread -+def save_txt(p, *vsheets): -+ with p.open_text(mode='w') as fp: -+ for vs in vsheets: -+ col = [vs.visibleCols[0]] -+ for dispvals in genAllValues(vs.rows, [vs.visibleCols[0]]): -+ fp.write(dispvals[0] or '') -+ fp.write('\n') -+ status('%s save finished' % p) -+ -+multisave_txt = save_txt -+ -+ -+def loadInternalSheet(klass, p, **kwargs): -+ 'Load internal sheet of given klass. Internal sheets are always tsv.' -+ vs = klass(p.name, source=p, **kwargs) -+ options._set('encoding', 'utf8', vs) -+ if p.exists(): -+ vd.sheets.insert(0, vs) -+ vs.reload.__wrapped__(vs) -+ vd.sheets.pop(0) -+ return vs +diff --git a/visidata/help.py b/visidata/help.py +index fded3ac5..a7cf2dbb 100644 +--- a/visidata/help.py ++++ b/visidata/help.py +@@ -63,7 +63,7 @@ def openManPage(vd): + from pkg_resources import resource_filename + import os + with SuspendCurses(): +- if os.system(' '.join(['man', resource_filename(__name__, 'man/vd.1')])) != 0: ++ if os.system(' '.join(['man', 'vd.1'])) != 0: + vd.push(TextSheet('man_vd', source=Path(resource_filename(__name__, 'man/vd.txt')))) + + -- 2.26.2 diff --git a/sources b/sources index d974771..6a853be 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -SHA512 (visidata-2.0.1.tar.gz) = d5a3c1ed49e16d6b8c49cdb16142c6fd049273db228c432b37e3c65410b065258b8f33cde99badbe7cb5555621146d264fa8b24be18c199581c71ab71bf35984 +SHA512 (visidata-2.1.tar.gz) = 08b00251a022ca65300ce33c26d416ff03cd7a840dfef580531e1568ae8144232744a7ece21237d7ca5339e8bb26f964487b36e43deed501e7886ff0e3b63668 diff --git a/visidata.spec b/visidata.spec index 9662f97..384bf99 100644 --- a/visidata.spec +++ b/visidata.spec @@ -1,7 +1,7 @@ %global srcname visidata Name: %{srcname} -Version: 2.0.1 +Version: 2.1 Release: 1%{?dist} Summary: Terminal interface for exploring and arranging tabular data @@ -88,6 +88,9 @@ rm -rf %{srcname}.egg-info %changelog +* Sat Dec 12 2020 Elliott Sales de Andrade - 2.1-1 +- Update to latest version (#1904920) + * Mon Oct 12 2020 Elliott Sales de Andrade - 2.0.1-1 - Update to latest version (#1887632)