pmacs3/ctags.py

219 lines
7.4 KiB
Python
Raw Normal View History

2007-03-06 10:05:38 -05:00
#!/usr/bin/python
#
# by Erik Osheim
import os, re, sets, sys
entries = {}
packages = {}
classes = {}
class_methods = {}
class_re = re.compile('^(.*?)\t(.*?)\t(.*?)\tc$')
2007-03-06 10:05:38 -05:00
function_re = re.compile('^(.*?)\t(.*?)\t(.*?)\tf$')
method_re = re.compile('^([^\t]+)\t([^\t]+)\t([^\t]+)\tm\tclass:([^\t]+)(?:\t.*)?$')
2007-03-06 10:05:38 -05:00
class_supers_re = re.compile('^\/\^ *class +[_A-Za-z][_A-Za-z0-9]* *\((.*?)\) *: *\$\/;\"$')
def_args_re = re.compile('^\/\^ *def +[_A-Za-z][_A-Za-z0-9]* *\((.*?)\) *: *\$\/;\"$')
find_args_re = re.compile('[\*_a-zA-Z][\*_a-zA-Z0-9]*(?:=(?:[^,\'" ]+|"(?:\\.|[^\\"])*"|\'(?:\\.|[^\\"])*\'))?')
2007-03-06 10:05:38 -05:00
base_objects = sets.Set(['object', 'list', 'dict'])
def is_fully_qualified(s):
return s in base_objects or '.' in s
def parse_entry(line):
m = class_re.match(line)
if m:
return ClassEntry(m.group(1), m.group(2), m.group(3))
m = function_re.match(line)
if m:
return FunctionEntry(m.group(1), m.group(2), m.group(3))
m = method_re.match(line)
if m:
return MethodEntry(m.group(1), m.group(2), m.group(3), m.group(4))
raise Exception, "Oh no: %s" % line
class Entry:
type = 'generic'
def __init__(self, symbol, path):
self.symbol = symbol
self.path = path
def __repr__(self):
return '<%s %s.%s>' % (self.type.title(), self.package(), self.symbol)
def package(self):
return self.path[:-3].replace('/', '.')
def fullname(self):
return '%s.%s' % (self.package(), self.symbol)
def prototype(self):
return self.fullname()
def dump(self):
return '%s %s' % (self.type, self.prototype())
class ClassEntry(Entry):
type = 'class'
def __init__(self, symbol, path, match):
Entry.__init__(self, symbol, path)
m = class_supers_re.match(match)
self.match = match
self.supers = []
if m:
self.supers = [x.strip() for x in m.group(1).split(',')]
for i in range(0, len(self.supers)):
if not is_fully_qualified(self.supers[i]):
self.supers[i] = '%s.%s' % (self.package(), self.supers[i])
def prototype(self):
return '%s(%s)' % (self.fullname(), ', '.join(self.supers))
class FunctionEntry(Entry):
type = 'function'
def __init__(self, symbol, path, match):
Entry.__init__(self, symbol, path)
m = def_args_re.match(match)
self.match = match
self.args = []
if m:
self.args = re.findall(find_args_re, m.group(1))
def prototype(self):
return '%s(%s)' % (self.fullname(), ', '.join(self.args))
class MethodEntry(FunctionEntry):
type = 'method'
def __init__(self, symbol, path, match, parent):
FunctionEntry.__init__(self, symbol, path, match)
self.parent = parent
if is_fully_qualified(parent):
self.parent = parent
else:
self.parent = '%s.%s' % (self.package(), parent)
def fullname(self):
return '%s.%s' % (self.parent, self.symbol)
def process_tagfile(path):
global entries, classes, packages
f = open(path, 'r')
data = f.read()
f.close()
process_data(data)
def process_direct():
(stdin, stdout, stderr) = os.popen3("exuberant-ctags -L - -f -")
for base in ('.'):
for root, dirs, files in os.walk(base):
if 'CVS' in dirs:
dirs.remove('CVS')
for name in files:
if name.endswith('.py'):
if base != '.':
path = os.path.join(root, name)
else:
path = name
stdin.write('%s\n' % path)
stdin.flush()
stdin.close()
data = stdout.read()
stdout.close()
stderr.close()
process_data(data)
def process_data(data):
global entries, classes, packages
# process the ctags output data
for l in data.split('\n'):
if not l:
continue
elif l.startswith('!'):
continue
else:
e = parse_entry(l)
entries[e.fullname()] = e
package = e.package()
if e.type == 'method':
p = e.parent
if not is_fully_qualified(p):
p = '%s.%s' % (package, p)
classes.setdefault(p, {})
classes[p][e.symbol] = e
else:
packages.setdefault(package, {})
packages[package][e.symbol] = e
# this returns the methods available in the class
def get_methods_for_class(c):
global class_methods
cn = c.fullname()
# if we haven't determined this classes methods yet, then let's do it
if cn not in class_methods:
classes_seen = sets.Set()
methods_seen = sets.Set()
class_methods[cn] = []
# create a queue of classes to process... this solves the ordering
# problem for class inheritance...i.e.:
# class Shape
# class Rectangle(Shape)
# class Rhombus(Shape)
# class Square(Rectangle, Rhombus)
# 1. [Square] --> process Square --> [Rectangle, Rhombus]
# 2. [Rectangle, Rhombus] --> process Rectangle --> [Rhombus, Shape]
# 3. [Rhombus, Shape] --> process Rhombus --> [Shape, Shape]
# 4. [Shape, Shape] --> process Shape --> [Shape]
# 5. [Shape] --> already processed Shape, skipping
to_process = [c]
while to_process:
e = to_process.pop(0)
fn = e.fullname()
# if we've seen this class already, then skip it
if fn in classes_seen:
continue
# mark that we've seen this class; if we don't know about it's
# methods, then let's just skip it.
classes_seen.add(fn)
if fn not in classes:
continue
# for each method in the class, add it to our list if it's new
for msymbol in classes[fn]:
if msymbol not in methods_seen:
class_methods[cn].append(classes[fn][msymbol])
methods_seen.add(msymbol)
# for each parent of this class, append it to the end of the queue
# if we know about it
for sfn in e.supers:
if sfn in entries:
to_process.append(entries[sfn])
return class_methods[cn]
if __name__ == "__main__":
if len(sys.argv[1:]) == 0:
process_direct()
else:
process_tagfile(sys.argv[1])
# exit here to get good timing data
#sys.exit(0)
# for each package, print out the classes and functions in that package
for p in packages:
print 'package %s' % p
for es in packages[p]:
e = packages[p][es]
print ' %s %s' % (e.type, e.prototype())
fn = e.fullname()
# for each class, print out the methods that class provides (either
# implemented directly or inherited from a super class)
if e.type == 'class':
for m in get_methods_for_class(e):
# status determines whether the class is being inherited,
# or implemented directly
if fn != m.parent:
status = '*'
else:
status = ' '
print ' %s %s' % (status, m.dump())
print ''