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