Created
December 10, 2011 02:26
-
-
Save cwvh/1454329 to your computer and use it in GitHub Desktop.
Simple parsing with pretty decorators.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import re | |
class Env(object): | |
"""Maintains the environment variables for local and global scope.""" | |
def __init__(self, env={}, parent=os.environ): | |
self.env = env.copy() | |
self.parent = parent.copy() | |
def __getitem__(self, key): | |
"""Get a variable from this or the parent environment.""" | |
return self.env.get(key) or self.parent[key] | |
def __setitem__(self, key, value): | |
self.env[key] = value | |
def __repr__(self): | |
return '%s(%s)' % (type(self).__name__, repr(self.env)) | |
def interp(self, string, regex=r'\${[a-zA-Z_][a-zA-Z_0-9]*}'): | |
match = re.search(regex, string) | |
while match: | |
start, end = match.span() | |
var = string[start + 2 : end - 1] | |
string = ''.join((string[:start], self[var], string[end:])) | |
match = re.search(regex, string) | |
return string | |
def parse(iterable, env=Env()): | |
handlers = {} | |
class pattern(object): | |
def __init__(self, regex): | |
self.regex = regex | |
def __call__(self, function): | |
handlers[self.regex] = function | |
def wrap(*args, **kwds): | |
function(*args, **kwds) | |
return wrap | |
@pattern(r'^([a-zA-Z_\$\%][a-zA-Z_0-9]*)\s*=\s*(.+)$') | |
def vardecl(match, env=env): | |
name, value = match.groups() | |
env[name] = value | |
return name, value | |
@pattern(r'^([a-zA-Z_\$\%][a-zA-Z_0-9\{\}\.]*)\s*:\s*(.*)$') | |
def target(match, env=env): | |
interpolated = [env.interp(m) for m in match.groups() if m] | |
name = interpolated[0] | |
components = [] | |
if len(interpolated) > 1: # we have components to parse | |
line = ' '.join(word for word in interpolated[1:]) | |
match = re.search('(\w+)', line) | |
components = match.groups() | |
return name, components | |
@pattern(r'^\s*\#\s*(.*)') | |
def comment(match, env=env): | |
line = match.group(1) | |
return '# ' + line | |
@pattern(r'^\t(.+)$') | |
def command(match, env=env): | |
cmd = match.group(1) | |
return '\tcmd(%s)' % cmd | |
def notimplemented(*args): | |
return 'not implemented: ' + ', '.join(args) | |
for line in iterable: | |
result = None | |
for pattern, function in handlers.iteritems(): | |
match = re.match(pattern, line) | |
if match is not None: | |
result = function(match) | |
break | |
if result is None: | |
result = notimplemented(line) | |
print result | |
if __name__ == '__main__': | |
import argparse | |
parser = argparse.ArgumentParser() | |
parser.add_argument('target', help='The target to build', nargs='+') | |
parser.add_argument('-f', '--makefile', default='Makefile') | |
args = parser.parse_args() | |
with open(args.makefile) as f: | |
makefile = (line.rstrip() for line in f.readlines() if line.strip()) | |
parse(makefile) | |
# $ python parse.py -f Makefile | |
# ('TARGET', 'target1') | |
# ('target1', ('target2',)) | |
# cmd(@(echo "target1")) | |
# ('target2', ('target3',)) | |
# cmd(@(echo "target2")) | |
# ('target3', ('target4',)) | |
# cmd(@(echo "target3")) | |
# ('target4', []) | |
# cmd(@(echo "target4")) | |
# ('%.txt', []) | |
# cmd(@(echo "txt rule")) | |
## This is a comment. | |
# ('FOOBAR', '"something"') | |
# ('"something"', ('target4',)) | |
# cmd(@(echo ${FOOBAR})) | |
# $ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment