""" lsv() - a variable listing utility for python, like R's ls() or Matlab
 
Example (Vanilla Python):
 
>>> from lsv import lsv
>>> lsv()
Name   Type             Size
------ ---------- ----------
ag     list               10 [{'gold': 0, 'orig_id': '997', 'worker': 'A17HNBZ
i      int                   163
m      np.float64 (164, 800) [[-1. -1. -1. ..., -1. -1. -1.]  [-1. -1. -1. ...
m2     np.float64 (164, 800) [[ 0. -1. -1. ..., -1. -1. -1.]  [-1. -1. -1. ...
my_ids list               20 ['1000', '1004', '1006', '1007', '1008', '1009', 
num2w  list              164 ['A11GX90QFWDLMM', 'A14JQX7IFAICP0', 'A14Q86RX5HG
s      np.float64        164 [ 30.62001327  19.05619065   8.8599591    8.67849
u      np.float64 (164, 164) [[-0.32339176 -0.3673385   0.0303522  ...,  0.046
v      np.float64 (164, 164) [[-0.32339176 -0.13512814 -0.04440647 ..., -0.043
w2num  dict              164 {'AADS3JU8O57J3': 129, 'A2KST2DIWAB8Z4': 75, 'A1C
wnum   list              164 [(0, 'A11GX90QFWDLMM'), (1, 'A14JQX7IFAICP0'), (2
ww_mat np.float64 (164, 164) [[ 0.  1.  1. ...,  1.  1.  1.]  [ 1.  0.  0. ...
x      dict                5 {'gold': 1, 'orig_id': '605', 'worker': 'A1ZVGUVI
y      tuple               2 (('A2MN1MDFIH9CEW', 'AXBQF8RALCIGV'), 20)
z      list              164 ['A1XUMT8PKEPRR1', 'A2OOM1MEGTT1MJ', 'AWK3LFAT0IH
 

In IPython, you get it magic.

In [100]: import lsv as lsv_mod
In [101]: lsv
"""

# brendan o'connor - anyall.org/code
__all__ = ['lsv']

import numpy
import numpy as np
import os,types,re,sys

lsv_exception_types = (
    types.ModuleType,
    types.FunctionType,
    types.BuiltinFunctionType,
    type,
    numpy.ufunc,
)
# want to kill pollution from "from ___ import *"'s
lsv_exception_names = "T F Out In RTLD_LOCAL RTLD_GLOBAL".split()
for mod in "numpy pylab".split():
  try:  lsv_exception_names += dir(eval(mod))
  except NameError: pass
lsv_exception_names = set(lsv_exception_names)

def lsv(more=None):
  """ Example
  >>> lsv()
  Name   Type             Size
  ------ ---------- ----------
  ag     list               10 [{'gold': 0, 'orig_id': '997', 'worker
  i      int                   163
  m      np.float64 (164, 800) [[-1. -1. -1. ..., -1. -1. -1.]  [-1. 
  m2     np.float64 (164, 800) [[ 0. -1. -1. ..., -1. -1. -1.]  [-1. 
  my_ids list               20 ['1000', '1004', '1006', '1007', '1008
  num2w  list              164 ['A11GX90QFWDLMM', 'A14JQX7IFAICP0', '
  s      np.float64        164 [ 30.62001327  19.05619065   8.8599591
  u      np.float64 (164, 164) [[-0.32339176 -0.3673385   0.0303522  
  v      np.float64 (164, 164) [[-0.32339176 -0.13512814 -0.04440647 
  w2num  dict              164 {'AADS3JU8O57J3': 129, 'A2KST2DIWAB8Z4
  wnum   list              164 [(0, 'A11GX90QFWDLMM'), (1, 'A14JQX7IF
  ww_mat np.float64 (164, 164) [[ 0.  1.  1. ...,  1.  1.  1.]  [ 1. 
  x      dict                5 {'gold': 1, 'orig_id': '605', 'worker'
  y      tuple               2 (('A2MN1MDFIH9CEW', 'AXBQF8RALCIGV'), 
  z      list              164 ['A1XUMT8PKEPRR1', 'A2OOM1MEGTT1MJ', '
  """
  items = globals().items()
  if more: items += more.items()
  # Under IPython, this is virtually empty.  But in vanilla python interpreter,
  # it's chock-full of stuff
  items += sys.modules['__main__'].__dict__.items()
  try:
    items += __IPYTHON__.user_ns.items()
  except NameError: pass
  #print len(items)
  #print [k for (k,v) in items]
  seen = set()
  things = []
  for name,x in items:
    if id(x) in seen: continue
    seen.add(id(x))
    if isinstance(x, lsv_exception_types): continue
    if name in lsv_exception_names: continue
    if re.search('^_i[0-9]+$',name): continue
    if re.search('^_[0-9]+$',name): continue
    if re.search('^_+i*$',name): continue
    if re.search('^__',name): continue
    if re.search('^_',name): continue
    if re.search('^(ANSI_|ITERM|SCREEN|XTERM)',name): continue  # my anyall.org/.pythonrc crap
    if name.startswith('lsv_'): continue
    if isinstance(x, (types.ModuleType,types.ClassType)): continue
    if hasattr(x,'__module__') and x.__module__ in ('__future__','site'): continue
    things.append((name,x))
  things.sort()
  if not things:
    print "[no variables of note]"
    return
  text_cells = [  (name, nice_type_name(x), nice_size(x)) for name,x in things]
  col_widths = [max(len(row[j]) for row in text_cells) for j in range(3)]
  col_widths = [max(wid, 4) for wid in col_widths]

  # e.g.:   "%-15s %-12s %+12s"
  fmt = "%%-%ds %%-%ds %%+%ds" % tuple(col_widths)

  # leftover = num_terminal_columns() - 15-1 - 12-1 - 12-1
  leftover = num_terminal_columns() - sum(col_widths) - 3
  print fmt % ("Name","Type","Size")
  print fmt % ('-'*col_widths[0], '-'*col_widths[1], '-'*col_widths[2])

  fmt2 = fmt + " %s"
  for name,x in things:
    print fmt2 % (name, nice_type_name(x), nice_size(x), nice_oneline_repr(x,leftover))

def nice_oneline_repr(x, limit):
  if isinstance(x, np.ndarray):
    s = str(x)
  elif hasattr(x,'__len__') and len(x)>1000:
    if hasattr(x,'__getslice__'):  return repr(x[:1000])[:limit]
    return '<dict>'
  else:
    s = repr(x)
  s = s.replace("\n", " ")
  return s[:limit]

def nice_type_name(x):
  if hasattr(x,'dtype'):
    s = x.dtype.name
    return "np."+s
  s = repr(type(x))
  m = re.search("<type '([^']+)'>", s)
  if m: s = m.group(1)
  m = re.search(r"^numpy\.(.*)", s)
  if m: s = m.group(1)
  return s

def nice_size(x):
  if hasattr(x,'shape') and len(x.shape) > 1:
    return repr(x.shape)
  if hasattr(x,'__len__') and not isinstance(x,str):
    return str(len(x))
  return ""

def num_terminal_columns():
  try:
    # mac only?
    m = re.search("([0-9]+) columns", os.popen("stty -a").read())
    if m: return int(m.group(1))
  except OSError: pass
  return 80


# turn it into ipython magic %lsv or just 'lsv'
try:
  __IPYTHON__
  from IPython.iplib import InteractiveShell
  _lsv = lsv
  InteractiveShell.magic_lsv = lambda self,parameter_s='': _lsv()
except (NameError, ImportError):
  pass
