PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/scripts/jinjify.py

https://gitlab.com/phora/nikola
Python | 227 lines | 175 code | 40 blank | 12 comment | 39 complexity | d523579ba77cbffb591c0f9f26da2e78 MD5 | raw file
  1. #!/usr/bin/env python
  2. import io
  3. import glob
  4. import sys
  5. import os
  6. import re
  7. import json
  8. import shutil
  9. import colorama
  10. import jinja2
  11. dumb_replacements = [
  12. ["{% if any(post.is_mathjax for post in posts) %}", '{% if posts|selectattr("is_mathjax")|list %}'],
  13. ["json.dumps(title)", "title|tojson"],
  14. ["{{ parent.extra_head() }}", "{{ super() }}"],
  15. ["prefix='\\", "prefix='"],
  16. ["og: http://ogp.me/ns# \\", "og: http://ogp.me/ns#"],
  17. ["article: http://ogp.me/ns/article# \\", "article: http://ogp.me/ns/article#"],
  18. ["fb: http://ogp.me/ns/fb# \\", "fb: http://ogp.me/ns/fb#"],
  19. ['dir="rtl" \\', 'dir="rtl"']
  20. ]
  21. dumber_replacements = [
  22. ["<html\n\\", "<html\n"],
  23. ["\n'\\\n", "\n'\n"],
  24. ["{% endif %}\n\\", "{% endif %}\n"]
  25. ]
  26. def jinjify(in_theme, out_theme):
  27. """Convert in_theme into a jinja version and put it in out_theme"""
  28. in_templates_path = os.path.join(in_theme, "templates")
  29. out_templates_path = os.path.join(out_theme, "templates")
  30. try:
  31. os.makedirs(out_templates_path)
  32. except:
  33. pass
  34. lookup = jinja2.Environment()
  35. lookup.filters['tojson'] = json.dumps
  36. lookup.loader = jinja2.FileSystemLoader([out_templates_path], encoding='utf-8')
  37. for template in glob.glob(os.path.join(in_templates_path, "*.tmpl")):
  38. out_template = os.path.join(out_templates_path, os.path.basename(template))
  39. with io.open(template, "r", encoding="utf-8") as inf:
  40. data = mako2jinja(inf)
  41. lines = []
  42. for line in data.splitlines():
  43. for repl in dumb_replacements:
  44. line = line.replace(*repl)
  45. lines.append(line)
  46. data = '\n'.join(lines)
  47. for repl in dumber_replacements:
  48. data = data.replace(*repl)
  49. with io.open(out_template, "w+", encoding="utf-8") as outf:
  50. outf.write(data + '\n')
  51. # Syntax check output
  52. source, filename = lookup.loader.get_source(lookup, os.path.basename(template))[:2]
  53. try:
  54. lookup.parse(source)
  55. except Exception as e:
  56. error("Syntax error in {0}:{1}".format(out_template, e.lineno))
  57. parent = os.path.basename(in_theme.rstrip('/'))
  58. child = os.path.basename(out_theme.rstrip('/'))
  59. mappings = {
  60. 'base-jinja': 'base',
  61. 'bootstrap-jinja': 'base-jinja',
  62. 'bootstrap3-jinja': 'bootstrap-jinja',
  63. }
  64. if child in mappings:
  65. parent = mappings[child]
  66. with open(os.path.join(out_theme, "parent"), "wb+") as outf:
  67. outf.write(parent + '\n')
  68. with open(os.path.join(out_theme, "engine"), "wb+") as outf:
  69. outf.write("jinja\n")
  70. # Copy assets
  71. # shutil.rmtree(os.path.join(out_theme, "assets"))
  72. # shutil.copytree(os.path.join(in_theme, "assets"), os.path.join(out_theme, "assets"))
  73. # Copy bundles
  74. # shutil.copy(os.path.join(in_theme, "bundles"), os.path.join(out_theme, "bundles"))
  75. # Copy README
  76. if os.path.isfile(os.path.join(in_theme, "README.md")):
  77. shutil.copy(os.path.join(in_theme, "README.md"), os.path.join(out_theme, "README.md"))
  78. def error(msg):
  79. print(colorama.Fore.RED + "ERROR:" + msg)
  80. def mako2jinja(input_file):
  81. output = ''
  82. # TODO: OMG, this code is so horrible. Look at it; just look at it:
  83. macro_start = re.compile(r'(.*)<%.*def name="(.*?)".*>(.*)', re.IGNORECASE)
  84. macro_end = re.compile(r'(.*)</%def>(.*)', re.IGNORECASE)
  85. if_start = re.compile(r'(.*)% *if (.*):(.*)', re.IGNORECASE)
  86. if_else = re.compile(r'(.*)% *else.*:(.*)', re.IGNORECASE)
  87. if_elif = re.compile(r'(.*)% *elif (.*):(.*)', re.IGNORECASE)
  88. if_end = re.compile(r'(.*)% *endif(.*)', re.IGNORECASE)
  89. for_start = re.compile(r'(.*)% *for (.*):(.*)', re.IGNORECASE)
  90. for_end = re.compile(r'(.*)% *endfor(.*)', re.IGNORECASE)
  91. namespace = re.compile(r'(.*)<% *namespace name="(.*?)".* file="(.*?)".*/>(.*)', re.IGNORECASE)
  92. inherit = re.compile(r'(.*)<% *inherit file="(.*?)".*/>(.*)', re.IGNORECASE)
  93. block_single_line = re.compile(r'(.*)<% *block.*name="(.*?)".*>(.*)</% *block>(.*)', re.IGNORECASE)
  94. block_start = re.compile(r'(.*)<% *block.*name="(.*?)".*>(.*)', re.IGNORECASE)
  95. block_end = re.compile(r'(.*)</%block>(.*)', re.IGNORECASE)
  96. val = re.compile(r'\$\{(.*?)\}', re.IGNORECASE)
  97. func_len = re.compile(r'len\((.*?)\)', re.IGNORECASE)
  98. filter_h = re.compile(r'\|h', re.IGNORECASE)
  99. filter_striphtml = re.compile(r'\|striphtml', re.IGNORECASE)
  100. filter_u = re.compile(r'\|u', re.IGNORECASE)
  101. comment_single_line = re.compile(r'^.*##(.*?)$', re.IGNORECASE)
  102. for line in input_file:
  103. # Process line for repeated inline replacements
  104. m_val = val.search(line)
  105. m_func_len = func_len.search(line)
  106. m_filter_h = filter_h.search(line)
  107. m_filter_striphtml = filter_striphtml.search(line)
  108. m_filter_u = filter_u.search(line)
  109. if m_val:
  110. line = val.sub(r'{{ \1 }}', line)
  111. if m_filter_h:
  112. line = filter_h.sub(r'|e', line)
  113. if m_filter_striphtml:
  114. line = filter_striphtml.sub(r'|e', line)
  115. if m_filter_u:
  116. line = filter_u.sub(r'|urlencode', line)
  117. if m_func_len:
  118. line = func_len.sub(r'\1|length', line)
  119. # Process line for single 'whole line' replacements
  120. m_macro_start = macro_start.search(line)
  121. m_macro_end = macro_end.search(line)
  122. m_if_start = if_start.search(line)
  123. m_if_else = if_else.search(line)
  124. m_if_elif = if_elif.search(line)
  125. m_if_end = if_end.search(line)
  126. m_for_start = for_start.search(line)
  127. m_for_end = for_end.search(line)
  128. m_namspace = namespace.search(line)
  129. m_inherit = inherit.search(line)
  130. m_block_single_line = block_single_line.search(line)
  131. m_block_start = block_start.search(line)
  132. m_block_end = block_end.search(line)
  133. m_comment_single_line = comment_single_line.search(line)
  134. if m_comment_single_line:
  135. output += m_comment_single_line.expand(r'{# \1 #}') + '\n'
  136. elif m_macro_start:
  137. output += m_macro_start.expand(r'\1{% macro \2 %}\3') + '\n'
  138. elif m_macro_end:
  139. output += m_macro_end.expand(r'\1{% endmacro %}\1') + '\n'
  140. elif m_if_start:
  141. output += m_if_start.expand(r'\1{% if \2 %}\3') + '\n'
  142. elif m_if_else:
  143. output += m_if_else.expand(r'\1{% else %}\2') + '\n'
  144. elif m_if_elif:
  145. output += m_if_elif.expand(r'\1{% elif \2 %}\3') + '\n'
  146. elif m_if_end:
  147. output += m_if_end.expand(r'\1{% endif %}\2') + '\n'
  148. elif m_for_start:
  149. output += m_for_start.expand(r'\1{% for \2 %}\3') + '\n'
  150. elif m_for_end:
  151. output += m_for_end.expand(r'\1{% endfor %}\2') + '\n'
  152. elif m_namspace:
  153. output += m_namspace.expand(r"\1{% import '\3' as \2 with context %}\4") + '\n'
  154. elif m_inherit:
  155. output += m_inherit.expand(r"{% extends '\2' %}\3") + '\n'
  156. elif m_block_single_line:
  157. output += m_block_single_line.expand(r'\1{% block \2 %}\3{% endblock %}\4') + '\n'
  158. elif m_block_start:
  159. output += m_block_start.expand(r'\1{% block \2 %}\3') + '\n'
  160. elif m_block_end:
  161. output += m_block_end.expand(r'\1{% endblock %}\2') + '\n'
  162. else:
  163. # Doesn't match anything we're going to process, pass though
  164. output += line
  165. return output
  166. if __name__ == "__main__":
  167. if len(sys.argv) == 1:
  168. print('Performing standard conversions:')
  169. for m, j in (
  170. ('nikola/data/themes/base', 'nikola/data/themes/base-jinja'),
  171. ('nikola/data/themes/bootstrap', 'nikola/data/themes/bootstrap-jinja'),
  172. ('nikola/data/themes/bootstrap3', 'nikola/data/themes/bootstrap3-jinja')
  173. ):
  174. print(' {0} -> {1}'.format(m, j))
  175. jinjify(m, j)
  176. elif len(sys.argv) != 3:
  177. print('ERROR: needs input and output directory, or no arguments for default conversions.')
  178. else:
  179. jinjify(sys.argv[1], sys.argv[2])