1.1 --- a/astgrep.py Mon Oct 27 22:49:46 2008 +0100
1.2 +++ b/astgrep.py Wed Oct 29 02:17:19 2008 +0100
1.3 @@ -23,8 +23,16 @@
1.4 import compiler
1.5 import os
1.6 import linecache
1.7 +import types
1.8
1.9 -__version__ = "0.1"
1.10 +__version__ = "0.1.1"
1.11 +
1.12 +# Excluded AST nodes and their names.
1.13 +
1.14 +excluded_term_types = ["Module", "Stmt"]
1.15 +excluded_term_cls = tuple([getattr(compiler.ast, name) for name in excluded_term_types])
1.16 +
1.17 +# Search functions.
1.18
1.19 def search_recursive(directory, term_type, term, op=None):
1.20
1.21 @@ -44,32 +52,58 @@
1.22
1.23 """
1.24 Search the file with the given 'filename' for terms having the given
1.25 - 'term_type' whose value matches the specified 'term'.
1.26 + 'term_type' whose value matches the specified 'term'. If 'term_type' is
1.27 + given as "*", attempt to match any term type.
1.28 """
1.29
1.30 - node = compiler.parseFile(filename)
1.31 - cls = getattr(compiler.ast, term_type)
1.32 + try:
1.33 + node = compiler.parseFile(filename)
1.34 + except SyntaxError:
1.35 + return []
1.36 +
1.37 + if term_type != "*":
1.38 + cls = getattr(compiler.ast, term_type)
1.39 + else:
1.40 + cls = None
1.41 +
1.42 return search_tree(node, cls, term, op, filename)
1.43
1.44 def search_tree(node, cls, term, op=None, filename=None):
1.45
1.46 """
1.47 Search the tree rooted at the given 'node' for nodes of the given class
1.48 - 'cls' for content matching the specified 'term'.
1.49 + 'cls' for content matching the specified 'term'. If 'cls' is None, all node
1.50 + types will be considered for matches.
1.51
1.52 Return a list of results of the form (node, value, filename).
1.53 """
1.54
1.55 results = []
1.56
1.57 - if isinstance(node, cls):
1.58 + # Ignore excluded nodes.
1.59 +
1.60 + if isinstance(node, excluded_term_cls):
1.61 + pass
1.62 +
1.63 + # Test permitted nodes.
1.64 +
1.65 + elif cls is None or isinstance(node, cls):
1.66 if op is None:
1.67 results.append((node, None, filename))
1.68 else:
1.69 for child in node.getChildren():
1.70 - if isinstance(child, (str, unicode, int, float, long, bool)) and op(unicode(child)):
1.71 - results.append((node, child, filename))
1.72 - break
1.73 +
1.74 + # Test literals.
1.75 +
1.76 + if isinstance(child, (str, int, float, long, bool)):
1.77 + if op(str(child)):
1.78 + results.append((node, child, filename))
1.79 +
1.80 + # Only check a single string child value since subsequent
1.81 + # values are typically docstrings.
1.82 +
1.83 + if isinstance(child, str):
1.84 + break
1.85
1.86 # Search within nodes, even if matches have already been found.
1.87
1.88 @@ -82,29 +116,51 @@
1.89
1.90 """
1.91 Expand the given 'results', making a list containing tuples of the form
1.92 - (filename, line number, line, value).
1.93 + (node, filename, line number, line, value).
1.94 """
1.95
1.96 expanded = []
1.97
1.98 for node, value, filename in results:
1.99 - if filename is not None:
1.100 - line = linecache.getline(filename, node.lineno).rstrip()
1.101 + lineno = node.lineno
1.102 +
1.103 + if filename is not None and lineno is not None:
1.104 + line = linecache.getline(filename, lineno).rstrip()
1.105 else:
1.106 line = None
1.107
1.108 - expanded.append((filename, node.lineno, line, value))
1.109 + expanded.append((node, filename, lineno, line, value))
1.110
1.111 return expanded
1.112
1.113 +def get_term_types():
1.114 +
1.115 + "Return the term types supported by the module."
1.116 +
1.117 + term_types = []
1.118 +
1.119 + for name in dir(compiler.ast):
1.120 + if name in excluded_term_types:
1.121 + continue
1.122 +
1.123 + obj = getattr(compiler.ast, name)
1.124 +
1.125 + if isinstance(obj, types.ClassType) and \
1.126 + issubclass(obj, compiler.ast.Node) and \
1.127 + name[0].isupper():
1.128 +
1.129 + term_types.append(name)
1.130 +
1.131 + return term_types
1.132 +
1.133 # Command syntax.
1.134
1.135 syntax_description = """
1.136 [ -n | --line-number ]
1.137 [ -p | --print-token ]
1.138 - ( ( -t TERM_TYPE ) | ( --type=TERM_TYPE ) )
1.139 + [ ( -t TERM_TYPE ) | ( --type=TERM_TYPE ) ]
1.140 [ ( -e PATTERN ) | ( --regexp=PATTERN ) ]
1.141 - ( ( ( -r | -R | --recursive ) DIRECTORY ) | FILENAME )
1.142 + [ -r | -R | --recursive ] ( FILENAME ... )
1.143 """
1.144
1.145 # Main program.
1.146 @@ -116,45 +172,61 @@
1.147 import sys
1.148 import cmdsyntax
1.149 import re
1.150 + import textwrap
1.151
1.152 # Match command arguments.
1.153
1.154 syntax = cmdsyntax.Syntax(syntax_description)
1.155 syntax_matches = syntax.get_args(sys.argv[1:])
1.156 + show_syntax = 0
1.157
1.158 try:
1.159 args = syntax_matches[0]
1.160 except IndexError:
1.161 + show_syntax = 1
1.162 +
1.163 + if show_syntax:
1.164 print "Syntax:"
1.165 print syntax_description
1.166 + print "Term types:"
1.167 + print "\n".join(textwrap.wrap(", ".join(get_term_types())))
1.168 sys.exit(1)
1.169
1.170 # Get the search details.
1.171
1.172 - term_type = args["TERM_TYPE"]
1.173 + term_type = args.get("TERM_TYPE", "*")
1.174 term = args.get("PATTERN")
1.175 + recursive = args.has_key("r") or args.has_key("R") or args.has_key("recursive")
1.176
1.177 if term is None:
1.178 op = None
1.179 else:
1.180 op = re.compile(term).search
1.181
1.182 - # Perform the search either in a single file or in a directory hierarchy.
1.183 + # Perform the search in files and directory hierarchies.
1.184 +
1.185 + results = []
1.186
1.187 - if args.has_key("FILENAME"):
1.188 - results = search_file(args["FILENAME"], term_type, term, op)
1.189 - else:
1.190 - results = search_recursive(args["DIRECTORY"], term_type, term, op)
1.191 + for filename in args["FILENAME"]:
1.192 + if os.path.isfile(filename):
1.193 + results += search_file(filename, term_type, term, op)
1.194 + elif recursive and os.path.isdir(filename):
1.195 + results += search_recursive(filename, term_type, term, op)
1.196
1.197 # Present the results.
1.198
1.199 - for filename, lineno, line, value in expand_results(results):
1.200 + for node, filename, lineno, line, value in expand_results(results):
1.201 format = "%s:"
1.202 output = [filename]
1.203
1.204 + # Handle line numbers and missing details.
1.205 +
1.206 if args.has_key("n") or args.has_key("line-number"):
1.207 - format += "%d:"
1.208 - output.append(lineno)
1.209 + if lineno is not None:
1.210 + format += "%d:"
1.211 + output.append(lineno)
1.212 +
1.213 + # Show matching tokens, if requested.
1.214
1.215 if args.has_key("p"):
1.216 if value is not None:
1.217 @@ -162,10 +234,13 @@
1.218 output.append(value)
1.219 else:
1.220 format += "%s:"
1.221 - output.append("<%s>" % term_type)
1.222 + output.append("<%s>" % (term_type or "*"))
1.223 +
1.224 + # Show lines, if defined.
1.225
1.226 - format += " %s"
1.227 - output.append(line)
1.228 + if line is not None:
1.229 + format += " %s"
1.230 + output.append(line)
1.231
1.232 print format % tuple(output)
1.233