3 # Distributed under GPLv2.1 or any later
5 # Copyright (C) 2014 Tomas Gavenciak <gavento@ucw.cz>
6 # Copyright (C) 2014 Cyril Hrubis <metan@ucw.cz>
11 from sys
import argv
, exit
12 from os
import path
, remove
, system
14 def perror(filename
, line
, lineno
, row
, error
):
15 print('%s:%i:%i: error: %s\n' % (filename
, lineno
, row
, error
))
17 print(' ' * row
+ '^\n')
20 # parse {{ expression }} blocks, escape special chars
21 def transform_verbatim(filename
, line
, lineno
, startrow
=0):
22 tokens
= re
.split('({{|}})', line
)
29 perror(filename
, line
, lineno
, row
+ startrow
, 'Unexpected {{')
32 code
= code
+ '" + str('
38 perror(filename
, line
, lineno
, row
+ startrow
, 'Unexpected }}')
40 # escape \ and " but only in verbatim mode
42 token
= token
.replace("\\", "\\\\").replace('"', '\\"')
48 perror(filename
, line
, lineno
, row
+ startrow
, 'Unterminated {{')
52 def transform(filename
, lines
, include_dirs
, startindent
, indent_depth
):
64 if re
.match('\s*@\s.*', l
):
65 padd
= l
[:len(l
) - len(l
.lstrip())]
67 # lines with '@ end' ends intent block by setting new indent
68 if re
.match('@\s*end\s*', l
):
69 lastindent
= len(l
[2:]) - len(l
[2:].lstrip())
70 elif re
.match('@\s*include.*', l
):
71 include_filename
= re
.sub('@\s*include\s*', '', l
)
74 if not include_filename
:
75 perror(filename
, l
, lineno
, len(l
), 'Expected filename')
77 for dirname
in include_dirs
:
78 if path
.isfile(dirname
+ '/' + include_filename
):
79 include_path
= dirname
+ '/' + include_filename
83 perror(filename
, l
, lineno
, len(l
) - len(include_filename
),
84 "Failed to locate '%s' in %s" %
85 (include_filename
, include_dirs
))
88 infile
= open(include_path
, 'r')
89 except Exception as err
:
90 perror(filename
, l
, lineno
, len(l
) - len(include_filename
), str(err
))
92 out
= out
+ transform(include_filename
, infile
.readlines(),
93 include_dirs
, lastindent
+ startindent
,
98 code
= re
.sub('\t', ' ', l
[2:]).rstrip()
99 # full-line comments do not change last indent
100 if code
and not re
.match('^[ ]*#', code
):
101 if code
.endswith(':'):
102 lastindent
= len(code
) - len(code
.lstrip()) + indent_depth
104 out
.append(' ' * startindent
+ 'cct.set_padd("%s")' % padd
)
105 out
.append(' ' * startindent
+ code
)
107 out
.append(' ' * startindent
+ 'cct.set_padd("")')
108 # special handling for {@ call() @}
109 elif re
.match('.*{@.*@}.*', l
):
110 tokens
= re
.split('{@|@}', l
)
112 row
= len((tokens
[0] + tokens
[1] + tokens
[2]).replace('\t', ' '))
113 perror(filename
, l
, lineno
, row
+ 4,
114 "Only one {@ call() @} per line is allowed")
115 prefix
= transform_verbatim(filename
, tokens
[0], lineno
)
116 startrow
= len((tokens
[0] + tokens
[1]).replace('\t', ' ')) + 2
117 suffix
= transform_verbatim(filename
, tokens
[2], lineno
, startrow
)
118 out
.append(' ' * (lastindent
+ startindent
) + 'cct.set_prefix(' + prefix
+ ')')
119 out
.append(' ' * (lastindent
+ startindent
) + 'cct.set_suffix(' + suffix
+ ')')
120 out
.append(' ' * (lastindent
+ startindent
) + tokens
[1].strip())
121 out
.append(' ' * (lastindent
+ startindent
) + 'cct.reset()')
123 code
= transform_verbatim(filename
, l
, lineno
)
124 out
.append(' ' * (lastindent
+ startindent
) + 'cct.write(' + code
+ ')')
129 "#!/usr/bin/env python",
131 "# Generated file do _not_ edit by hand!",
133 "from sys import exit",
134 "from os import remove, path",
137 " def __init__(self, outfile_path, filename):",
138 " self.first = True",
139 " self.filename = filename",
140 " self.outfile_path = outfile_path",
144 " self.outfile = open(outfile_path, 'w')",
145 " except Exception as err:",
146 " self.error('Failed to open file: ' + outfile_path + ' : ' + str(err))",
148 " def error_cleanup(self):",
149 " self.outfile.close()",
150 " remove(self.outfile_path)",
152 " def error(self, string):",
153 " self.error_cleanup()",
154 " print('cct: error: ' + string)",
157 " def write(self, line):",
159 " if 'cct_header' in globals():",
160 " self.first = False",
161 " cct_header(path.basename(self.outfile_path), self.filename)",
162 " if not line and not ''.join(self.suffix):",
163 " prefix = ''.join(self.prefix).rstrip()",
165 " prefix = ''.join(self.prefix)",
166 " self.outfile.write(prefix + line + ''.join(self.suffix) + '\\n')",
168 " def set_prefix(self, prefix):",
169 " self.prefix.append(prefix)",
171 " def set_suffix(self, suffix):",
172 " self.suffix.append(suffix)",
175 " self.suffix.pop()",
176 " self.prefix.pop()",
179 " if 'cct_footer' in globals():",
180 " cct_footer(path.basename(self.outfile_path), self.filename)",
183 " self.outfile.close()",
184 " except Exception as err:",
185 " self.error('Failed to write ' + self.outfile_path + ' : ' + str(err))",
190 "except Exception as err:",
191 " cct.error_cleanup()",
196 def generate(filename
, lines
, include_dirs
, indent_depth
, outfile
):
198 out
.append("cct = cct('%s', '%s')" % (outfile
, filename
))
201 res
= transform(filename
, lines
, include_dirs
, indent_depth
, indent_depth
)
202 out
= out
+ res
+ footer
203 return '\n'.join(out
)
210 print('Usage:\ncct [-Idir] [-v] [-o outfile] file.c.t\n')
211 print('-E\n\tStops at first phase, leaves python script')
212 print('-i\n\tSets indenntation depth, default is 4')
213 print('-I\n\tAdds include path(s)')
214 print('-o\n\tSets output file')
215 print('-v\n\tSets verbose mode')
216 print('-h | --help\n\tPrints this help.')
218 def write_script(script_name
, t
):
220 result
= open(script_name
, 'w')
221 except Exception as err
:
222 error('Failed to open file: ' + script_name
+ ' : ' + str(err
))
228 except Exception as err
:
229 error('Failed to close file: ' + script_name
+ ' : ' + str(err
))
233 opts
, args
= getopt
.getopt(argv
[1:], 'Eho:i:I:v', ['help'])
234 except getopt
.GetoptError
as err
:
245 for opt
, arg
in opts
:
246 if opt
in ('-h', '--help'):
250 indent_depth
= int(arg
)
252 include_dirs
.append(arg
)
261 error('No input files.')
264 if not args
[0].endswith('.t'):
265 error('No outfile set and template does not end with .t')
267 outfile
= args
[0][:-2]
270 print("Settings\n--------")
271 print("Include Dirs: %s" % include_dirs
)
272 print("Template File: %s" % args
[0])
273 print("Output File: %s" % outfile
)
274 print("Indentation Depth: %i" % indent_depth
)
277 with
open(args
[0], 'rt') as f
:
278 t
= generate(args
[0], f
.readlines(), include_dirs
, indent_depth
, outfile
)
280 script_name
= outfile
+ '.py'
287 # Something failed fallback to writing file
288 # and executing it because the error trace
289 # from exec() tends to be less informative
290 write_script(script_name
, t
)
291 system('python ' + script_name
)
295 write_script(script_name
, t
)
297 if __name__
== '__main__':