debian-koji/devtools/convert-cheetah
2025-05-07 14:07:03 +02:00

190 lines
5.2 KiB
Python
Executable file

#!/usr/bin/python3
import os.path
import re
import sys
import tempfile
from optparse import OptionParser
"""
Poorly convert some cheetah template code to jinja
This is NOT a full or accurate conversion, it is a set of simple
tranformations that reduce some of the manual work.
Always review the changes.
Always review the changes.
"""
def main():
global subs
global options
parser = OptionParser(usage="%prog <filename>")
parser.add_option('-w', '--write', action='store_true', help='write changes to file')
options, args = parser.parse_args()
options.args = args
if len(args) != 1:
error('Please specify one template')
fn = args[0]
handle_file(fn)
def handle_file(fn):
outp = None
if options.write:
dirname = os.path.dirname(fn)
basename = os.path.basename(fn)
outfile = tempfile.NamedTemporaryFile(dir=dirname, mode='wt', prefix=f'_{basename}', delete=False)
with outfile as outp:
_handle_file(fn, outp)
os.replace(outfile.name, fn)
print(f'Wrote {fn}')
else:
_handle_file(fn, outp)
def _handle_file(fn, outp):
with open(fn, 'rt') as fp:
for lineno, line in enumerate(fp):
line = handle_line(lineno, line, outp)
if line is not None and outp is not None:
outp.write(line)
def handle_line(lineno, line, outp):
orig = line
matches = 0
skip = False
rules = list(SUBS)
while rules:
prog, repl = rules.pop(0)
last = line
if repl == SKIP:
m = prog.search(line)
if m:
print(f'{lineno}: Matched skip pattern {prog.pattern!r}')
print(line, end='')
skip = True
line = orig
break
continue
elif repl == DROP:
m = prog.search(line)
if m:
print(f'{lineno}: Matched DROP pattern {prog.pattern!r}')
print(line, end='')
return None
elif repl == BREAK:
m = prog.search(line)
if m:
print(f'{lineno}: Matched BREAK pattern {prog.pattern!r}')
print(line, end='')
break
elif isinstance(repl, Jump):
# forget remaing rules and use target rules from here
m = prog.search(line)
if m:
rules = list(repl.target)
else:
line, n = prog.subn(repl, line)
if n:
matches += n
print(f'{lineno}: Matched {prog.pattern!r} (count: {n})')
if matches:
print(f'Made {matches} substitutions for line {lineno}')
print(f'ORIG: {orig}', end='')
print(f' NEW: {line}')
return collapse(line)
def rules(subs):
# compile subs
return [(re.compile(pat, flags), repl) for pat, repl, flags in subs]
class Jump:
# jump to new set of substitutions
def __init__(self, target):
self.target = target
SKIP = ('skip subs for this line',)
DROP = ('drop line',)
BREAK = ('stop subs checks for this line',)
STATE = rules([
# subrules for some line statements
[r'[$]', '', 0],
[r'len\(([\w.$]+)\)', r'(\1 |length)', 0 ],
])
SUBS = rules([
# [pattern, replacement, flags]
[r'util.(toggleOrder|rowToggle|sortImage|passthrough_except|passthrough|authToken)\b', r'util.\g<1>2', 0],
[r'(#include .*)header.chtml', r'\1header2.chtml', 0],
[r'(#include .*)footer.chtml', r'\1footer2.chtml', 0],
[r'^#import', DROP, 0],
[r'^#from .* import', DROP, 0],
[r'^\s*#(if|for|elif|set)', Jump(STATE), 0],
[r'#end if', r'#endif', 0],
[r'#end for', r'#endfor', 0],
[r'[(][$]self, ', r'(', 0 ],
[r'\([$]self\)', r'()', 0 ],
[r'len\(([\w.$]+)\)', r'(\1 |length)', 0 ],
[r'[$](([\w.]+)[(][^()]*[)])', r'{{ \1 }}', 0 ],
[r'${\s*([^{}]+)\s*}', r'{{ \1 }}', 0 ],
[r'#echo ([^#]+)#', r'{{ \1 }}', 0 ],
[r'#if ([^#]+) then ([^#]+) else ([^#]+)\s*#', r'{{ \2 if \1 else \3 }}', 0 ],
[r'''[$]([\w.]+)\['(\w+)'\]''', r'{{ \1.\2 }}', 0],
[r'''[$]([\w.]+)\["(\w+)"\]''', r'{{ \1.\2 }}', 0],
[r'[$]([\w.]+)[([]', SKIP, 0],
[r'^(\s*)#attr ', r'\1#set ', 0],
[r'^\s*#', BREAK, 0],
[r'[$]([\w.]+)', r'{{ \1 }}', 0],
])
def error(msg):
print(msg)
sys.exit(1)
BRACES = re.compile(r'({{ | }})')
def collapse(line):
"""Collapse nested double braces"""
tokens = BRACES.split(line)
depth = 0
tokens2 = []
for tok in tokens:
if tok == '{{ ':
# only keep braces at the outer layer
if depth == 0:
tokens2.append(tok)
depth += 1
elif tok == ' }}':
depth -= 1
if depth < 0:
warning("Brace mismatch. Can't collapse")
break
elif depth == 0:
# only keep braces at the outer layer
tokens2.append(tok)
else:
# keep everything else
tokens2.append(tok)
if depth < 0:
warning('Unexpected }}')
return line
elif depth > 0:
warning('Missing }}')
return line
else:
return ''.join(tokens2)
if __name__ == '__main__':
main()