1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
from io import open
from os.path import isdir
import re
def filterLines(lines, regex):
'''Filters each line of the given line iterator using the given regular
expression string. For each match, a tuple containing the text matching
each capture group from the regular expression is yielded.
'''
matcher = re.compile(regex)
for line in lines:
if line.endswith('\n'):
line = line[ : -1]
match = matcher.match(line)
if match:
yield match.groups()
def filterFile(filePath, regex):
'''Filters each line of the given text file using the given regular
expression string. For each match, a tuple containing the text matching
each capture group from the regular expression is yielded.
'''
with open(filePath, 'r', encoding='utf-8') as inp:
for groups in filterLines(inp, regex):
yield groups
def joinContinuedLines(lines):
'''Iterates through the given lines, replacing lines that are continued
using a trailing backslash with a single line.
'''
buf = ''
for line in lines:
if line.endswith('\\\n'):
buf += line[ : -2]
elif line.endswith('\\'):
buf += line[ : -1]
else:
yield buf + line
buf = ''
if buf:
raise ValueError('Continuation on last line')
_reEval = re.compile(r'(\$\(|\))')
def evalMakeExpr(expr, makeVars):
'''Evaluates variable references in an expression.
Raises ValueError if there is a syntax error in the expression.
Raises KeyError if the expression references a non-existing variable.
'''
stack = [ [] ]
for part in _reEval.split(expr):
if part == '$(':
stack.append([])
elif part == ')' and len(stack) != 1:
name = ''.join(stack.pop())
if name.startswith('addprefix '):
prefix, args = name[len('addprefix') : ].split(',')
prefix = prefix.strip()
value = ' '.join(prefix + arg for arg in args.split())
elif name.startswith('addsuffix '):
suffix, args = name[len('addsuffix') : ].split(',')
suffix = suffix.strip()
value = ' '.join(arg + suffix for arg in args.split())
elif name.startswith('shell '):
# Unsupported; assume result is never used.
value = '?'
elif name.isdigit():
# This is a function argument; assume the evaluated result is
# never used.
value = '?'
else:
value = makeVars[name]
stack[-1].append(value)
else:
stack[-1].append(part)
if len(stack) != 1:
raise ValueError('Open without close in "%s"' % expr)
return ''.join(stack.pop())
def extractMakeVariables(filePath, makeVars = None):
'''Extract all variable definitions from the given Makefile.
The optional makeVars argument is a dictionary containing the already
defined variables. These variables will be included in the output; the
given dictionary is not modified.
Returns a dictionary that maps each variable name to its value.
'''
makeVars = {} if makeVars is None else dict(makeVars)
with open(filePath, 'r', encoding='utf-8') as inp:
for name, assign, value in filterLines(
joinContinuedLines(inp),
r'[ ]*([A-Za-z0-9_]+)[ ]*([+:]?=)(.*)'
):
if assign == '=':
makeVars[name] = value.strip()
elif assign == ':=':
makeVars[name] = evalMakeExpr(value, makeVars).strip()
elif assign == '+=':
# Note: Make will or will not evaluate the added expression
# depending on how the variable was originally defined,
# but we don't store that information.
makeVars[name] = makeVars[name] + ' ' + value.strip()
else:
assert False, assign
return makeVars
def parseBool(valueStr):
'''Parses a string containing a boolean value.
Accepted values are "true" and "false"; anything else raises ValueError.
'''
if valueStr == 'true':
return True
elif valueStr == 'false':
return False
else:
raise ValueError('Invalid boolean "%s"' % valueStr)
|