mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 03:44:55 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			208 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			208 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
'''Parse a Python file and retrieve classes and methods.
 | 
						|
 | 
						|
Parse enough of a Python file to recognize class and method
 | 
						|
definitions and to find out the superclasses of a class.
 | 
						|
 | 
						|
The interface consists of a single function:
 | 
						|
	readmodule(module, path)
 | 
						|
module is the name of a Python module, path is an optional list of
 | 
						|
directories where the module is to be searched.  If present, path is
 | 
						|
prepended to the system search path sys.path.
 | 
						|
The return value is a dictionary.  The keys of the dictionary are
 | 
						|
the names of the classes defined in the module (including classes
 | 
						|
that are defined via the from XXX import YYY construct).  The values
 | 
						|
are class instances of the class Class defined here.
 | 
						|
 | 
						|
A class is described by the class Class in this module.  Instances
 | 
						|
of this class have the following instance variables:
 | 
						|
	name -- the name of the class
 | 
						|
	super -- a list of super classes (Class instances)
 | 
						|
	methods -- a dictionary of methods
 | 
						|
	file -- the file in which the class was defined
 | 
						|
	lineno -- the line in the file on which the class statement occurred
 | 
						|
The dictionary of methods uses the method names as keys and the line
 | 
						|
numbers on which the method was defined as values.
 | 
						|
If the name of a super class is not recognized, the corresponding
 | 
						|
entry in the list of super classes is not a class instance but a
 | 
						|
string giving the name of the super class.  Since import statements
 | 
						|
are recognized and imported modules are scanned as well, this
 | 
						|
shouldn't happen often.
 | 
						|
 | 
						|
BUGS
 | 
						|
Continuation lines are not dealt with at all and strings may confuse
 | 
						|
the hell out of the parser, but it usually works.'''
 | 
						|
 | 
						|
import os
 | 
						|
import sys
 | 
						|
import imp
 | 
						|
import regex
 | 
						|
import string
 | 
						|
 | 
						|
id = '\\(<id>[A-Za-z_][A-Za-z0-9_]*\\)'	# match identifier
 | 
						|
blank_line = regex.compile('^[ \t]*\\($\\|#\\)')
 | 
						|
is_class = regex.symcomp('^class[ \t]+'+id+'[ \t]*\\(<sup>([^)]*)\\)?[ \t]*:')
 | 
						|
is_method = regex.symcomp('^[ \t]+def[ \t]+'+id+'[ \t]*(')
 | 
						|
is_import = regex.symcomp('^import[ \t]*\\(<imp>[^#]+\\)')
 | 
						|
is_from = regex.symcomp('^from[ \t]+'+id+'[ \t]+import[ \t]+\\(<imp>[^#]+\\)')
 | 
						|
dedent = regex.compile('^[^ \t]')
 | 
						|
indent = regex.compile('^[^ \t]*')
 | 
						|
 | 
						|
_modules = {}				# cache of modules we've seen
 | 
						|
 | 
						|
# each Python class is represented by an instance of this class
 | 
						|
class Class:
 | 
						|
	'''Class to represent a Python class.'''
 | 
						|
	def __init__(self, module, name, super, file, lineno):
 | 
						|
		self.module = module
 | 
						|
		self.name = name
 | 
						|
		if super is None:
 | 
						|
			super = []
 | 
						|
		self.super = super
 | 
						|
		self.methods = {}
 | 
						|
		self.file = file
 | 
						|
		self.lineno = lineno
 | 
						|
 | 
						|
	def _addmethod(self, name, lineno):
 | 
						|
		self.methods[name] = lineno
 | 
						|
 | 
						|
def readmodule(module, path = []):
 | 
						|
	'''Read a module file and return a dictionary of classes.
 | 
						|
 | 
						|
	Search for MODULE in PATH and sys.path, read and parse the
 | 
						|
	module and return a dictionary with one entry for each class
 | 
						|
	found in the module.'''
 | 
						|
 | 
						|
	if _modules.has_key(module):
 | 
						|
		# we've seen this module before...
 | 
						|
		return _modules[module]
 | 
						|
	if module in sys.builtin_module_names:
 | 
						|
		# this is a built-in module
 | 
						|
		dict = {}
 | 
						|
		_modules[module] = dict
 | 
						|
		return dict
 | 
						|
 | 
						|
	# search the path for the module
 | 
						|
	f = None
 | 
						|
	suffixes = imp.get_suffixes()
 | 
						|
	for dir in path + sys.path:
 | 
						|
		for suff, mode, type in suffixes:
 | 
						|
			file = os.path.join(dir, module + suff)
 | 
						|
			try:
 | 
						|
				f = open(file, mode)
 | 
						|
			except IOError:
 | 
						|
				pass
 | 
						|
			else:
 | 
						|
				# found the module
 | 
						|
				break
 | 
						|
		if f:
 | 
						|
			break
 | 
						|
	if not f:
 | 
						|
		raise IOError, 'module ' + module + ' not found'
 | 
						|
	if type != imp.PY_SOURCE:
 | 
						|
		# not Python source, can't do anything with this module
 | 
						|
		f.close()
 | 
						|
		dict = {}
 | 
						|
		_modules[module] = dict
 | 
						|
		return dict
 | 
						|
 | 
						|
	cur_class = None
 | 
						|
	dict = {}
 | 
						|
	_modules[module] = dict
 | 
						|
	imports = []
 | 
						|
	lineno = 0
 | 
						|
	while 1:
 | 
						|
		line = f.readline()
 | 
						|
		if not line:
 | 
						|
			break
 | 
						|
		lineno = lineno + 1	# count lines
 | 
						|
		line = line[:-1]	# remove line feed
 | 
						|
		if blank_line.match(line) >= 0:
 | 
						|
			# ignore blank (and comment only) lines
 | 
						|
			continue
 | 
						|
