2012-12-05 14:33:51 -05:00
|
|
|
import os
|
|
|
|
import re
|
|
|
|
from stat import ST_MTIME
|
|
|
|
from subprocess import Popen, PIPE, STDOUT
|
|
|
|
|
|
|
|
class CtagsRunError(Exception): pass
|
|
|
|
class CtagsParseError(Exception): pass
|
|
|
|
|
|
|
|
# ctags --languages=... -R -excmd=number
|
|
|
|
|
|
|
|
class TagManager(object):
|
|
|
|
lang = None
|
|
|
|
prune = set(['SCCS', 'RCS', 'CVS', '.svn', '.hg', '.git', '.bzr'])
|
|
|
|
exts = set()
|
|
|
|
|
|
|
|
def __init__(self, base='.'):
|
|
|
|
self.db = None
|
|
|
|
self.base = base
|
|
|
|
self.path = os.path.join(self.base, 'tags')
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def has(self, name):
|
|
|
|
return name in self.db.tags
|
|
|
|
|
|
|
|
def get(self, name):
|
|
|
|
return self.db.tags.get(name, [])
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
if self.is_outdated():
|
|
|
|
self.generate()
|
|
|
|
self.db = Ctags(self.path)
|
|
|
|
self.db.parse()
|
|
|
|
|
|
|
|
def generate(self):
|
|
|
|
args = ['ctags', '-f', self.path, '-L-', '--excmd=number']
|
|
|
|
if self.lang:
|
|
|
|
args.append('--language-force=%s' % self.lang)
|
|
|
|
|
|
|
|
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 CtagsRunError("%r: %r" % (args, outdata))
|
|
|
|
|
|
|
|
def get_paths(self):
|
|
|
|
return list(self._walk())
|
|
|
|
|
|
|
|
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:
|
2020-08-31 20:58:27 -04:00
|
|
|
next(itr)
|
2012-12-05 14:33:51 -05:00
|
|
|
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)
|
2020-08-31 20:58:27 -04:00
|
|
|
return
|
2012-12-05 14:33:51 -05:00
|
|
|
|
|
|
|
def _match(self, path):
|
|
|
|
return os.path.splitext(path)[1] in self.exts
|
|
|
|
|
|
|
|
class Ctags(object):
|
|
|
|
line_re = re.compile(r'^(.+?)\t(.+?)\t(.+?);"(.+)$')
|
|
|
|
|
|
|
|
def __init__(self, path):
|
|
|
|
self.path = path
|
|
|
|
self.records = []
|
|
|
|
self.tags = {}
|
|
|
|
|
|
|
|
def get(self, tag):
|
|
|
|
return self.tags.get(tag, [])
|
|
|
|
|
|
|
|
def parse(self):
|
|
|
|
for line in open(self.path, 'r'):
|
|
|
|
if line.startswith('!'): continue
|
|
|
|
|
|
|
|
m = self.line_re.match(line)
|
|
|
|
if not m: raise CtagsParseError("%r" % line)
|
|
|
|
|
|
|
|
name, path, ex, _ = m.groups()
|
|
|
|
self.add(CtagRecord(name, path, ex))
|
|
|
|
|
|
|
|
def add(self, r):
|
|
|
|
self.records.append(r)
|
|
|
|
self.tags.setdefault(r.name, [])
|
|
|
|
self.tags[r.name].append(r)
|
|
|
|
|
|
|
|
class CtagRecord(object):
|
|
|
|
def __init__(self, name, path, ex):
|
|
|
|
if not ex.isdigit():
|
|
|
|
raise Exception("excmd %r unsupported; use --excmd=number" % ex)
|
|
|
|
self.name = name
|
|
|
|
self.path = path
|
|
|
|
self.line = int(ex)
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return "CtagRecord(%r, %r, %d)" % (self.name, self.path, self.line)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
import sys
|
|
|
|
from pprint import pprint
|
|
|
|
|
|
|
|
ctags = Ctags(sys.argv[1])
|
|
|
|
ctags.parse()
|
|
|
|
|
|
|
|
if len(sys.argv) > 2:
|
2020-08-31 20:58:27 -04:00
|
|
|
print("records for %r:" % sys.argv[2])
|
2012-12-05 14:33:51 -05:00
|
|
|
for r in ctags.get(sys.argv[2]):
|
2020-08-31 20:58:27 -04:00
|
|
|
print(r)
|
2012-12-05 14:33:51 -05:00
|
|
|
else:
|
|
|
|
pprint(ctags.records)
|