0001# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org)
0002# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
0003import os
0004import py_compile
0005import marshal
0006import inspect
0007import re
0008from command import Command
0009import pluginlib
0010
0011class GrepCommand(Command):
0012
0013    summary = 'Search project for symbol'
0014    usage = 'SYMBOL'
0015
0016    max_args = 1
0017    min_args = 1
0018
0019    bad_names = ['.svn', 'CVS', '_darcs']
0020
0021    parser = Command.standard_parser()
0022
0023    parser.add_option(
0024        '-x', '--exclude-module',
0025        metavar="module.name",
0026        dest="exclude_modules",
0027        action="append",
0028        help="Don't search the given module")
0029
0030    parser.add_option(
0031        '-t', '--add-type',
0032        metavar=".ext",
0033        dest="add_types",
0034        action="append",
0035        help="Search the given type of files")
0036
0037    def command(self):
0038        self.exclude_modules = self.options.exclude_modules or []
0039        self.add_types = self.options.add_types or []
0040        self.symbol = self.args[0]
0041        self.basedir = os.path.dirname(
0042            pluginlib.find_egg_info_dir(os.getcwd()))
0043        if self.verbose:
0044            print "Searching in %s" % self.basedir
0045        self.total_files = 0
0046        self.search_dir(self.basedir)
0047        if self.verbose > 1:
0048            print "Searched %i files" % self.total_files
0049
0050    def search_dir(self, dir):
0051        names = os.listdir(dir)
0052        names.sort()
0053        dirs = []
0054        for name in names:
0055            full = os.path.join(dir, name)
0056            if name in self.bad_names:
0057                continue
0058            if os.path.isdir(full):
0059                # Breadth-first; we'll do this later...
0060                dirs.append(full)
0061                continue
0062            for t in self.add_types:
0063                if name.lower().endswith(t.lower()):
0064                    self.search_text(full)
0065            if not name.endswith('.py'):
0066                continue
0067            self.search_file(full)
0068        for dir in dirs:
0069            self.search_dir(dir)
0070
0071    def search_file(self, filename):
0072        self.total_files += 1
0073        if not filename.endswith('.py'):
0074            self.search_text(filename)
0075            return
0076        pyc = filename[:-2]+'pyc'
0077        if not os.path.exists(pyc):
0078            py_compile.compile(filename)
0079        if not os.path.exists(pyc):
0080            # Invalid syntax...
0081            self.search_text(filename, as_module=True)
0082            return
0083        f = open(pyc, 'rb')
0084        # .pyc Header:
0085        f.read(8)
0086        code = marshal.load(f)
0087        f.close()
0088        self.search_code(code, filename, [])
0089
0090    def search_code(self, code, filename, path):
0091        if code.co_name != "?":
0092            path = path + [code.co_name]
0093        else:
0094            path = path
0095        sym = self.symbol
0096        if sym in code.co_varnames:
0097            self.found(code, filename, path)
0098        elif sym in code.co_names:
0099            self.found(code, filename, path)
0100        for const in code.co_consts:
0101            if const == sym:
0102                self.found(code, filename, path)
0103            if inspect.iscode(const):
0104                if not const.co_filename == filename:
0105                    continue
0106                self.search_code(const, filename, path)
0107
0108    def search_text(self, filename, as_module=False):
0109        f = open(filename, 'rb')
0110        lineno = 0
0111        any = False
0112        for line in f:
0113            lineno += 1
0114            if line.find(self.symbol) != -1:
0115                if not any:
0116                    any = True
0117                    if as_module:
0118                        print '%s (unloadable)' % self.module_name(filename)
0119                    else:
0120                        print self.relative_name(filename)
0121                print '  %3i  %s' % (lineno, line)
0122                if not self.verbose:
0123                    break
0124        f.close()
0125
0126    def found(self, code, filename, path):
0127        print self.display(filename, path)
0128        self.find_occurance(code)
0129
0130    def find_occurance(self, code):
0131        f = open(code.co_filename, 'rb')
0132        lineno = 0
0133        for index, line in zip(xrange(code.co_firstlineno), f):
0134            lineno += 1
0135            pass
0136        lines = []
0137        first_indent = None
0138        for line in f:
0139            lineno += 1
0140            if line.find(self.symbol) != -1:
0141                this_indent = len(re.match(r'^[ \t]*', line).group(0))
0142                if first_indent is None:
0143                    first_indent = this_indent
0144                else:
0145                    if this_indent < first_indent:
0146                        break
0147                print '  %3i  %s' % (lineno, line[first_indent:].rstrip())
0148                if not self.verbose:
0149                    break
0150
0151    def module_name(self, filename):
0152        assert filename, startswith(self.basedir)
0153        mod = filename[len(self.basedir):].strip('/').strip(os.path.sep)
0154        mod = os.path.splitext(mod)[0]
0155        mod = mod.replace(os.path.sep, '.').replace('/', '.')
0156        return mod
0157
0158    def relative_name(self, filename):
0159        assert filename, startswith(self.basedir)
0160        name = filename[len(self.basedir):].strip('/').strip(os.path.sep)
0161        return name
0162
0163    def display(self, filename, path):
0164        parts = '.'.join(path)
0165        if parts:
0166            parts = ':' + parts
0167        return self.module_name(filename) + parts