Synopsis - Synopsis/Parsers/Python/__init__.py

File: Synopsis/Parsers/Python/__init__.py

      1#
      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 Thu Apr 16 16:27:15 2009 by
synopsis (version devel)