##		if indent.match(line) >= 0:
 | 
						|
##			indentation = len(string.expandtabs(indent.group(0), 8))
 | 
						|
		if is_import.match(line) >= 0:
 | 
						|
			# import module
 | 
						|
			for n in string.splitfields(is_import.group('imp'), ','):
 | 
						|
				n = string.strip(n)
 | 
						|
				try:
 | 
						|
					# recursively read the
 | 
						|
					# imported module
 | 
						|
					d = readmodule(n, path)
 | 
						|
				except:
 | 
						|
					print 'module',n,'not found'
 | 
						|
					pass
 | 
						|
			continue
 | 
						|
		if is_from.match(line) >= 0:
 | 
						|
			# from module import stuff
 | 
						|
			mod = is_from.group('id')
 | 
						|
			names = string.splitfields(is_from.group('imp'), ',')
 | 
						|
			try:
 | 
						|
				# recursively read the imported module
 | 
						|
				d = readmodule(mod, path)
 | 
						|
			except:
 | 
						|
				print 'module',mod,'not found'
 | 
						|
				continue
 | 
						|
			# add any classes that were defined in the
 | 
						|
			# imported module to our name space if they
 | 
						|
			# were mentioned in the list
 | 
						|
			for n in names:
 | 
						|
				n = string.strip(n)
 | 
						|
				if d.has_key(n):
 | 
						|
					dict[n] = d[n]
 | 
						|
				elif n == '*':
 | 
						|
					# only add a name if not
 | 
						|
					# already there (to mimic what
 | 
						|
					# Python does internally)
 | 
						|
					# also don't add names that
 | 
						|
					# start with _
 | 
						|
					for n in d.keys():
 | 
						|
						if n[0] != '_' and \
 | 
						|
						   not dict.has_key(n):
 | 
						|
							dict[n] = d[n]
 | 
						|
			continue
 | 
						|
		if is_class.match(line) >= 0:
 | 
						|
			# we found a class definition
 | 
						|
			class_name = is_class.group('id')
 | 
						|
			inherit = is_class.group('sup')
 | 
						|
			if inherit:
 | 
						|
				# the class inherits from other classes
 | 
						|
				inherit = string.strip(inherit[1:-1])
 | 
						|
				names = []
 | 
						|
				for n in string.splitfields(inherit, ','):
 | 
						|
					n = string.strip(n)
 | 
						|
					if dict.has_key(n):
 | 
						|
						# we know this super class
 | 
						|
						n = dict[n]
 | 
						|
					else:
 | 
						|
						c = string.splitfields(n, '.')
 | 
						|
						if len(c) > 1:
 | 
						|
							# super class
 | 
						|
							# is of the
 | 
						|
							# form module.class:
 | 
						|
							# look in
 | 
						|
							# module for class
 | 
						|
							m = c[-2]
 | 
						|
							c = c[-1]
 | 
						|
							if _modules.has_key(m):
 | 
						|
								d = _modules[m]
 | 
						|
								if d.has_key(c):
 | 
						|
									n = d[c]
 | 
						|
					names.append(n)
 | 
						|
				inherit = names
 | 
						|
			# remember this class
 | 
						|
			cur_class = Class(module, class_name, inherit, file, lineno)
 | 
						|
			dict[class_name] = cur_class
 | 
						|
			continue
 | 
						|
		if is_method.match(line) >= 0:
 | 
						|
			# found a method definition
 | 
						|
			if cur_class:
 | 
						|
				# and we know the class it belongs to
 | 
						|
				meth_name = is_method.group('id')
 | 
						|
				cur_class._addmethod(meth_name, lineno)
 | 
						|
			continue
 | 
						|
		if dedent.match(line) >= 0:
 | 
						|
			# end of class definition
 | 
						|
			cur_class = None
 | 
						|
	f.close()
 | 
						|
	return dict
 |