Synopsis - Cross-Reference
File: Synopsis/Parsers/Python/__init__.py1# 2# Copyright (C) 2006 Stefan Seefeld 3# All rights reserved. 4# Licensed to the public under the terms of the GNU LGPL (>= 2), 5# see the file COPYING for details. 6# 7 8from Synopsis.Processor import * 9from Synopsis import ASG 10from Synopsis.QualifiedName import QualifiedPythonName as QName 11from Synopsis.SourceFile import SourceFile 12from Synopsis.DocString import DocString 13from ASGTranslator import ASGTranslator 14from SXRGenerator import SXRGenerator 15import os 16 17__all__ = ['Parser'] 18 19def expand_package(root, verbose = False): 20 """Find all modules in a given package.""" 21 22 modules = [] 23 if not os.path.isdir(root) or not os.path.isfile(os.path.join(root, '__init__.py')): 24 return modules 25 if verbose: print 'expanding %s'%root 26 files = os.listdir(root) 27 modules += [os.path.join(root, file) 28 for file in files if os.path.splitext(file)[1] == '.py'] 29 for d in [dir for dir in files if os.path.isdir(os.path.join(root, dir))]: 30 modules.extend(expand_package(os.path.join(root, d), verbose)) 31 return modules 32 33 34def find_imported(target, base_path, origin, verbose = False): 35 """ 36 Lookup imported files, based on current file's location. 37 38 target: (module, name) pair. 39 base_path: root directory to which to confine the lookup. 40 origin: file name of the module issuing the import.""" 41 42 module, name = target[0].replace('.', os.sep), target[1] 43 origin = os.path.dirname(origin) 44 if base_path: 45 locations = [base_path + origin.rsplit(os.sep, i)[0] 46 for i in range(origin.count(os.sep) + 1)] + [base_path] 47 else: 48 locations = [origin] 49 50 # '*' is only valid in a module scope 51 if name and name != '*': 52 # name may be a module or a module's attribute. 53 # Only find the module. 54 files = ['%s/%s.py'%(module, name), '%s.py'%module] 55 else: 56 files = ['%s.py'%module] 57 files.append(os.path.join(module, '__init__.py')) 58 for l in locations: 59 for f in files: 60 target = os.path.join(l, f) 61 if verbose: print 'trying %s'%target 62 if os.path.exists(target): 63 return target, target[len(base_path):] 64 return None, None 65 66 67 68class Parser(Processor): 69 """ 70 Python Parser. See http://www.python.org/dev/peps/pep-0258 for additional 71 info.""" 72 73 primary_file_only = Parameter(True, 'should only primary file be processed') 74 base_path = Parameter(None, 'Path prefix to strip off of input file names.') 75 sxr_prefix = Parameter(None, 'Path prefix (directory) to contain sxr info.') 76 default_docformat = Parameter('', 'default documentation format') 77 78 def process(self, ir, **kwds): 79 80 self.set_parameters(kwds) 81 if not self.input: raise MissingArgument('input') 82 self.ir = ir 83 self.scopes = [] 84 85 # Create return type for Python functions: 86 self.return_type = ASG.BuiltinTypeId('Python',('',)) 87 88 # Validate base_path. 89 if self.base_path: 90 if not os.path.isdir(self.base_path): 91 raise InvalidArgument('base_path: "%s" not a directory.' 92 %self.base_path) 93 if self.base_path[-1] != os.sep: 94 self.base_path += os.sep 95 96 # all_files is a list of (filename, base_path) pairs. 97 # we have to record the base_path per file, since it defaults to the 98 # filename itself, for files coming directly from user input. 99 # For files that are the result of expanded packages it defaults to the 100 # package directory itself. 101 self.all_files = [] 102 for i in self.input: 103 base_path = self.base_path or os.path.dirname(i) 104 if base_path and base_path[-1] != os.sep: 105 base_path += os.sep 106 # expand packages into modules 107 if os.path.isdir(i): 108 if os.path.exists(os.path.join(i, '__init__.py')): 109 self.all_files.extend([(p,base_path) 110 for p in expand_package(i, self.verbose)]) 111 else: 112 raise InvalidArgument('"%s" is not a Python package'%i) 113 else: 114 self.all_files.append((i,base_path)) 115 # process modules 116 while len(self.all_files): 117 file, base_path = self.all_files.pop() 118 self.process_file(file, base_path) 119 120 return self.output_and_return_ir() 121 122 123 def process_file(self, filename, base_path): 124 """Parse an individual python file.""" 125 126 long_filename = filename 127 if filename[:len(base_path)] != base_path: 128 raise InvalidArgument('invalid input filename:\n' 129 '"%s" does not match base_path "%s"' 130 %(filename, base_path)) 131 if self.verbose: print 'parsing %s'%filename 132 short_filename = filename[len(base_path):] 133 sourcefile = SourceFile(short_filename, long_filename, 'Python', True) 134 self.ir.files[short_filename] = sourcefile 135 136 package = None 137 package_name = [] 138 package_path = base_path 139 # Only attempt to set up enclosing packages if a base_path was given. 140 if package_path != filename: 141 components = short_filename.split(os.sep) 142 if components[0] == '': 143 package_path += os.sep 144 components = components[1:] 145 for c in components[:-1]: 146 package_name.append(c) 147 package_path = os.path.join(package_path, c) 148 qname = QName(package_name) 149 if not os.path.isfile(os.path.join(package_path, '__init__.py')): 150 raise InvalidArgument('"%s" is not a package'%qname) 151 # Try to locate the package 152 type_id = self.ir.asg.types.get(qname) 153 if (type_id): 154 module = type_id.declaration 155 else: 156 module = ASG.Module(sourcefile, -1, 'package', qname) 157 self.ir.asg.types[qname] = ASG.DeclaredTypeId('Python', qname, module) 158 if package: 159 package.declarations.append(module) 160 else: 161 self.ir.asg.declarations.append(module) 162 163 package = module 164 165 translator = ASGTranslator(package, self.ir.asg.types, self.default_docformat) 166 translator.process_file(sourcefile) 167 # At this point, sourcefile contains a single declaration: the module. 168 if package: 169 package.declarations.extend(sourcefile.declarations) 170 else: 171 self.ir.asg.declarations.extend(sourcefile.declarations) 172 if not self.primary_file_only: 173 for i in translator.imports: 174 target = find_imported(i, self.base_path, sourcefile.name, self.verbose) 175 if target[0] and target[1] not in self.ir.files: 176 # Only process if we have not visited it yet. 177 self.all_files.append((target[0], base_path)) 178 179 if self.sxr_prefix: 180 sxr = os.path.join(self.sxr_prefix, short_filename + '.sxr') 181 dirname = os.path.dirname(sxr) 182 if not os.path.exists(dirname): 183 os.makedirs(dirname, 0755) 184 185 sxr_generator = SXRGenerator() 186 module = sourcefile.declarations[0] 187 sxr_generator.process_file(module.name, sourcefile, sxr) 188
Generated on Tue May 13 02:39:14 2008 by
synopsis (version 0.10)
synopsis (version 0.10)