generate_all.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #!/usr/bin/python
  2. # Copyright Abel Sinkovics (abel@sinkovics.hu) 2015.
  3. # Distributed under the Boost Software License, Version 1.0.
  4. # (See accompanying file LICENSE_1_0.txt or copy at
  5. # http://www.boost.org/LICENSE_1_0.txt)
  6. import sys
  7. import argparse
  8. import re
  9. import os
  10. def remove_last_dot(s):
  11. if s.endswith('.'):
  12. return s[:-1]
  13. else:
  14. return s
  15. def remove_newline(s):
  16. return re.sub('[\r\n]', '', s)
  17. def is_definition(s):
  18. cmd = s.strip()
  19. def_prefixes = ['#include ', 'using ', 'struct ', 'template ']
  20. return any([cmd.startswith(s) for s in def_prefixes]) or cmd.endswith(';')
  21. def prefix_lines(prefix, s):
  22. return '\n'.join(['%s%s' % (prefix, l) for l in s.split('\n')])
  23. def protect_metashell(s):
  24. if s.startswith('#include <metashell'):
  25. return '#ifdef __METASHELL\n%s\n#endif' % (s)
  26. else:
  27. return s
  28. def parse_md(qbk):
  29. sections = []
  30. defs = []
  31. current_section = ''
  32. in_cpp_snippet = False
  33. numbered_section_header = re.compile('^\[section *([0-9.]+)')
  34. metashell_command = re.compile('^> [^ ]')
  35. metashell_prompt = re.compile('^(\.\.\.|)>')
  36. msh_cmd = ''
  37. for l in qbk:
  38. if l.startswith(' '):
  39. ll = l[2:]
  40. if not in_cpp_snippet:
  41. in_msh_cpp_snippet = True
  42. if in_msh_cpp_snippet:
  43. if metashell_command.match(ll) or msh_cmd != '':
  44. cmd = metashell_prompt.sub('', remove_newline(ll))
  45. if msh_cmd != '':
  46. msh_cmd = msh_cmd + '\n'
  47. msh_cmd = msh_cmd + cmd
  48. if msh_cmd.endswith('\\'):
  49. msh_cmd = msh_cmd[:-1].strip() + ' '
  50. else:
  51. if not is_definition(msh_cmd):
  52. msh_cmd = '// query:\n%s' % (prefix_lines('// ', msh_cmd))
  53. defs.append((current_section, protect_metashell(msh_cmd.strip())))
  54. msh_cmd = ''
  55. elif not in_cpp_snippet:
  56. in_msh_cpp_snippet = False
  57. in_cpp_snippet = True
  58. else:
  59. in_cpp_snippet = False
  60. m = numbered_section_header.match(l)
  61. if m:
  62. current_section = remove_last_dot(m.group(1)).replace('.', '_')
  63. sections.append(current_section)
  64. sections.sort(key = lambda s: [int(n) for n in s.split('_')])
  65. return (sections, defs)
  66. def delete_old_headers(path):
  67. for f in os.listdir(path):
  68. if f.endswith('.hpp'):
  69. os.remove(os.path.join(path, f))
  70. def gen_headers(sections, defs, path):
  71. files = {}
  72. prev_section = ''
  73. for s in sections:
  74. prev_name = prev_section.replace('_', '.')
  75. include_guard = 'BOOST_METAPARSE_GETTING_STARTED_%s_HPP' % (s)
  76. if prev_section == '':
  77. prev_include = ''
  78. else:
  79. prev_include = \
  80. '// Definitions before section {0}\n'.format(prev_name) + \
  81. '#include "{0}.hpp"\n'.format(prev_section) + \
  82. '\n'
  83. files[os.path.join(path, s + '.hpp')] = \
  84. '#ifndef {0}\n'.format(include_guard) + \
  85. '#define {0}\n'.format(include_guard) + \
  86. '\n' + \
  87. '// Automatically generated header file\n' + \
  88. '\n' + \
  89. prev_include + \
  90. '// Definitions of section {0}\n'.format(prev_name) + \
  91. '\n'.join( \
  92. ['%s\n' % (d) for (sec, d) in defs if sec == prev_section] \
  93. ) + \
  94. '\n' + \
  95. '#endif\n' + \
  96. '\n'
  97. prev_section = s
  98. return files
  99. def remove_metashell_protection(s):
  100. prefix = '#ifdef __METASHELL\n'
  101. suffix = '#endif'
  102. return \
  103. s[len(prefix):-len(suffix)] \
  104. if s.startswith(prefix) and s.endswith(suffix) \
  105. else s
  106. def make_code_snippet(s):
  107. return '\n'.join([' {0}'.format(l) for l in s.split('\n')])
  108. def what_we_have_so_far_docs(doc_dir, qbk, defs, sections):
  109. files = {}
  110. so_far = ''
  111. sections_with_definition = []
  112. for s in sections:
  113. if so_far != '':
  114. files[os.path.join(doc_dir, 'before_{0}.qbk'.format(s))] = \
  115. '[#before_{0}]\n[\'Definitions before section {1}]\n\n{2}\n'.format(
  116. s,
  117. s.replace('_', '.') + '.',
  118. so_far
  119. )
  120. sections_with_definition.append(s)
  121. so_far = so_far + '\n'.join([
  122. '{0}\n'.format(make_code_snippet(remove_metashell_protection(d)))
  123. for (sec, d) in defs
  124. if sec == s and not d.startswith('//')
  125. ])
  126. is_section = re.compile('^\[section (([0-9]\.)+)')
  127. note_prefix = \
  128. '[note Note that you can find everything that has been included and' \
  129. ' defined so far [link before_'
  130. in_definitions_before_each_section = False
  131. result = []
  132. for l in qbk:
  133. if in_definitions_before_each_section:
  134. if l.strip() == '[endsect]':
  135. in_definitions_before_each_section = False
  136. result.append(l)
  137. elif l.strip() == '[section Definitions before each section]':
  138. in_definitions_before_each_section = True
  139. result.append(l)
  140. result.append('\n')
  141. for s in sections_with_definition:
  142. result.append('[include before_{0}.qbk]\n'.format(s))
  143. result.append('\n')
  144. elif not l.startswith(note_prefix):
  145. result.append(l)
  146. m = is_section.match(l)
  147. if m:
  148. section_number = m.group(1).replace('.', '_')[:-1]
  149. if section_number in sections_with_definition:
  150. result.append('{0}{1} here].]\n'.format(note_prefix, section_number))
  151. return (files, result)
  152. def strip_not_finished_line(s):
  153. s = s.strip()
  154. return s[:-1] if s.endswith('\\') else s
  155. def make_copy_paste_friendly(lines):
  156. result = []
  157. for l in lines:
  158. if l.startswith('> '):
  159. result.append(l[2:])
  160. elif l.startswith('...> '):
  161. result[-1] = strip_not_finished_line(result[-1]) + l[5:].lstrip()
  162. return result
  163. def extract_code_snippets(qbk, fn_base):
  164. code_prefix = ' '
  165. files = {}
  166. result = []
  167. in_cpp_code = False
  168. counter = 0
  169. in_copy_paste_friendly_examples = False
  170. skip_empty_lines = False
  171. for l in qbk:
  172. if l.strip() != '' or not skip_empty_lines:
  173. skip_empty_lines = False
  174. if in_copy_paste_friendly_examples:
  175. if 'endsect' in l:
  176. in_copy_paste_friendly_examples = False
  177. result.append('\n')
  178. result.extend([
  179. '[include {0}_{1}.qbk]\n'.format(re.sub('^.*/', '', fn_base), i) \
  180. for i in range(0, counter)
  181. ])
  182. result.append('\n')
  183. result.append(l)
  184. in_copy_paste_friendly_examples = False
  185. elif '[section Copy-paste friendly code examples]' in l:
  186. in_copy_paste_friendly_examples = True
  187. result.append(l)
  188. elif 'copy-paste friendly version' in l:
  189. skip_empty_lines = True
  190. else:
  191. result.append(l)
  192. if in_cpp_code:
  193. if not l.startswith(code_prefix):
  194. in_cpp_code = False
  195. if len(code) > 1:
  196. f = '{0}_{1}'.format(fn_base, counter)
  197. basename_f = re.sub('^.*/', '', f)
  198. files['{0}.qbk'.format(f)] = \
  199. '[#{0}]\n\n{1}\n'.format(
  200. basename_f,
  201. ''.join(
  202. [code_prefix + s for s in make_copy_paste_friendly(code)]
  203. )
  204. )
  205. result.append(
  206. '[link {0} copy-paste friendly version]\n'.format(basename_f)
  207. )
  208. result.append('\n')
  209. counter = counter + 1
  210. elif \
  211. l.startswith(code_prefix + '> ') \
  212. or l.startswith(code_prefix + '...> '):
  213. code.append(l[len(code_prefix):])
  214. elif l.startswith(code_prefix):
  215. in_cpp_code = True
  216. code = [l[len(code_prefix):]]
  217. return (files, result)
  218. def write_file(fn, content):
  219. with open(fn, 'w') as f:
  220. f.write(content)
  221. def write_files(files):
  222. for fn in files:
  223. write_file(fn, files[fn])
  224. def main():
  225. desc = 'Generate headers with the definitions of a Getting Started guide'
  226. parser = argparse.ArgumentParser(description=desc)
  227. parser.add_argument(
  228. '--src',
  229. dest='src',
  230. default='doc/getting_started.qbk',
  231. help='The .qbk source of the Getting Started guide'
  232. )
  233. parser.add_argument(
  234. '--dst',
  235. dest='dst',
  236. default='example/getting_started',
  237. help='The target directory to generate into (all headers in that directory will be deleted!)'
  238. )
  239. args = parser.parse_args()
  240. qbk = open(args.src, 'r').readlines()
  241. delete_old_headers(args.dst)
  242. doc_dir = os.path.dirname(args.src)
  243. (sections, defs) = parse_md(qbk)
  244. files1 = gen_headers(sections, defs, args.dst)
  245. (files2, qbk) = what_we_have_so_far_docs(doc_dir, qbk, defs, sections)
  246. (files3, qbk) = \
  247. extract_code_snippets(
  248. qbk,
  249. args.src[:-4] if args.src.endswith('.qbk') else args.src
  250. )
  251. write_files(files1)
  252. write_files(files2)
  253. write_files(files3)
  254. write_file(args.src, ''.join(qbk))
  255. if __name__ == "__main__":
  256. main()