Source code for visidata.expr

import time

from visidata import Progress, Sheet, Column, asyncthread, vd, Column


[docs]class ExprColumn(Column): 'Column using *expr* to derive the value from each row.' def __init__(self, name, expr=None, **kwargs): super().__init__(name, **kwargs) self.expr = expr or name self.ncalcs = 0 self.totaltime = 0 self.maxtime = 0 def calcValue(self, row): t0 = time.perf_counter() r = self.sheet.evalExpr(self.compiledExpr, row, col=self, curcol=self) t1 = time.perf_counter() self.ncalcs += 1 self.maxtime = max(self.maxtime, t1-t0) self.totaltime += (t1-t0) return r def putValue(self, row, val): a = self.getDisplayValue(row) b = self.format(self.type(val)) if a != b: vd.warning("Cannot change value of calculated column. Use `'` to freeze column.") @property def expr(self): return self._expr @expr.setter def expr(self, expr): self.compiledExpr = compile(expr, '<expr>', 'eval') if expr else None self._expr = expr
class CompleteExpr: def __init__(self, sheet=None): self.varnames = [] if sheet: self.varnames.extend(sorted(col.name for col in sheet.columns)) self.varnames.extend(sorted(x for x in vd.getGlobals())) for c in vd.contexts: self.varnames.extend(sorted(x for x in dir(c))) def __call__(self, val, state): i = len(val)-1 while val[i:].isidentifier() and i >= 0: i -= 1 if i < 0: base = '' partial = val elif val[i] == '.': # no completion of attributes return None else: base = val[:i+1] partial = val[i+1:] # Remove unmatching and duplicate completions varnames_dict = {x:None for x in self.varnames if x.startswith(partial)} varnames = list(varnames_dict.keys()) if not varnames: return val return base + varnames[state%len(varnames)] @Column.api @asyncthread def setValuesFromExpr(self, rows, expr, **kwargs): 'Set values in this column for *rows* to the result of the Python expression *expr* applied to each row.' compiledExpr = compile(expr, '<expr>', 'eval') vd.addUndoSetValues([self], rows) nset = 0 for row in Progress(rows, 'setting'): # Note: expressions that are only calculated once, do not need to pass column identity # they can reference their "previous selves" once without causing a recursive problem try: v = self.sheet.evalExpr(compiledExpr, row, **kwargs) self.setValue(row, v) nset += 1 except Exception as e: vd.exceptionCaught(e) self.recalc() vd.status(f'set {nset} values = {expr}') @Sheet.api def inputExpr(self, prompt, *args, **kwargs): return vd.input(prompt, "expr", *args, completer=CompleteExpr(self), **kwargs) Sheet.addCommand('=', 'addcol-expr', 'addColumnAtCursor(ExprColumn(inputExpr("new column expr="), col=cursorCol, curcol=cursorCol))', 'create new column from Python expression, with column names as variables') Sheet.addCommand('g=', 'setcol-expr', 'cursorCol.setValuesFromExpr(someSelectedRows, inputExpr("set selected="), curcol=cursorCol)', 'set current column for selected rows to result of Python expression') Sheet.addCommand('z=', 'setcell-expr', 'cursorCol.setValues([cursorRow], evalExpr(inputExpr("set expr="), row=cursorRow, curcol=cursorCol))', 'evaluate Python expression on current row and set current cell with result of Python expression') Sheet.addCommand('gz=', 'setcol-iter', 'cursorCol.setValues(someSelectedRows, *list(itertools.islice(eval(input("set column= ", "expr", completer=CompleteExpr())), len(someSelectedRows))))', 'set current column for selected rows to the items in result of Python sequence expression') Sheet.addCommand(None, 'show-expr', 'status(evalExpr(inputExpr("show expr="), row=cursorRow, curcol=cursorCol))', 'evaluate Python expression on current row and show result on status line') vd.addGlobals( ExprColumn=ExprColumn, ColumnExpr=ExprColumn, CompleteExpr=CompleteExpr, ) vd.addMenuItems(''' Edit > Modify > current cell > Python expression > setcell-expr Edit > Modify > selected cells > Python sequence > setcol-expr Column > Add column > Python expr > addcol-expr ''')