Skip to content

Instantly share code, notes, and snippets.

@ajkerrigan
Last active April 11, 2023 12:58
Show Gist options
  • Save ajkerrigan/3c1a441ef829d6ab4dbfddfb27ffbabf to your computer and use it in GitHub Desktop.
Save ajkerrigan/3c1a441ef829d6ab4dbfddfb27ffbabf to your computer and use it in GitHub Desktop.
Hacking around on VisiData auto-run commands

VisiData Auto-Run Commands

I've mucked around with the idea of auto-running commands when a sheet opens before. I don't remember which walls I hit, but saulpw/visidata#1681 got me curious to try the idea of a post-load hook in .visidatarc again.

The contents of post_load_hook.py could be dropped directly into a .visidatarc or kept in its own file and imported from .visidatarc.

This is just a sample of course, but seems to work for some common cases at least. If I try to bulk load a bunch of sheets and reload all of them simultaneously I do get this traceback which suggests I need to do something smarter:

 Traceback (most recent call last):                                                                                        ║
   File "/home/aj/.local/pipx/venvs/visidata/lib/python3.11/site-packages/visidata/basesheet.py", line 209, in execCommand ║
     vd.cmdlog.afterExecSheet(vd.activeSheet, escaped, err)                                                                ║
   File "/home/aj/.local/pipx/venvs/visidata/lib/python3.11/site-packages/visidata/macros.py", line 71, in afterExecSheet  ║
     cmdlog.afterExecSheet.__wrapped__(cmdlog, sheet, escaped, err)                                                        ║
   File "/home/aj/.local/pipx/venvs/visidata/lib/python3.11/site-packages/visidata/cmdlog.py", line 217, in afterExecSheet ║
     if not sheet.cmdlog.rows or vd.isLoggableCommand(vd.activeCommand.longname):                                          ║
                                                      ^^^^^^^^^^^^^^^^^^^^^^^^^                                            ║
 AttributeError: 'NoneType' object has no attribute 'longname'

But hey, omelettes and eggs and all that...

import time
from functools import wraps
from visidata import asyncthread, vd, BaseSheet, UNLOADED
from visidata.features.unfurl import unfurl_col
from visidata.loaders.parquet import ParquetSheet
from visidata.pyobj import expand_cols_deep
def post_load(sheet):
"""Run some commands anytime a sheet loads
Also maybe run some commands that are sheet-specific.
This _could_ be fancier and probably smarter, but seems to
work for the moment as-is.
If it looks like the sheet is in UNLOADED status, try hooking
post_load hooks up to sheet.reload instead.
"""
if sheet.rows is UNLOADED:
setup_post_load_hook(sheet, "reload")
return
if isinstance(sheet, ParquetSheet):
vd.status("Do something parquet-specific")
if sheet.name == 'c7n_cache':
# Adapted from https://gist.github.com/ajkerrigan/1214d4632b9e46cfa77c024d7c12f23d
# to unpickle/expand Cloud Custodian cache data on open
import pickle
key_col = sheet.colsByName['key']
value_col = sheet.colsByName['value']
vd.addGlobals({'pickle': pickle})
key_col.setValuesFromExpr(sheet.rows, 'pickle.loads(key)')
expand_cols_deep(sheet, [key_col], depth=1)
key_col.hide()
value_col.setValuesFromExpr(sheet.rows, 'pickle.loads(value)')
new_sheet = unfurl_col(sheet, value_col)
vd.push(new_sheet)
new_sheet.colsByName['value_key'].hide()
value_col = new_sheet.colsByName['value_value']
value_col.name = 'value'
expand_cols_deep(new_sheet, [value_col], depth=1)
value_col.hide()
new_sheet.execCommand("resize-cols-max")
new_sheet.execCommand("go-leftmost")
sheet.execCommand("go-top")
sheet.execCommand("resize-cols-max")
def setup_post_load_hook(obj, func):
"""
Set up a hook to run commands when a sheet finishes loading (ish).
Plan A: Sheet is loading as soon as it's pushed. Wait until it looks
like the load finished, and kick off some auto-commands.
Plan B: Sheet hangs in UNLOADED status for a while. Figure that out
later.
"""
f = getattr(obj, func)
@asyncthread
def post_load_hook(*args, **kwargs):
sheet = obj if isinstance(obj, BaseSheet) else args[0]
time.sleep(.1)
while {t.name for t in sheet.currentThreads} - {"post_load_hook"}:
vd.status("waiting to execute post_load hook")
time.sleep(.1)
vd.status("running post_load hook")
post_load(sheet)
@wraps(f)
def wrapper(*args, **kwargs):
f(*args, **kwargs)
post_load_hook(*args, **kwargs)
setattr(obj, func, wrapper)
vd.status(f"{obj.__class__.__name__}.{func}() wrapped to setup post-load hooks")
setup_post_load_hook(vd, "push")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment