]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | /* |
2 | * Licensed to the Apache Software Foundation (ASF) under one | |
3 | * or more contributor license agreements. See the NOTICE file | |
4 | * distributed with this work for additional information | |
5 | * regarding copyright ownership. The ASF licenses this file | |
6 | * to you under the Apache License, Version 2.0 (the | |
7 | * "License"); you may not use this file except in compliance | |
8 | * with the License. You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, | |
13 | * software distributed under the License is distributed on an | |
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | * KIND, either express or implied. See the License for the | |
16 | * specific language governing permissions and limitations | |
17 | * under the License. | |
18 | */ | |
19 | ||
20 | #include <string> | |
21 | #include <fstream> | |
22 | #include <iostream> | |
23 | #include <vector> | |
24 | #include <map> | |
25 | ||
26 | #include <stdlib.h> | |
27 | #include <sys/stat.h> | |
28 | #include <sstream> | |
29 | #include "thrift/platform.h" | |
30 | #include "thrift/generate/t_generator.h" | |
31 | #include "thrift/generate/t_html_generator.h" | |
32 | ||
33 | using std::map; | |
34 | using std::ofstream; | |
35 | using std::ostringstream; | |
36 | using std::pair; | |
37 | using std::string; | |
38 | using std::stringstream; | |
39 | using std::vector; | |
40 | ||
41 | static const string endl = "\n"; // avoid ostream << std::endl flushes | |
42 | ||
43 | enum input_type { INPUT_UNKNOWN, INPUT_UTF8, INPUT_PLAIN }; | |
44 | ||
45 | /** | |
46 | * HTML code generator | |
47 | * | |
48 | * mostly copy/pasting/tweaking from mcslee's work. | |
49 | */ | |
50 | class t_html_generator : public t_generator { | |
51 | public: | |
52 | t_html_generator(t_program* program, | |
53 | const std::map<std::string, std::string>& parsed_options, | |
54 | const std::string& option_string) | |
55 | : t_generator(program) { | |
56 | (void)option_string; | |
57 | std::map<std::string, std::string>::const_iterator iter; | |
58 | ||
59 | standalone_ = false; | |
60 | unsafe_ = false; | |
61 | for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { | |
62 | if( iter->first.compare("standalone") == 0) { | |
63 | standalone_ = true; | |
64 | } else if( iter->first.compare("noescape") == 0) { | |
65 | unsafe_ = true; | |
66 | } else { | |
67 | throw "unknown option html:" + iter->first; | |
68 | } | |
69 | } | |
70 | ||
71 | ||
72 | out_dir_base_ = "gen-html"; | |
73 | input_type_ = INPUT_UNKNOWN; | |
74 | ||
75 | escape_.clear(); | |
76 | escape_['&'] = "&"; | |
77 | escape_['<'] = "<"; | |
78 | escape_['>'] = ">"; | |
79 | escape_['"'] = """; | |
80 | escape_['\''] = "'"; | |
81 | ||
82 | init_allowed__markup(); | |
83 | } | |
84 | ||
85 | void generate_program() override; | |
86 | void generate_program_toc(); | |
87 | void generate_program_toc_row(t_program* tprog); | |
88 | void generate_program_toc_rows(t_program* tprog, std::vector<t_program*>& finished); | |
89 | void generate_index(); | |
90 | std::string escape_html(std::string const& str); | |
91 | std::string escape_html_tags(std::string const& str); | |
92 | void generate_css(); | |
93 | void generate_css_content(std::ostream& f_target); | |
94 | void generate_style_tag(); | |
95 | std::string make_file_link(std::string name); | |
96 | bool is_utf8_sequence(std::string const& str, size_t firstpos); | |
97 | void detect_input_encoding(std::string const& str, size_t firstpos); | |
98 | void init_allowed__markup(); | |
99 | ||
100 | /** | |
101 | * Program-level generation functions | |
102 | */ | |
103 | ||
104 | void generate_typedef(t_typedef* ttypedef) override; | |
105 | void generate_enum(t_enum* tenum) override; | |
106 | void generate_const(t_const* tconst) override; | |
107 | void generate_struct(t_struct* tstruct) override; | |
108 | void generate_service(t_service* tservice) override; | |
109 | void generate_xception(t_struct* txception) override; | |
110 | ||
111 | void print_doc(t_doc* tdoc); | |
112 | int print_type(t_type* ttype); | |
113 | void print_const_value(t_type* type, t_const_value* tvalue); | |
114 | void print_fn_args_doc(t_function* tfunction); | |
115 | ||
116 | private: | |
117 | ofstream_with_content_based_conditional_update f_out_; | |
118 | std::string current_file_; | |
119 | input_type input_type_; | |
120 | std::map<std::string, int> allowed_markup; | |
121 | bool standalone_; | |
122 | bool unsafe_; | |
123 | }; | |
124 | ||
125 | /** | |
126 | * Emits the Table of Contents links at the top of the module's page | |
127 | */ | |
128 | void t_html_generator::generate_program_toc() { | |
129 | f_out_ << "<table class=\"table-bordered table-striped " | |
130 | "table-condensed\"><thead><tr><th>Module</th><th>Services</th>" | |
131 | << "<th>Data types</th><th>Constants</th></tr></thead><tbody>" << endl; | |
132 | generate_program_toc_row(program_); | |
133 | f_out_ << "</tbody></table>" << endl; | |
134 | } | |
135 | ||
136 | /** | |
137 | * Recurses through from the provided program and generates a ToC row | |
138 | * for each discovered program exactly once by maintaining the list of | |
139 | * completed rows in 'finished' | |
140 | */ | |
141 | void t_html_generator::generate_program_toc_rows(t_program* tprog, | |
142 | std::vector<t_program*>& finished) { | |
143 | for (auto & iter : finished) { | |
144 | if (tprog->get_path() == iter->get_path()) { | |
145 | return; | |
146 | } | |
147 | } | |
148 | finished.push_back(tprog); | |
149 | generate_program_toc_row(tprog); | |
150 | vector<t_program*> includes = tprog->get_includes(); | |
151 | for (auto & include : includes) { | |
152 | generate_program_toc_rows(include, finished); | |
153 | } | |
154 | } | |
155 | ||
156 | /** | |
157 | * Emits the Table of Contents links at the top of the module's page | |
158 | */ | |
159 | void t_html_generator::generate_program_toc_row(t_program* tprog) { | |
160 | string fname = tprog->get_name() + ".html"; | |
161 | f_out_ << "<tr>" << endl << "<td>" << tprog->get_name() << "</td><td>"; | |
162 | if (!tprog->get_services().empty()) { | |
163 | vector<t_service*> services = tprog->get_services(); | |
164 | vector<t_service*>::iterator sv_iter; | |
165 | for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { | |
166 | string name = get_service_name(*sv_iter); | |
167 | f_out_ << "<a href=\"" << make_file_link(fname) << "#Svc_" << name << "\">" << name | |
168 | << "</a><br/>" << endl; | |
169 | f_out_ << "<ul>" << endl; | |
170 | map<string, string> fn_html; | |
171 | vector<t_function*> functions = (*sv_iter)->get_functions(); | |
172 | vector<t_function*>::iterator fn_iter; | |
173 | for (fn_iter = functions.begin(); fn_iter != functions.end(); ++fn_iter) { | |
174 | string fn_name = (*fn_iter)->get_name(); | |
175 | string html = "<li><a href=\"" + make_file_link(fname) + "#Fn_" + name + "_" + fn_name | |
176 | + "\">" + fn_name + "</a></li>"; | |
177 | fn_html.insert(pair<string, string>(fn_name, html)); | |
178 | } | |
179 | for (auto & html_iter : fn_html) { | |
180 | f_out_ << html_iter.second << endl; | |
181 | } | |
182 | f_out_ << "</ul>" << endl; | |
183 | } | |
184 | } | |
185 | f_out_ << "</td>" << endl << "<td>"; | |
186 | map<string, string> data_types; | |
187 | if (!tprog->get_enums().empty()) { | |
188 | vector<t_enum*> enums = tprog->get_enums(); | |
189 | vector<t_enum*>::iterator en_iter; | |
190 | for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { | |
191 | string name = (*en_iter)->get_name(); | |
192 | // f_out_ << "<a href=\"" << make_file_link(fname) << "#Enum_" << name << "\">" << name | |
193 | // << "</a><br/>" << endl; | |
194 | string html = "<a href=\"" + make_file_link(fname) + "#Enum_" + name + "\">" + name + "</a>"; | |
195 | data_types.insert(pair<string, string>(name, html)); | |
196 | } | |
197 | } | |
198 | if (!tprog->get_typedefs().empty()) { | |
199 | vector<t_typedef*> typedefs = tprog->get_typedefs(); | |
200 | vector<t_typedef*>::iterator td_iter; | |
201 | for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { | |
202 | string name = (*td_iter)->get_symbolic(); | |
203 | // f_out_ << "<a href=\"" << make_file_link(fname) << "#Typedef_" << name << "\">" << name | |
204 | // << "</a><br/>" << endl; | |
205 | string html = "<a href=\"" + make_file_link(fname) + "#Typedef_" + name + "\">" + name | |
206 | + "</a>"; | |
207 | data_types.insert(pair<string, string>(name, html)); | |
208 | } | |
209 | } | |
210 | if (!tprog->get_objects().empty()) { | |
211 | vector<t_struct*> objects = tprog->get_objects(); | |
212 | vector<t_struct*>::iterator o_iter; | |
213 | for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { | |
214 | string name = (*o_iter)->get_name(); | |
215 | // f_out_ << "<a href=\"" << make_file_link(fname) << "#Struct_" << name << "\">" << name | |
216 | //<< "</a><br/>" << endl; | |
217 | string html = "<a href=\"" + make_file_link(fname) + "#Struct_" + name + "\">" + name | |
218 | + "</a>"; | |
219 | data_types.insert(pair<string, string>(name, html)); | |
220 | } | |
221 | } | |
222 | for (auto & data_type : data_types) { | |
223 | f_out_ << data_type.second << "<br/>" << endl; | |
224 | } | |
225 | f_out_ << "</td>" << endl << "<td>"; | |
226 | if (!tprog->get_consts().empty()) { | |
227 | map<string, string> const_html; | |
228 | vector<t_const*> consts = tprog->get_consts(); | |
229 | vector<t_const*>::iterator con_iter; | |
230 | for (con_iter = consts.begin(); con_iter != consts.end(); ++con_iter) { | |
231 | string name = (*con_iter)->get_name(); | |
232 | string html = "<code><a href=\"" + make_file_link(fname) + "#Const_" + name + "\">" + name | |
233 | + "</a></code>"; | |
234 | const_html.insert(pair<string, string>(name, html)); | |
235 | } | |
236 | for (auto & con_iter : const_html) { | |
237 | f_out_ << con_iter.second << "<br/>" << endl; | |
238 | } | |
239 | } | |
240 | f_out_ << "</td>" << endl << "</tr>"; | |
241 | } | |
242 | ||
243 | /** | |
244 | * Prepares for file generation by opening up the necessary file output | |
245 | * stream. | |
246 | */ | |
247 | void t_html_generator::generate_program() { | |
248 | // Make output directory | |
249 | MKDIR(get_out_dir().c_str()); | |
250 | current_file_ = program_->get_name() + ".html"; | |
251 | string fname = get_out_dir() + current_file_; | |
252 | f_out_.open(fname.c_str()); | |
253 | f_out_ << "<!DOCTYPE html>" << endl; | |
254 | f_out_ << "<html lang=\"en\">" << endl; | |
255 | f_out_ << "<head>" << endl; | |
256 | f_out_ << "<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" />" << endl; | |
257 | generate_style_tag(); | |
258 | f_out_ << "<title>Thrift module: " << program_->get_name() << "</title></head><body>" << endl | |
259 | << "<div class=\"container-fluid\">" << endl | |
260 | << "<h1>Thrift module: " << program_->get_name() << "</h1>" << endl; | |
261 | ||
262 | print_doc(program_); | |
263 | ||
264 | generate_program_toc(); | |
265 | ||
266 | if (!program_->get_consts().empty()) { | |
267 | f_out_ << "<hr/><h2 id=\"Constants\">Constants</h2>" << endl; | |
268 | vector<t_const*> consts = program_->get_consts(); | |
269 | f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; | |
270 | f_out_ << "<thead><tr><th>Constant</th><th>Type</th><th>Value</th></tr></thead><tbody>" << endl; | |
271 | generate_consts(consts); | |
272 | f_out_ << "</tbody></table>"; | |
273 | } | |
274 | ||
275 | if (!program_->get_enums().empty()) { | |
276 | f_out_ << "<hr/><h2 id=\"Enumerations\">Enumerations</h2>" << endl; | |
277 | // Generate enums | |
278 | vector<t_enum*> enums = program_->get_enums(); | |
279 | vector<t_enum*>::iterator en_iter; | |
280 | for (en_iter = enums.begin(); en_iter != enums.end(); ++en_iter) { | |
281 | generate_enum(*en_iter); | |
282 | } | |
283 | } | |
284 | ||
285 | if (!program_->get_typedefs().empty()) { | |
286 | f_out_ << "<hr/><h2 id=\"Typedefs\">Type declarations</h2>" << endl; | |
287 | // Generate typedefs | |
288 | vector<t_typedef*> typedefs = program_->get_typedefs(); | |
289 | vector<t_typedef*>::iterator td_iter; | |
290 | for (td_iter = typedefs.begin(); td_iter != typedefs.end(); ++td_iter) { | |
291 | generate_typedef(*td_iter); | |
292 | } | |
293 | } | |
294 | ||
295 | if (!program_->get_objects().empty()) { | |
296 | f_out_ << "<hr/><h2 id=\"Structs\">Data structures</h2>" << endl; | |
297 | // Generate structs and exceptions in declared order | |
298 | vector<t_struct*> objects = program_->get_objects(); | |
299 | vector<t_struct*>::iterator o_iter; | |
300 | for (o_iter = objects.begin(); o_iter != objects.end(); ++o_iter) { | |
301 | if ((*o_iter)->is_xception()) { | |
302 | generate_xception(*o_iter); | |
303 | } else { | |
304 | generate_struct(*o_iter); | |
305 | } | |
306 | } | |
307 | } | |
308 | ||
309 | if (!program_->get_services().empty()) { | |
310 | f_out_ << "<hr/><h2 id=\"Services\">Services</h2>" << endl; | |
311 | // Generate services | |
312 | vector<t_service*> services = program_->get_services(); | |
313 | vector<t_service*>::iterator sv_iter; | |
314 | for (sv_iter = services.begin(); sv_iter != services.end(); ++sv_iter) { | |
315 | service_name_ = get_service_name(*sv_iter); | |
316 | generate_service(*sv_iter); | |
317 | } | |
318 | } | |
319 | ||
320 | f_out_ << "</div></body></html>" << endl; | |
321 | f_out_.close(); | |
322 | ||
323 | generate_index(); | |
324 | generate_css(); | |
325 | } | |
326 | ||
327 | /** | |
328 | * Emits the index.html file for the recursive set of Thrift programs | |
329 | */ | |
330 | void t_html_generator::generate_index() { | |
331 | current_file_ = "index.html"; | |
332 | string index_fname = get_out_dir() + current_file_; | |
333 | f_out_.open(index_fname.c_str()); | |
334 | f_out_ << "<!DOCTYPE html>" << endl << "<html lang=\"en\"><head>" << endl; | |
335 | generate_style_tag(); | |
336 | f_out_ << "<title>All Thrift declarations</title></head><body>" << endl | |
337 | << "<div class=\"container-fluid\">" << endl << "<h1>All Thrift declarations</h1>" << endl; | |
338 | f_out_ << "<table class=\"table-bordered table-striped " | |
339 | "table-condensed\"><thead><tr><th>Module</th><th>Services</th><th>Data types</th>" | |
340 | << "<th>Constants</th></tr></thead><tbody>" << endl; | |
341 | vector<t_program*> programs; | |
342 | generate_program_toc_rows(program_, programs); | |
343 | f_out_ << "</tbody></table>" << endl; | |
344 | f_out_ << "</div></body></html>" << endl; | |
345 | f_out_.close(); | |
346 | } | |
347 | ||
348 | void t_html_generator::generate_css() { | |
349 | if (!standalone_) { | |
350 | current_file_ = "style.css"; | |
351 | string css_fname = get_out_dir() + current_file_; | |
352 | f_out_.open(css_fname.c_str()); | |
353 | generate_css_content(f_out_); | |
354 | f_out_.close(); | |
355 | } | |
356 | } | |
357 | ||
358 | void t_html_generator::generate_css_content(std::ostream& f_target) { | |
359 | f_target << BOOTSTRAP_CSS() << endl; | |
360 | f_target << "/* Auto-generated CSS for generated Thrift docs */" << endl; | |
361 | f_target << "h3, h4 { margin-bottom: 6px; }" << endl; | |
362 | f_target << "div.definition { border: 1px solid #CCC; margin-bottom: 10px; padding: 10px; }" | |
363 | << endl; | |
364 | f_target << "div.extends { margin: -0.5em 0 1em 5em }" << endl; | |
365 | f_target << "td { vertical-align: top; }" << endl; | |
366 | f_target << "table { empty-cells: show; }" << endl; | |
367 | f_target << "code { line-height: 20px; }" << endl; | |
368 | f_target << ".table-bordered th, .table-bordered td { border-bottom: 1px solid #DDDDDD; }" | |
369 | << endl; | |
370 | } | |
371 | ||
372 | /** | |
373 | * Generates the CSS tag. | |
374 | * Depending on "standalone", either a CSS file link (default), or the entire CSS is embedded | |
375 | * inline. | |
376 | */ | |
377 | void t_html_generator::generate_style_tag() { | |
378 | if (!standalone_) { | |
379 | f_out_ << "<link href=\"style.css\" rel=\"stylesheet\" type=\"text/css\"/>" << endl; | |
380 | } else { | |
381 | f_out_ << "<style type=\"text/css\"/><!--" << endl; | |
382 | generate_css_content(f_out_); | |
383 | f_out_ << "--></style>" << endl; | |
384 | } | |
385 | } | |
386 | ||
387 | /** | |
388 | * Returns the target file for a <a href> link | |
389 | * The returned string is empty, whenever filename refers to the current file. | |
390 | */ | |
391 | std::string t_html_generator::make_file_link(std::string filename) { | |
392 | return (current_file_.compare(filename) != 0) ? filename : ""; | |
393 | } | |
394 | ||
395 | /** | |
396 | * If the provided documentable object has documentation attached, this | |
397 | * will emit it to the output stream in HTML format. | |
398 | */ | |
399 | void t_html_generator::print_doc(t_doc* tdoc) { | |
400 | if (tdoc->has_doc()) { | |
401 | if (unsafe_) { | |
402 | f_out_ << tdoc->get_doc() << "<br/>"; | |
403 | } else { | |
404 | f_out_ << "<pre>" << escape_html(tdoc->get_doc()) << "</pre><br/>"; | |
405 | } | |
406 | } | |
407 | } | |
408 | ||
409 | bool t_html_generator::is_utf8_sequence(std::string const& str, size_t firstpos) { | |
410 | // leading char determines the length of the sequence | |
411 | unsigned char c = str.at(firstpos); | |
412 | int count = 0; | |
413 | if ((c & 0xE0) == 0xC0) { | |
414 | count = 1; | |
415 | } else if ((c & 0xF0) == 0xE0) { | |
416 | count = 2; | |
417 | } else if ((c & 0xF8) == 0xF0) { | |
418 | count = 3; | |
419 | } else if ((c & 0xFC) == 0xF8) { | |
420 | count = 4; | |
421 | } else if ((c & 0xFE) == 0xFC) { | |
422 | count = 5; | |
423 | } else { | |
424 | // pdebug("UTF-8 test: char '%c' (%d) is not a valid UTF-8 leading byte", c, int(c)); | |
425 | return false; // no UTF-8 | |
426 | } | |
427 | ||
428 | // following chars | |
429 | size_t pos = firstpos + 1; | |
430 | while ((pos < str.length()) && (0 < count)) { | |
431 | c = str.at(pos); | |
432 | if ((c & 0xC0) != 0x80) { | |
433 | // pdebug("UTF-8 test: char '%c' (%d) is not a valid UTF-8 following byte", c, int(c)); | |
434 | return false; // no UTF-8 | |
435 | } | |
436 | --count; | |
437 | ++pos; | |
438 | } | |
439 | ||
440 | // true if the sequence is complete | |
441 | return (0 == count); | |
442 | } | |
443 | ||
444 | void t_html_generator::detect_input_encoding(std::string const& str, size_t firstpos) { | |
445 | if (is_utf8_sequence(str, firstpos)) { | |
446 | pdebug("Input seems to be already UTF-8 encoded"); | |
447 | input_type_ = INPUT_UTF8; | |
448 | return; | |
449 | } | |
450 | ||
451 | // fallback | |
452 | pwarning(1, "Input is not UTF-8, treating as plain ANSI"); | |
453 | input_type_ = INPUT_PLAIN; | |
454 | } | |
455 | ||
456 | void t_html_generator::init_allowed__markup() { | |
457 | allowed_markup.clear(); | |
458 | // standalone tags | |
459 | allowed_markup["br"] = 1; | |
460 | allowed_markup["br/"] = 1; | |
461 | allowed_markup["img"] = 1; | |
462 | // paired tags | |
463 | allowed_markup["b"] = 1; | |
464 | allowed_markup["/b"] = 1; | |
465 | allowed_markup["u"] = 1; | |
466 | allowed_markup["/u"] = 1; | |
467 | allowed_markup["i"] = 1; | |
468 | allowed_markup["/i"] = 1; | |
469 | allowed_markup["s"] = 1; | |
470 | allowed_markup["/s"] = 1; | |
471 | allowed_markup["big"] = 1; | |
472 | allowed_markup["/big"] = 1; | |
473 | allowed_markup["small"] = 1; | |
474 | allowed_markup["/small"] = 1; | |
475 | allowed_markup["sup"] = 1; | |
476 | allowed_markup["/sup"] = 1; | |
477 | allowed_markup["sub"] = 1; | |
478 | allowed_markup["/sub"] = 1; | |
479 | allowed_markup["pre"] = 1; | |
480 | allowed_markup["/pre"] = 1; | |
481 | allowed_markup["tt"] = 1; | |
482 | allowed_markup["/tt"] = 1; | |
483 | allowed_markup["ul"] = 1; | |
484 | allowed_markup["/ul"] = 1; | |
485 | allowed_markup["ol"] = 1; | |
486 | allowed_markup["/ol"] = 1; | |
487 | allowed_markup["li"] = 1; | |
488 | allowed_markup["/li"] = 1; | |
489 | allowed_markup["a"] = 1; | |
490 | allowed_markup["/a"] = 1; | |
491 | allowed_markup["p"] = 1; | |
492 | allowed_markup["/p"] = 1; | |
493 | allowed_markup["code"] = 1; | |
494 | allowed_markup["/code"] = 1; | |
495 | allowed_markup["dl"] = 1; | |
496 | allowed_markup["/dl"] = 1; | |
497 | allowed_markup["dt"] = 1; | |
498 | allowed_markup["/dt"] = 1; | |
499 | allowed_markup["dd"] = 1; | |
500 | allowed_markup["/dd"] = 1; | |
501 | allowed_markup["h1"] = 1; | |
502 | allowed_markup["/h1"] = 1; | |
503 | allowed_markup["h2"] = 1; | |
504 | allowed_markup["/h2"] = 1; | |
505 | allowed_markup["h3"] = 1; | |
506 | allowed_markup["/h3"] = 1; | |
507 | allowed_markup["h4"] = 1; | |
508 | allowed_markup["/h4"] = 1; | |
509 | allowed_markup["h5"] = 1; | |
510 | allowed_markup["/h5"] = 1; | |
511 | allowed_markup["h6"] = 1; | |
512 | allowed_markup["/h6"] = 1; | |
513 | } | |
514 | ||
515 | std::string t_html_generator::escape_html_tags(std::string const& str) { | |
516 | std::ostringstream result; | |
517 | ||
518 | unsigned char c = '?'; | |
519 | size_t lastpos; | |
520 | size_t firstpos = 0; | |
521 | while (firstpos < str.length()) { | |
522 | ||
523 | // look for non-ASCII char | |
524 | lastpos = firstpos; | |
525 | while (lastpos < str.length()) { | |
526 | c = str.at(lastpos); | |
527 | if (('<' == c) || ('>' == c)) { | |
528 | break; | |
529 | } | |
530 | ++lastpos; | |
531 | } | |
532 | ||
533 | // copy what we got so far | |
534 | if (lastpos > firstpos) { | |
535 | result << str.substr(firstpos, lastpos - firstpos); | |
536 | firstpos = lastpos; | |
537 | } | |
538 | ||
539 | // reached the end? | |
540 | if (firstpos >= str.length()) { | |
541 | break; | |
542 | } | |
543 | ||
544 | // tag end without corresponding begin | |
545 | ++firstpos; | |
546 | if ('>' == c) { | |
547 | result << ">"; | |
548 | continue; | |
549 | } | |
550 | ||
551 | // extract the tag | |
552 | std::ostringstream tagstream; | |
553 | while (firstpos < str.length()) { | |
554 | c = str.at(firstpos); | |
555 | ++firstpos; | |
556 | if ('<' == c) { | |
557 | tagstream << "<"; // nested begin? | |
558 | } else if ('>' == c) { | |
559 | break; | |
560 | } else { | |
561 | tagstream << c; // not very efficient, but tags should be quite short | |
562 | } | |
563 | } | |
564 | ||
565 | // we allow for several markup in docstrings, all else will become escaped | |
566 | string tag_content = tagstream.str(); | |
567 | string tag_key = tag_content; | |
568 | size_t first_white = tag_key.find_first_of(" \t\f\v\n\r"); | |
569 | if (first_white != string::npos) { | |
570 | tag_key.erase(first_white); | |
571 | } | |
572 | for (char & i : tag_key) { | |
573 | i = tolower(i); | |
574 | } | |
575 | if (allowed_markup.find(tag_key) != allowed_markup.end()) { | |
576 | result << "<" << tag_content << ">"; | |
577 | } else { | |
578 | result << "<" << tagstream.str() << ">"; | |
579 | pverbose("illegal markup <%s> in doc-comment\n", tag_key.c_str()); | |
580 | } | |
581 | } | |
582 | ||
583 | return result.str(); | |
584 | } | |
585 | ||
586 | std::string t_html_generator::escape_html(std::string const& str) { | |
587 | // the generated HTML header says it is UTF-8 encoded | |
588 | // if UTF-8 input has been detected before, we don't need to change anything | |
589 | if (input_type_ == INPUT_UTF8) { | |
590 | return escape_html_tags(str); | |
591 | } | |
592 | ||
593 | // convert unsafe chars to their &#<num>; equivalent | |
594 | std::ostringstream result; | |
595 | unsigned char c = '?'; | |
596 | unsigned int ic = 0; | |
597 | size_t lastpos; | |
598 | size_t firstpos = 0; | |
599 | while (firstpos < str.length()) { | |
600 | ||
601 | // look for non-ASCII char | |
602 | lastpos = firstpos; | |
603 | while (lastpos < str.length()) { | |
604 | c = str.at(lastpos); | |
605 | ic = c; | |
606 | if ((32 > ic) || (127 < ic)) { | |
607 | break; | |
608 | } | |
609 | ++lastpos; | |
610 | } | |
611 | ||
612 | // copy what we got so far | |
613 | if (lastpos > firstpos) { | |
614 | result << str.substr(firstpos, lastpos - firstpos); | |
615 | firstpos = lastpos; | |
616 | } | |
617 | ||
618 | // reached the end? | |
619 | if (firstpos >= str.length()) { | |
620 | break; | |
621 | } | |
622 | ||
623 | // some control code? | |
624 | if (ic <= 31) { | |
625 | switch (c) { | |
626 | case '\r': | |
627 | case '\n': | |
628 | case '\t': | |
629 | result << c; | |
630 | break; | |
631 | default: // silently consume all other ctrl chars | |
632 | break; | |
633 | } | |
634 | ++firstpos; | |
635 | continue; | |
636 | } | |
637 | ||
638 | // reached the end? | |
639 | if (firstpos >= str.length()) { | |
640 | break; | |
641 | } | |
642 | ||
643 | // try to detect input encoding | |
644 | if (input_type_ == INPUT_UNKNOWN) { | |
645 | detect_input_encoding(str, firstpos); | |
646 | if (input_type_ == INPUT_UTF8) { | |
647 | lastpos = str.length(); | |
648 | result << str.substr(firstpos, lastpos - firstpos); | |
649 | break; | |
650 | } | |
651 | } | |
652 | ||
653 | // convert the character to something useful based on the detected encoding | |
654 | switch (input_type_) { | |
655 | case INPUT_PLAIN: | |
656 | result << "&#" << ic << ";"; | |
657 | ++firstpos; | |
658 | break; | |
659 | default: | |
660 | throw "Unexpected or unrecognized input encoding"; | |
661 | } | |
662 | } | |
663 | ||
664 | return escape_html_tags(result.str()); | |
665 | } | |
666 | ||
667 | /** | |
668 | * Prints out the provided type in HTML | |
669 | */ | |
670 | int t_html_generator::print_type(t_type* ttype) { | |
671 | std::string::size_type len = 0; | |
672 | f_out_ << "<code>"; | |
673 | if (ttype->is_container()) { | |
674 | if (ttype->is_list()) { | |
675 | f_out_ << "list<"; | |
676 | len = 6 + print_type(((t_list*)ttype)->get_elem_type()); | |
677 | f_out_ << ">"; | |
678 | } else if (ttype->is_set()) { | |
679 | f_out_ << "set<"; | |
680 | len = 5 + print_type(((t_set*)ttype)->get_elem_type()); | |
681 | f_out_ << ">"; | |
682 | } else if (ttype->is_map()) { | |
683 | f_out_ << "map<"; | |
684 | len = 5 + print_type(((t_map*)ttype)->get_key_type()); | |
685 | f_out_ << ", "; | |
686 | len += print_type(((t_map*)ttype)->get_val_type()); | |
687 | f_out_ << ">"; | |
688 | } | |
689 | } else if (ttype->is_base_type()) { | |
690 | f_out_ << (ttype->is_binary() ? "binary" : ttype->get_name()); | |
691 | len = ttype->get_name().size(); | |
692 | } else { | |
693 | string prog_name = ttype->get_program()->get_name(); | |
694 | string type_name = ttype->get_name(); | |
695 | f_out_ << "<a href=\"" << make_file_link(prog_name + ".html") << "#"; | |
696 | if (ttype->is_typedef()) { | |
697 | f_out_ << "Struct_"; | |
698 | } else if (ttype->is_struct() || ttype->is_xception()) { | |
699 | f_out_ << "Struct_"; | |
700 | } else if (ttype->is_enum()) { | |
701 | f_out_ << "Enum_"; | |
702 | } else if (ttype->is_service()) { | |
703 | f_out_ << "Svc_"; | |
704 | } | |
705 | f_out_ << type_name << "\">"; | |
706 | len = type_name.size(); | |
707 | if (ttype->get_program() != program_) { | |
708 | f_out_ << prog_name << "."; | |
709 | len += prog_name.size() + 1; | |
710 | } | |
711 | f_out_ << type_name << "</a>"; | |
712 | } | |
713 | f_out_ << "</code>"; | |
714 | return (int)len; | |
715 | } | |
716 | ||
717 | /** | |
718 | * Prints out an HTML representation of the provided constant value | |
719 | */ | |
720 | void t_html_generator::print_const_value(t_type* type, t_const_value* tvalue) { | |
721 | ||
722 | // if tvalue is an identifier, the constant content is already shown elsewhere | |
723 | if (tvalue->get_type() == t_const_value::CV_IDENTIFIER) { | |
724 | string fname = program_->get_name() + ".html"; | |
725 | string name = escape_html(tvalue->get_identifier()); | |
726 | f_out_ << "<code><a href=\"" + make_file_link(fname) + "#Const_" + name + "\">" + name | |
727 | + "</a></code>"; | |
728 | return; | |
729 | } | |
730 | ||
731 | t_type* truetype = type; | |
732 | while (truetype->is_typedef()) { | |
733 | truetype = ((t_typedef*)truetype)->get_type(); | |
734 | } | |
735 | ||
736 | bool first = true; | |
737 | if (truetype->is_base_type()) { | |
738 | t_base_type::t_base tbase = ((t_base_type*)truetype)->get_base(); | |
739 | switch (tbase) { | |
740 | case t_base_type::TYPE_STRING: | |
741 | f_out_ << '"' << escape_html(get_escaped_string(tvalue)) << '"'; | |
742 | break; | |
743 | case t_base_type::TYPE_BOOL: | |
744 | f_out_ << ((tvalue->get_integer() != 0) ? "true" : "false"); | |
745 | break; | |
746 | case t_base_type::TYPE_I8: | |
747 | f_out_ << tvalue->get_integer(); | |
748 | break; | |
749 | case t_base_type::TYPE_I16: | |
750 | f_out_ << tvalue->get_integer(); | |
751 | break; | |
752 | case t_base_type::TYPE_I32: | |
753 | f_out_ << tvalue->get_integer(); | |
754 | break; | |
755 | case t_base_type::TYPE_I64: | |
756 | f_out_ << tvalue->get_integer(); | |
757 | break; | |
758 | case t_base_type::TYPE_DOUBLE: | |
759 | if (tvalue->get_type() == t_const_value::CV_INTEGER) { | |
760 | f_out_ << tvalue->get_integer(); | |
761 | } else { | |
762 | f_out_ << tvalue->get_double(); | |
763 | } | |
764 | break; | |
765 | default: | |
766 | f_out_ << "UNKNOWN BASE TYPE"; | |
767 | break; | |
768 | } | |
769 | } else if (truetype->is_enum()) { | |
770 | f_out_ << escape_html(truetype->get_name()) << "." | |
771 | << escape_html(tvalue->get_identifier_name()); | |
772 | } else if (truetype->is_struct() || truetype->is_xception()) { | |
773 | f_out_ << "{ "; | |
774 | const vector<t_field*>& fields = ((t_struct*)truetype)->get_members(); | |
775 | vector<t_field*>::const_iterator f_iter; | |
776 | const map<t_const_value*, t_const_value*, t_const_value::value_compare>& val = tvalue->get_map(); | |
777 | map<t_const_value*, t_const_value*, t_const_value::value_compare>::const_iterator v_iter; | |
778 | for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { | |
779 | t_type* field_type = NULL; | |
780 | for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { | |
781 | if ((*f_iter)->get_name() == v_iter->first->get_string()) { | |
782 | field_type = (*f_iter)->get_type(); | |
783 | } | |
784 | } | |
785 | if (field_type == NULL) { | |
786 | throw "type error: " + truetype->get_name() + " has no field " | |
787 | + v_iter->first->get_string(); | |
788 | } | |
789 | if (!first) { | |
790 | f_out_ << ", "; | |
791 | } | |
792 | first = false; | |
793 | f_out_ << escape_html(v_iter->first->get_string()) << " = "; | |
794 | print_const_value(field_type, v_iter->second); | |
795 | } | |
796 | f_out_ << " }"; | |
797 | } else if (truetype->is_map()) { | |
798 | f_out_ << "{ "; | |
799 | map<t_const_value*, t_const_value*, t_const_value::value_compare> map_elems = tvalue->get_map(); | |
800 | map<t_const_value*, t_const_value*, t_const_value::value_compare>::iterator map_iter; | |
801 | for (map_iter = map_elems.begin(); map_iter != map_elems.end(); map_iter++) { | |
802 | if (!first) { | |
803 | f_out_ << ", "; | |
804 | } | |
805 | first = false; | |
806 | print_const_value(((t_map*)truetype)->get_key_type(), map_iter->first); | |
807 | f_out_ << " = "; | |
808 | print_const_value(((t_map*)truetype)->get_val_type(), map_iter->second); | |
809 | } | |
810 | f_out_ << " }"; | |
811 | } else if (truetype->is_list()) { | |
812 | f_out_ << "{ "; | |
813 | vector<t_const_value*> list_elems = tvalue->get_list(); | |
814 | ; | |
815 | vector<t_const_value*>::iterator list_iter; | |
816 | for (list_iter = list_elems.begin(); list_iter != list_elems.end(); list_iter++) { | |
817 | if (!first) { | |
818 | f_out_ << ", "; | |
819 | } | |
820 | first = false; | |
821 | print_const_value(((t_list*)truetype)->get_elem_type(), *list_iter); | |
822 | } | |
823 | f_out_ << " }"; | |
824 | } else if (truetype->is_set()) { | |
825 | f_out_ << "{ "; | |
826 | vector<t_const_value*> list_elems = tvalue->get_list(); | |
827 | ; | |
828 | vector<t_const_value*>::iterator list_iter; | |
829 | for (list_iter = list_elems.begin(); list_iter != list_elems.end(); list_iter++) { | |
830 | if (!first) { | |
831 | f_out_ << ", "; | |
832 | } | |
833 | first = false; | |
834 | print_const_value(((t_set*)truetype)->get_elem_type(), *list_iter); | |
835 | } | |
836 | f_out_ << " }"; | |
837 | } else { | |
838 | f_out_ << "UNKNOWN TYPE"; | |
839 | } | |
840 | } | |
841 | ||
842 | /** | |
843 | * Prints out documentation for arguments/exceptions of a function, if any documentation has been | |
844 | * supplied. | |
845 | */ | |
846 | void t_html_generator::print_fn_args_doc(t_function* tfunction) { | |
847 | bool has_docs = false; | |
848 | vector<t_field*> args = tfunction->get_arglist()->get_members(); | |
849 | vector<t_field*>::iterator arg_iter = args.begin(); | |
850 | if (arg_iter != args.end()) { | |
851 | for (; arg_iter != args.end(); arg_iter++) { | |
852 | if ((*arg_iter)->has_doc() && !(*arg_iter)->get_doc().empty()) | |
853 | has_docs = true; | |
854 | } | |
855 | if (has_docs) { | |
856 | arg_iter = args.begin(); | |
857 | f_out_ << "<br/><h4 id=\"Parameters_" << service_name_ << "_" << tfunction->get_name() | |
858 | << "\">Parameters</h4>" << endl; | |
859 | f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; | |
860 | f_out_ << "<thead><tr><th>Name</th><th>Description</th></tr></thead><tbody>"; | |
861 | for (; arg_iter != args.end(); arg_iter++) { | |
862 | f_out_ << "<tr><td>" << (*arg_iter)->get_name(); | |
863 | f_out_ << "</td><td>"; | |
864 | f_out_ << escape_html((*arg_iter)->get_doc()); | |
865 | f_out_ << "</td></tr>" << endl; | |
866 | } | |
867 | f_out_ << "</tbody></table>"; | |
868 | } | |
869 | } | |
870 | ||
871 | has_docs = false; | |
872 | vector<t_field*> excepts = tfunction->get_xceptions()->get_members(); | |
873 | vector<t_field*>::iterator ex_iter = excepts.begin(); | |
874 | if (ex_iter != excepts.end()) { | |
875 | for (; ex_iter != excepts.end(); ex_iter++) { | |
876 | if ((*ex_iter)->has_doc() && !(*ex_iter)->get_doc().empty()) | |
877 | has_docs = true; | |
878 | } | |
879 | if (has_docs) { | |
880 | ex_iter = excepts.begin(); | |
881 | f_out_ << "<br/><h4 id=\"Exceptions_" << service_name_ << "_" << tfunction->get_name() | |
882 | << "\">Exceptions</h4>" << endl; | |
883 | f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; | |
884 | f_out_ << "<thead><tr><th>Type</th><th>Description</th></tr></thead><tbody>"; | |
885 | for (; ex_iter != excepts.end(); ex_iter++) { | |
886 | f_out_ << "<tr><td>" << (*ex_iter)->get_type()->get_name(); | |
887 | f_out_ << "</td><td>"; | |
888 | f_out_ << escape_html((*ex_iter)->get_doc()); | |
889 | f_out_ << "</td></tr>" << endl; | |
890 | } | |
891 | f_out_ << "</tbody></table>"; | |
892 | } | |
893 | } | |
894 | } | |
895 | ||
896 | /** | |
897 | * Generates a typedef. | |
898 | * | |
899 | * @param ttypedef The type definition | |
900 | */ | |
901 | void t_html_generator::generate_typedef(t_typedef* ttypedef) { | |
902 | string name = ttypedef->get_name(); | |
903 | f_out_ << "<div class=\"definition\">"; | |
904 | f_out_ << "<h3 id=\"Typedef_" << name << "\">Typedef: " << name << "</h3>" << endl; | |
905 | f_out_ << "<p><strong>Base type:</strong> "; | |
906 | print_type(ttypedef->get_type()); | |
907 | f_out_ << "</p>" << endl; | |
908 | print_doc(ttypedef); | |
909 | f_out_ << "</div>" << endl; | |
910 | } | |
911 | ||
912 | /** | |
913 | * Generates code for an enumerated type. | |
914 | * | |
915 | * @param tenum The enumeration | |
916 | */ | |
917 | void t_html_generator::generate_enum(t_enum* tenum) { | |
918 | string name = tenum->get_name(); | |
919 | f_out_ << "<div class=\"definition\">"; | |
920 | f_out_ << "<h3 id=\"Enum_" << name << "\">Enumeration: " << name << "</h3>" << endl; | |
921 | print_doc(tenum); | |
922 | vector<t_enum_value*> values = tenum->get_constants(); | |
923 | vector<t_enum_value*>::iterator val_iter; | |
924 | f_out_ << "<br/><table class=\"table-bordered table-striped table-condensed\">" << endl; | |
925 | for (val_iter = values.begin(); val_iter != values.end(); ++val_iter) { | |
926 | f_out_ << "<tr><td><code>"; | |
927 | f_out_ << (*val_iter)->get_name(); | |
928 | f_out_ << "</code></td><td><code>"; | |
929 | f_out_ << (*val_iter)->get_value(); | |
930 | f_out_ << "</code></td><td>" << endl; | |
931 | print_doc((*val_iter)); | |
932 | f_out_ << "</td></tr>" << endl; | |
933 | } | |
934 | f_out_ << "</table></div>" << endl; | |
935 | } | |
936 | ||
937 | /** | |
938 | * Generates a constant value | |
939 | */ | |
940 | void t_html_generator::generate_const(t_const* tconst) { | |
941 | string name = tconst->get_name(); | |
942 | f_out_ << "<tr id=\"Const_" << name << "\"><td><code>" << name << "</code></td><td>"; | |
943 | print_type(tconst->get_type()); | |
944 | f_out_ << "</td><td><code>"; | |
945 | print_const_value(tconst->get_type(), tconst->get_value()); | |
946 | f_out_ << "</code></td></tr>"; | |
947 | if (tconst->has_doc()) { | |
948 | f_out_ << "<tr><td colspan=\"3\"><blockquote>"; | |
949 | print_doc(tconst); | |
950 | f_out_ << "</blockquote></td></tr>"; | |
951 | } | |
952 | } | |
953 | ||
954 | /** | |
955 | * Generates a struct definition for a thrift data type. | |
956 | * | |
957 | * @param tstruct The struct definition | |
958 | */ | |
959 | void t_html_generator::generate_struct(t_struct* tstruct) { | |
960 | string name = tstruct->get_name(); | |
961 | f_out_ << "<div class=\"definition\">"; | |
962 | f_out_ << "<h3 id=\"Struct_" << name << "\">"; | |
963 | if (tstruct->is_xception()) { | |
964 | f_out_ << "Exception: "; | |
965 | } else if (tstruct->is_union()) { | |
966 | f_out_ << "Union: "; | |
967 | } else { | |
968 | f_out_ << "Struct: "; | |
969 | } | |
970 | f_out_ << name << "</h3>" << endl; | |
971 | vector<t_field*> members = tstruct->get_members(); | |
972 | vector<t_field*>::iterator mem_iter = members.begin(); | |
973 | f_out_ << "<table class=\"table-bordered table-striped table-condensed\">"; | |
974 | f_out_ << "<thead><tr><th>Key</th><th>Field</th><th>Type</th><th>Description</th><th>Requiredness</" | |
975 | "th><th>Default value</th></tr></thead><tbody>" << endl; | |
976 | for (; mem_iter != members.end(); mem_iter++) { | |
977 | f_out_ << "<tr><td>" << (*mem_iter)->get_key() << "</td><td>"; | |
978 | f_out_ << (*mem_iter)->get_name(); | |
979 | f_out_ << "</td><td>"; | |
980 | print_type((*mem_iter)->get_type()); | |
981 | f_out_ << "</td><td>"; | |
982 | f_out_ << escape_html((*mem_iter)->get_doc()); | |
983 | f_out_ << "</td><td>"; | |
984 | if ((*mem_iter)->get_req() == t_field::T_OPTIONAL) { | |
985 | f_out_ << "optional"; | |
986 | } else if ((*mem_iter)->get_req() == t_field::T_REQUIRED) { | |
987 | f_out_ << "required"; | |
988 | } else { | |
989 | f_out_ << "default"; | |
990 | } | |
991 | f_out_ << "</td><td>"; | |
992 | t_const_value* default_val = (*mem_iter)->get_value(); | |
993 | if (default_val != NULL) { | |
994 | f_out_ << "<code>"; | |
995 | print_const_value((*mem_iter)->get_type(), default_val); | |
996 | f_out_ << "</code>"; | |
997 | } | |
998 | f_out_ << "</td></tr>" << endl; | |
999 | } | |
1000 | f_out_ << "</tbody></table><br/>"; | |
1001 | print_doc(tstruct); | |
1002 | f_out_ << "</div>"; | |
1003 | } | |
1004 | ||
1005 | /** | |
1006 | * Exceptions are special structs | |
1007 | * | |
1008 | * @param tstruct The struct definition | |
1009 | */ | |
1010 | void t_html_generator::generate_xception(t_struct* txception) { | |
1011 | generate_struct(txception); | |
1012 | } | |
1013 | ||
1014 | /** | |
1015 | * Generates the HTML block for a Thrift service. | |
1016 | * | |
1017 | * @param tservice The service definition | |
1018 | */ | |
1019 | void t_html_generator::generate_service(t_service* tservice) { | |
1020 | f_out_ << "<h3 id=\"Svc_" << service_name_ << "\">Service: " << service_name_ << "</h3>" << endl; | |
1021 | ||
1022 | if (tservice->get_extends()) { | |
1023 | f_out_ << "<div class=\"extends\"><em>extends</em> "; | |
1024 | print_type(tservice->get_extends()); | |
1025 | f_out_ << "</div>\n"; | |
1026 | } | |
1027 | print_doc(tservice); | |
1028 | vector<t_function*> functions = tservice->get_functions(); | |
1029 | vector<t_function*>::iterator fn_iter = functions.begin(); | |
1030 | for (; fn_iter != functions.end(); fn_iter++) { | |
1031 | string fn_name = (*fn_iter)->get_name(); | |
1032 | f_out_ << "<div class=\"definition\">"; | |
1033 | f_out_ << "<h4 id=\"Fn_" << service_name_ << "_" << fn_name << "\">Function: " << service_name_ | |
1034 | << "." << fn_name << "</h4>" << endl; | |
1035 | f_out_ << "<pre>"; | |
1036 | std::string::size_type offset = print_type((*fn_iter)->get_returntype()); | |
1037 | bool first = true; | |
1038 | f_out_ << " " << fn_name << "("; | |
1039 | offset += fn_name.size() + 2; | |
1040 | vector<t_field*> args = (*fn_iter)->get_arglist()->get_members(); | |
1041 | vector<t_field*>::iterator arg_iter = args.begin(); | |
1042 | for (; arg_iter != args.end(); arg_iter++) { | |
1043 | if (!first) { | |
1044 | f_out_ << "," << endl; | |
1045 | for (std::string::size_type i = 0; i < offset; ++i) { | |
1046 | f_out_ << " "; | |
1047 | } | |
1048 | } | |
1049 | first = false; | |
1050 | print_type((*arg_iter)->get_type()); | |
1051 | f_out_ << " " << (*arg_iter)->get_name(); | |
1052 | if ((*arg_iter)->get_value() != NULL) { | |
1053 | f_out_ << " = "; | |
1054 | print_const_value((*arg_iter)->get_type(), (*arg_iter)->get_value()); | |
1055 | } | |
1056 | } | |
1057 | f_out_ << ")" << endl; | |
1058 | first = true; | |
1059 | vector<t_field*> excepts = (*fn_iter)->get_xceptions()->get_members(); | |
1060 | vector<t_field*>::iterator ex_iter = excepts.begin(); | |
1061 | if (ex_iter != excepts.end()) { | |
1062 | f_out_ << " throws "; | |
1063 | for (; ex_iter != excepts.end(); ex_iter++) { | |
1064 | if (!first) { | |
1065 | f_out_ << ", "; | |
1066 | } | |
1067 | first = false; | |
1068 | print_type((*ex_iter)->get_type()); | |
1069 | } | |
1070 | f_out_ << endl; | |
1071 | } | |
1072 | f_out_ << "</pre>"; | |
1073 | print_doc(*fn_iter); | |
1074 | print_fn_args_doc(*fn_iter); | |
1075 | f_out_ << "</div>"; | |
1076 | } | |
1077 | } | |
1078 | ||
1079 | THRIFT_REGISTER_GENERATOR( | |
1080 | html, | |
1081 | "HTML", | |
1082 | " standalone: Self-contained mode, includes all CSS in the HTML files.\n" | |
1083 | " Generates no style.css file, but HTML files will be larger.\n" | |
1084 | " noescape: Do not escape html in doc text.\n") |