pmacs3/ctags.py

128 lines
3.4 KiB
Python
Executable File

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:
next(itr)
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)
return
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:
print("records for %r:" % sys.argv[2])
for r in ctags.get(sys.argv[2]):
print(r)
else:
pprint(ctags.records)