2010-08-16 23:45:33 -04:00
|
|
|
"""
|
|
|
|
Etags parser
|
|
|
|
|
|
|
|
:author: Dan Williams
|
|
|
|
"""
|
|
|
|
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
from stat import ST_MTIME
|
|
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
|
|
|
|
|
|
class EtagsRunError(Exception): pass
|
|
|
|
|
|
|
|
class TagManager(object):
|
|
|
|
lang = None
|
2011-11-08 22:31:11 -05:00
|
|
|
prune = set(['SCCS', 'RCS', 'CVS', '.svn', '.hg', '.git', '.bzr'])
|
2010-08-16 23:45:33 -04:00
|
|
|
exts = set()
|
|
|
|
|
|
|
|
def __init__(self, base='.'):
|
2012-12-05 14:33:51 -05:00
|
|
|
self.db = None
|
2010-08-16 23:45:33 -04:00
|
|
|
self.base = base
|
|
|
|
self.path = os.path.join(self.base, 'TAGS')
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def has(self, name):
|
2012-12-05 14:33:51 -05:00
|
|
|
return name in self.db.tags
|
2010-08-16 23:45:33 -04:00
|
|
|
|
|
|
|
def get(self, name):
|
2012-12-05 14:33:51 -05:00
|
|
|
return self.db.get(name, [])
|
2010-08-16 23:45:33 -04:00
|
|
|
|
2010-08-17 07:47:52 -04:00
|
|
|
def list(self):
|
2012-12-05 14:33:51 -05:00
|
|
|
return self.db.records
|
2010-08-17 07:47:52 -04:00
|
|
|
|
2010-08-16 23:45:33 -04:00
|
|
|
def update(self):
|
|
|
|
if self.is_outdated():
|
|
|
|
self.run()
|
2012-12-05 14:33:51 -05:00
|
|
|
self.db = Etags(self.path)
|
|
|
|
self.db.parse()
|
2010-08-16 23:45:33 -04:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
lf = '--language-force=%s' % self.lang
|
|
|
|
args = ['ctags', '-e', '-f', self.path, lf, '-L-']
|
|
|
|
pipe = Popen(args, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
|
|
|
|
indata = '\n'.join(self.get_paths()) + '\n'
|
|
|
|
outdata = pipe.communicate(indata)
|
|
|
|
if pipe.returncode != 0:
|
|
|
|
raise EtagsRunError(outdata)
|
|
|
|
|
|
|
|
def get_paths(self):
|
|
|
|
return list(self._walk(mtime=0))
|
|
|
|
|
|
|
|
def is_outdated(self):
|
|
|
|
if not os.path.exists(self.path):
|
|
|
|
return True
|
|
|
|
mtime = os.stat(self.path)[ST_MTIME]
|
|
|
|
itr = self._walk(mtime)
|
|
|
|
try:
|
|
|
|
itr.next()
|
|
|
|
return True
|
|
|
|
except StopIteration:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _walk(self, mtime=0):
|
|
|
|
paths = []
|
|
|
|
for root, dirs, files in os.walk(self.base):
|
|
|
|
for d in dirs:
|
|
|
|
if d in self.prune:
|
|
|
|
dirs.remove(d)
|
|
|
|
for f in files:
|
|
|
|
path = os.path.join(root, f)
|
|
|
|
if not self._match(path):
|
|
|
|
continue
|
|
|
|
elif os.stat(path)[ST_MTIME] < mtime:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
yield os.path.join(root, f)
|
|
|
|
raise StopIteration
|
|
|
|
|
|
|
|
def _match(self, path):
|
|
|
|
_, ext = os.path.splitext(path)
|
|
|
|
return ext in self.exts
|
|
|
|
|
|
|
|
class Etags(object):
|
|
|
|
def __init__(self, fname=None):
|
|
|
|
self.fname = fname
|
|
|
|
self.rawdata = None
|
2012-12-05 14:33:51 -05:00
|
|
|
self.records = []
|
|
|
|
self.tags = {}
|
2010-08-16 23:45:33 -04:00
|
|
|
|
|
|
|
def _load(self):
|
|
|
|
fd = file(self.fname, 'r')
|
|
|
|
self.rawdata = fd.read()
|
|
|
|
fd.close()
|
|
|
|
|
2012-12-05 14:33:51 -05:00
|
|
|
def get(self, name):
|
|
|
|
return self.tags.get(name, [])
|
2010-08-16 23:45:33 -04:00
|
|
|
|
|
|
|
def parse(self, fname=None):
|
|
|
|
"""
|
|
|
|
Parser is based on the little info found in
|
|
|
|
Wikipedia: http://en.wikipedia.org/wiki/Ctags
|
|
|
|
"""
|
|
|
|
if fname:
|
|
|
|
self.fname = fname
|
|
|
|
self._load()
|
|
|
|
i = 0
|
|
|
|
data_len = len(self.rawdata)
|
|
|
|
data = self.rawdata
|
|
|
|
while i < data_len:
|
|
|
|
if ord(data[i]) == 0xc:
|
|
|
|
i = self._parse_block(data, i+2)
|
|
|
|
|
|
|
|
def _add_record(self, record):
|
2012-12-05 14:33:51 -05:00
|
|
|
self.records.append(record)
|
2010-08-16 23:45:33 -04:00
|
|
|
name = record.name
|
|
|
|
if name is None:
|
|
|
|
return
|
2012-12-05 14:33:51 -05:00
|
|
|
self.tags.setdefault(name, [])
|
|
|
|
self.tags[name].append(record)
|
2010-08-16 23:45:33 -04:00
|
|
|
|
|
|
|
def _parse_block(self, data, i):
|
|
|
|
n = data[i:].find('\n') + i
|
|
|
|
l = data[i:n]
|
|
|
|
try:
|
|
|
|
filename, size = l.split(',')
|
2012-12-05 14:33:51 -05:00
|
|
|
except ValueError, e:
|
|
|
|
raise Exception("parse failed(%s): %r %r %r" % (i, l, e, data))
|
2010-08-16 23:45:33 -04:00
|
|
|
size = int(size)
|
|
|
|
subblock = data[n+1:n+size+1]
|
2011-11-08 22:31:11 -05:00
|
|
|
|
2010-08-16 23:45:33 -04:00
|
|
|
# ...
|
|
|
|
for lineitem in subblock.split('\n'):
|
2011-11-08 22:31:11 -05:00
|
|
|
if len(lineitem) == 0: continue
|
2010-08-16 23:45:33 -04:00
|
|
|
record = self._parse_record(lineitem, filename)
|
|
|
|
self._add_record(record)
|
2011-11-08 22:31:11 -05:00
|
|
|
return n + size + 1
|
2010-08-16 23:45:33 -04:00
|
|
|
|
|
|
|
def _parse_record(self, lineitem, filename):
|
|
|
|
try:
|
|
|
|
defn, rest = lineitem.split(chr(0x7f))
|
|
|
|
except ValueError:
|
|
|
|
print lineitem
|
|
|
|
raise
|
|
|
|
name = None
|
|
|
|
if chr(0x01) in rest:
|
|
|
|
name, rest = rest.split(chr(0x01))
|
|
|
|
else:
|
|
|
|
txt = defn.strip()
|
|
|
|
sp = re.split('[ ,;*()\t&=]', txt)
|
|
|
|
sp = [x for x in sp if x != '']
|
|
|
|
if len(sp):
|
|
|
|
name = sp[-1]
|
|
|
|
|
|
|
|
tokens = rest.split(',')
|
|
|
|
line = int(tokens[0])
|
|
|
|
byte = int(tokens[1])
|
|
|
|
|
|
|
|
record = EtagRecord(path=filename, defn=defn, name=name, line=line, byte=byte)
|
|
|
|
return record
|
|
|
|
|
|
|
|
class EtagRecord(object):
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
self.path = None
|
|
|
|
self.defn = None
|
|
|
|
self.name = None
|
|
|
|
self.line = -1
|
|
|
|
self.byte = None
|
|
|
|
self.__dict__.update(kwargs)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "%s [%s:%d]" % (self.name, self.path, self.line)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
import sys
|
|
|
|
from pprint import pprint
|
2011-11-08 22:31:11 -05:00
|
|
|
|
2010-08-16 23:45:33 -04:00
|
|
|
etags = Etags(sys.argv[1])
|
|
|
|
etags.parse()
|
2011-11-08 22:31:11 -05:00
|
|
|
|
2010-08-16 23:45:33 -04:00
|
|
|
if len(sys.argv) > 2:
|
2012-12-05 14:33:51 -05:00
|
|
|
print etags.get(sys.argv[2])
|
2010-08-16 23:45:33 -04:00
|
|
|
else:
|
2012-12-05 14:33:51 -05:00
|
|
|
pprint(etags.records)
|