0
|
1 |
# Copyright (c) 2005 Nokia Corporation
|
|
2 |
#
|
|
3 |
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4 |
# you may not use this file except in compliance with the License.
|
|
5 |
# You may obtain a copy of the License at
|
|
6 |
#
|
|
7 |
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8 |
#
|
|
9 |
# Unless required by applicable law or agreed to in writing, software
|
|
10 |
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11 |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12 |
# See the License for the specific language governing permissions and
|
|
13 |
# limitations under the License.
|
|
14 |
|
|
15 |
from StringIO import StringIO
|
|
16 |
import re
|
|
17 |
import os.path
|
|
18 |
import traceback
|
|
19 |
import sys
|
|
20 |
|
|
21 |
TEMPLATEFILE_SUFFIX='.in'
|
|
22 |
|
|
23 |
def is_templatefile(filename):
|
|
24 |
return filename.endswith(TEMPLATEFILE_SUFFIX)
|
|
25 |
|
|
26 |
def outfilename_from_infilename(infilename):
|
|
27 |
if not is_templatefile(infilename):
|
|
28 |
raise ValueError, "expected file name with suffix "+TEMPLATEFILE_SUFFIX+", got "+infilename
|
|
29 |
return infilename[:-len(TEMPLATEFILE_SUFFIX)]
|
|
30 |
|
|
31 |
class MacroEvaluationError(Exception): pass
|
|
32 |
|
|
33 |
macro_delimiter_re=re.compile(r'(\${{|}})')
|
|
34 |
macro_eval_re=re.compile('\${{(.*?)}}$',re.S)
|
|
35 |
macro_exec_re=re.compile('\${{!(\s*\n)?(.*?)\s*}}$',re.S)
|
|
36 |
macro_if_re =re.compile('\${{if (.*)}}$',re.S)
|
|
37 |
|
|
38 |
def process_macros(instr, namespace=None):
|
|
39 |
"""Return result of doing macro processing on instr, in namespace namespace.
|
|
40 |
|
|
41 |
Basic substitution and evaluation:
|
|
42 |
>>> process_macros('${{foo}}',{'foo':1})
|
|
43 |
'1'
|
|
44 |
>>> process_macros('${{2+2}}')
|
|
45 |
'4'
|
|
46 |
>>> process_macros('${{foo+bar}}',{'foo': 1, 'bar': 2})
|
|
47 |
'3'
|
|
48 |
|
|
49 |
Error cases:
|
|
50 |
>>> process_macros('}}')
|
|
51 |
Traceback (most recent call last):
|
|
52 |
...
|
|
53 |
MacroEvaluationError: Mismatched parens in macro }}
|
|
54 |
>>> process_macros('${{')
|
|
55 |
Traceback (most recent call last):
|
|
56 |
...
|
|
57 |
MacroEvaluationError: Mismatched parens in macro ${{
|
|
58 |
|
|
59 |
Corner cases:
|
|
60 |
>>> process_macros('')
|
|
61 |
''
|
|
62 |
>>> process_macros(u'')
|
|
63 |
u''
|
|
64 |
|
|
65 |
Code execution:
|
|
66 |
>>> process_macros("${{!print 'foo'}}")
|
|
67 |
foo
|
|
68 |
''
|
|
69 |
>>> process_macros("${{! \\nfor k in range(4):\\n write(str(k))}}")
|
|
70 |
'0123'
|
|
71 |
|
|
72 |
>>> process_macros('${{if 1\\nfoo\\n$else\\nbar}}')
|
|
73 |
'foo'
|
|
74 |
>>> process_macros('xxx${{if 1\\n${{foo}}\\n$else\\nbar}}yyy',{'foo':42})
|
|
75 |
'xxx42yyy'
|
|
76 |
>>> process_macros('${{!#}}')
|
|
77 |
''
|
|
78 |
>>> process_macros('${{"${{foo}}"}}',{'foo':42})
|
|
79 |
'42'
|
|
80 |
>>> process_macros('${{"${{bar}}"}}',{'foo':42})
|
|
81 |
Traceback (most recent call last):
|
|
82 |
...
|
|
83 |
MacroEvaluationError: Error evaluating expression "${{bar}}": NameError: name 'bar' is not defined
|
|
84 |
<BLANKLINE>
|
|
85 |
"""
|
|
86 |
if namespace is None:
|
|
87 |
namespace={}
|
|
88 |
|
|
89 |
def process_text(text):
|
|
90 |
#print "Processing text: "+repr(text)
|
|
91 |
pos=0 # position of the first character of text that is not yet processed
|
|
92 |
outbuf=[]
|
|
93 |
macrobuf=[]
|
|
94 |
while 1:
|
|
95 |
m=macro_delimiter_re.search(text, pos)
|
|
96 |
if m:
|
|
97 |
##print "found delimiter: "+text[m.start():]
|
|
98 |
outbuf.append(text[pos:m.start()])
|
|
99 |
pos=m.start()
|
|
100 |
paren_level=0
|
|
101 |
for m in macro_delimiter_re.finditer(text,pos): # find a single whole macro expression
|
|
102 |
delim=m.group(1)
|
|
103 |
if delim=='${{':
|
|
104 |
paren_level+=1
|
|
105 |
elif delim=='}}':
|
|
106 |
paren_level-=1
|
|
107 |
else:
|
|
108 |
assert 0
|
|
109 |
if paren_level<0:
|
|
110 |
raise MacroEvaluationError("Mismatched parens in macro %s"%text[pos:m.end()])
|
|
111 |
if paren_level == 0: # macro expression finishes here, evaluate it.
|
|
112 |
outbuf.append(process_text(process_macro(text[pos:m.end()])))
|
|
113 |
pos=m.end()
|
|
114 |
break
|
|
115 |
if paren_level!=0:
|
|
116 |
raise MacroEvaluationError("Mismatched parens in macro %s"%text[pos:m.end()])
|
|
117 |
else: # no more macros, just output the rest of the plain text
|
|
118 |
outbuf.append(text[pos:])
|
|
119 |
break
|
|
120 |
result=''.join(outbuf)
|
|
121 |
#print "Text after processing: "+repr(result)
|
|
122 |
return result
|
|
123 |
def process_macro(macro_expression):
|
|
124 |
#print 'Processing macro: '+repr(macro_expression)
|
|
125 |
try:
|
|
126 |
outstr=StringIO()
|
|
127 |
namespace['write']=outstr.write
|
|
128 |
m=macro_exec_re.match(macro_expression) # ${{!code}} -- exec the code
|
|
129 |
if m:
|
|
130 |
exec m.group(2) in namespace
|
|
131 |
else:
|
|
132 |
m=macro_if_re.match(macro_expression) # ${{if -- if expression
|
|
133 |
if m:
|
|
134 |
outstr.write(handle_if(process_text(m.group(1)),namespace))
|
|
135 |
else:
|
|
136 |
m=macro_eval_re.match(macro_expression)
|
|
137 |
if m: # ${{code}} -- eval the code
|
|
138 |
outstr.write(str(eval(m.group(1),namespace)))
|
|
139 |
else:
|
|
140 |
raise MacroEvaluationError, 'Invalid macro'
|
|
141 |
#print 'Macro result: '+repr(outstr.getvalue())
|
|
142 |
return outstr.getvalue()
|
|
143 |
except:
|
|
144 |
raise MacroEvaluationError, 'Error evaluating expression "%s": %s'%(
|
|
145 |
macro_expression,
|
|
146 |
'\n'.join(traceback.format_exception_only(sys.exc_info()[0],sys.exc_info()[1])))
|
|
147 |
def if_tokenized(code):
|
|
148 |
#print "code: "+repr(code)
|
|
149 |
lines=code.split('\n')
|
|
150 |
yield ('if',lines[0])
|
|
151 |
#print 'lines: '+repr(lines)
|
|
152 |
for line in lines[1:]:
|
|
153 |
m=re.match('\$(elif|else)(.*)',line)
|
|
154 |
if m:
|
|
155 |
yield (m.group(1),m.group(2))
|
|
156 |
else:
|
|
157 |
#print "data line "+repr(line)
|
|
158 |
yield ('data',line)
|
|
159 |
raise StopIteration
|
|
160 |
def handle_if(code,namespace):
|
|
161 |
outbuf=[]
|
|
162 |
true_condition_found=0
|
|
163 |
for token,content in if_tokenized(code):
|
|
164 |
if token=='data' and true_condition_found:
|
|
165 |
outbuf.append(content)
|
|
166 |
else:
|
|
167 |
if true_condition_found: # end of true block reached
|
|
168 |
break
|
|
169 |
if token=='if' or token=='elif':
|
|
170 |
true_condition_found=eval(content,namespace)
|
|
171 |
elif token=='else':
|
|
172 |
true_condition_found=1
|
|
173 |
return '\n'.join(outbuf)
|
|
174 |
return process_text(instr)
|
|
175 |
|
|
176 |
def process_file(infilename,namespace):
|
|
177 |
outfilename=outfilename_from_infilename(infilename)
|
|
178 |
infile=open(infilename,'rt')
|
|
179 |
outfile=open(outfilename,'wt')
|
|
180 |
outfile.write(process_macros(infile.read(),namespace))
|
|
181 |
outfile.close()
|
|
182 |
infile.close()
|
|
183 |
|
|
184 |
def templatefiles_in_tree(rootdir):
|
|
185 |
files=[]
|
|
186 |
for dirpath, dirnames, filenames in os.walk(rootdir):
|
|
187 |
files+=[os.path.join(dirpath,x) for x in filenames if is_templatefile(x)]
|
|
188 |
return files
|
|
189 |
|
|
190 |
def misctest():
|
|
191 |
files=templatefiles_in_tree('.')
|
|
192 |
print "Templates in tree: "+str(files)
|
|
193 |
|
|
194 |
for k in files:
|
|
195 |
print "Processing file: "+k
|
|
196 |
process_file(k,namespace)
|
|
197 |
# import code
|
|
198 |
# code.interact(None,None,locals())
|
|
199 |
|
|
200 |
def _test():
|
|
201 |
import doctest
|
|
202 |
doctest.testmod()
|
|
203 |
|
|
204 |
if __name__ == "__main__":
|
|
205 |
_test()
|
|
206 |
|
|
207 |
|