1 /*=============================================================================
2 Copyright (c) 2002 2004 2006 Joel de Guzman
3 Copyright (c) 2004 Eric Niebler
4 http://spirit.sourceforge.net/
5
6 Use, modification and distribution is subject to the Boost Software
7 License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
8 http://www.boost.org/LICENSE_1_0.txt)
9 =============================================================================*/
10 #include "quickbook.hpp"
11 #include <boost/algorithm/string/classification.hpp>
12 #include <boost/algorithm/string/split.hpp>
13 #include <boost/filesystem/fstream.hpp>
14 #include <boost/filesystem/operations.hpp>
15 #include <boost/filesystem/path.hpp>
16 #include <boost/program_options.hpp>
17 #include <boost/range/algorithm/replace.hpp>
18 #include <boost/range/algorithm/transform.hpp>
19 #include <boost/ref.hpp>
20 #include <boost/version.hpp>
21 #include "actions.hpp"
22 #include "bb2html.hpp"
23 #include "document_state.hpp"
24 #include "files.hpp"
25 #include "for.hpp"
26 #include "grammar.hpp"
27 #include "path.hpp"
28 #include "post_process.hpp"
29 #include "state.hpp"
30 #include "stream.hpp"
31 #include "utils.hpp"
32
33 #include <iterator>
34 #include <stdexcept>
35 #include <vector>
36
37 #if defined(_WIN32)
38 #include <windows.h>
39 #include <shellapi.h>
40 #endif
41
42 #if (defined(BOOST_MSVC) && (BOOST_MSVC <= 1310))
43 #pragma warning(disable : 4355)
44 #endif
45
46 #define QUICKBOOK_VERSION "Quickbook Version 1.7.2"
47
48 namespace quickbook
49 {
50 namespace cl = boost::spirit::classic;
51 namespace fs = boost::filesystem;
52
53 tm* current_time; // the current time
54 tm* current_gm_time; // the current UTC time
55 bool debug_mode; // for quickbook developers only
56 bool self_linked_headers;
57 std::vector<fs::path> include_path;
58 std::vector<std::string> preset_defines;
59 fs::path image_location;
60
set_macros(quickbook::state & state)61 static void set_macros(quickbook::state& state)
62 {
63 QUICKBOOK_FOR (quickbook::string_view val, preset_defines) {
64 parse_iterator first(val.begin());
65 parse_iterator last(val.end());
66
67 cl::parse_info<parse_iterator> info =
68 cl::parse(first, last, state.grammar().command_line_macro);
69
70 if (!info.full) {
71 detail::outerr() << "Error parsing command line definition: '"
72 << val << "'" << std::endl;
73 ++state.error_count;
74 }
75 }
76 }
77
78 ///////////////////////////////////////////////////////////////////////////
79 //
80 // Parse a file
81 //
82 ///////////////////////////////////////////////////////////////////////////
parse_file(quickbook::state & state,value include_doc_id,bool nested_file)83 void parse_file(
84 quickbook::state& state, value include_doc_id, bool nested_file)
85 {
86 parse_iterator first(state.current_file->source().begin());
87 parse_iterator last(state.current_file->source().end());
88
89 cl::parse_info<parse_iterator> info =
90 cl::parse(first, last, state.grammar().doc_info);
91 assert(info.hit);
92
93 if (!state.error_count) {
94 std::string doc_type =
95 pre(state, info.stop, include_doc_id, nested_file);
96
97 info = cl::parse(
98 info.hit ? info.stop : first, last,
99 state.grammar().block_start);
100
101 post(state, doc_type);
102
103 if (!info.full) {
104 file_position const& pos =
105 state.current_file->position_of(info.stop.base());
106 detail::outerr(state.current_file->path, pos.line)
107 << "Syntax Error near column " << pos.column << ".\n";
108 ++state.error_count;
109 }
110 }
111 }
112
113 struct parse_document_options
114 {
115 enum output_format
116 {
117 boostbook,
118 html
119 };
120 enum output_style
121 {
122 output_none = 0,
123 output_file,
124 output_chunked
125 };
126
parse_document_optionsquickbook::parse_document_options127 parse_document_options()
128 : format(boostbook)
129 , style(output_file)
130 , output_path()
131 , indent(-1)
132 , linewidth(-1)
133 , pretty_print(true)
134 , strict_mode(false)
135 , deps_out_flags(quickbook::dependency_tracker::default_)
136 {
137 }
138
139 output_format format;
140 output_style style;
141 fs::path output_path;
142 int indent;
143 int linewidth;
144 bool pretty_print;
145 bool strict_mode;
146 fs::path deps_out;
147 quickbook::dependency_tracker::flags deps_out_flags;
148 fs::path locations_out;
149 fs::path xinclude_base;
150 quickbook::detail::html_options html_ops;
151 };
152
parse_document(fs::path const & filein_,parse_document_options const & options_)153 static int parse_document(
154 fs::path const& filein_, parse_document_options const& options_)
155 {
156 string_stream buffer;
157 document_state output;
158
159 int result = 0;
160
161 try {
162 quickbook::state state(
163 filein_, options_.xinclude_base, buffer, output);
164 state.strict_mode = options_.strict_mode;
165 set_macros(state);
166
167 if (state.error_count == 0) {
168 state.dependencies.add_dependency(filein_);
169 state.current_file = load(filein_); // Throws load_error
170
171 parse_file(state);
172
173 if (state.error_count) {
174 detail::outerr()
175 << "Error count: " << state.error_count << ".\n";
176 }
177 }
178
179 result = state.error_count ? 1 : 0;
180
181 if (!options_.deps_out.empty()) {
182 state.dependencies.write_dependencies(
183 options_.deps_out, options_.deps_out_flags);
184 }
185
186 if (!options_.locations_out.empty()) {
187 fs::ofstream out(options_.locations_out);
188 state.dependencies.write_dependencies(
189 options_.locations_out, dependency_tracker::checked);
190 }
191 } catch (load_error& e) {
192 detail::outerr(filein_) << e.what() << std::endl;
193 result = 1;
194 } catch (std::runtime_error& e) {
195 detail::outerr() << e.what() << std::endl;
196 result = 1;
197 }
198
199 if (result) {
200 return result;
201 }
202
203 if (options_.style) {
204 std::string stage2 = output.replace_placeholders(buffer.str());
205
206 if (options_.pretty_print) {
207 try {
208 stage2 = post_process(
209 stage2, options_.indent, options_.linewidth);
210 } catch (quickbook::post_process_failure&) {
211 ::quickbook::detail::outerr()
212 << "Post Processing Failed." << std::endl;
213 if (options_.format == parse_document_options::boostbook) {
214 // Can still write out a boostbook file, but return an
215 // error code.
216 result = 1;
217 }
218 else {
219 return 1;
220 }
221 }
222 }
223
224 if (options_.format == parse_document_options::html) {
225 if (result) {
226 return result;
227 }
228 return quickbook::detail::boostbook_to_html(
229 stage2, options_.html_ops);
230 }
231 else {
232 fs::ofstream fileout(options_.output_path);
233
234 if (fileout.fail()) {
235 ::quickbook::detail::outerr()
236 << "Error opening output file " << options_.output_path
237 << std::endl;
238
239 return 1;
240 }
241
242 fileout << stage2;
243
244 if (fileout.fail()) {
245 ::quickbook::detail::outerr()
246 << "Error writing to output file "
247 << options_.output_path << std::endl;
248
249 return 1;
250 }
251 }
252 }
253
254 return result;
255 }
256 }
257
258 ///////////////////////////////////////////////////////////////////////////
259 //
260 // Main program
261 //
262 ///////////////////////////////////////////////////////////////////////////
main(int argc,char * argv[])263 int main(int argc, char* argv[])
264 {
265 try {
266 namespace fs = boost::filesystem;
267 namespace po = boost::program_options;
268
269 using boost::program_options::options_description;
270 using boost::program_options::variables_map;
271 using boost::program_options::store;
272 using boost::program_options::parse_command_line;
273 using boost::program_options::wcommand_line_parser;
274 using boost::program_options::command_line_parser;
275 using boost::program_options::notify;
276 using boost::program_options::positional_options_description;
277
278 using namespace quickbook;
279 using quickbook::detail::command_line_string;
280
281 // First thing, the filesystem should record the current working
282 // directory.
283 fs::initial_path<fs::path>();
284
285 // Various initialisation methods
286 quickbook::detail::initialise_output();
287 quickbook::detail::initialise_markups();
288
289 // Declare the program options
290
291 options_description desc("Allowed options");
292 options_description html_desc("HTML options");
293 options_description hidden("Hidden options");
294 options_description all("All options");
295
296 #if QUICKBOOK_WIDE_PATHS
297 #define PO_VALUE po::wvalue
298 #else
299 #define PO_VALUE po::value
300 #endif
301
302 // clang-format off
303
304 desc.add_options()
305 ("help", "produce help message")
306 ("version", "print version string")
307 ("no-pretty-print", "disable XML pretty printing")
308 ("strict", "strict mode")
309 ("no-self-linked-headers", "stop headers linking to themselves")
310 ("indent", PO_VALUE<int>(), "indent spaces")
311 ("linewidth", PO_VALUE<int>(), "line width")
312 ("input-file", PO_VALUE<command_line_string>(), "input file")
313 ("output-format", PO_VALUE<command_line_string>(), "boostbook, html, onehtml")
314 ("output-file", PO_VALUE<command_line_string>(), "output file (for boostbook or onehtml)")
315 ("output-dir", PO_VALUE<command_line_string>(), "output directory (for html)")
316 ("no-output", "don't write out the result")
317 ("output-deps", PO_VALUE<command_line_string>(), "output dependency file")
318 ("ms-errors", "use Microsoft Visual Studio style error & warn message format")
319 ("include-path,I", PO_VALUE< std::vector<command_line_string> >(), "include path")
320 ("define,D", PO_VALUE< std::vector<command_line_string> >(), "define macro")
321 ("image-location", PO_VALUE<command_line_string>(), "image location")
322 ;
323
324 html_desc.add_options()
325 ("boost-root-path", PO_VALUE<command_line_string>(), "boost root (file path or absolute URL)")
326 ("css-path", PO_VALUE<command_line_string>(), "css file (file path or absolute URL)")
327 ("graphics-path", PO_VALUE<command_line_string>(), "graphics directory (file path or absolute URL)");
328 desc.add(html_desc);
329
330 hidden.add_options()
331 ("debug", "debug mode")
332 ("expect-errors",
333 "Succeed if the input file contains a correctly handled "
334 "error, fail otherwise.")
335 ("xinclude-base", PO_VALUE<command_line_string>(),
336 "Generate xincludes as if generating for this target "
337 "directory.")
338 ("output-deps-format", PO_VALUE<command_line_string>(),
339 "Comma separated list of formatting options for output-deps, "
340 "options are: escaped, checked")
341 ("output-checked-locations", PO_VALUE<command_line_string>(),
342 "Writes a file listing all the file locations that were "
343 "checked, starting with '+' if they were found, or '-' "
344 "if they weren't.\n"
345 "This is deprecated, use 'output-deps-format=checked' to "
346 "write the deps file in this format.")
347 ;
348
349 // clang-format on
350
351 all.add(desc).add(hidden);
352
353 positional_options_description p;
354 p.add("input-file", -1);
355
356 // Read option from the command line
357
358 variables_map vm;
359
360 #if QUICKBOOK_WIDE_PATHS
361 quickbook::ignore_variable(&argc);
362 quickbook::ignore_variable(&argv);
363
364 int wide_argc;
365 LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc);
366 if (!wide_argv) {
367 quickbook::detail::outerr()
368 << "Error getting argument values." << std::endl;
369 return 1;
370 }
371
372 store(
373 wcommand_line_parser(wide_argc, wide_argv)
374 .options(all)
375 .positional(p)
376 .run(),
377 vm);
378
379 LocalFree(wide_argv);
380 #else
381 store(
382 command_line_parser(argc, argv).options(all).positional(p).run(),
383 vm);
384 #endif
385
386 notify(vm);
387
388 // Process the command line options
389
390 parse_document_options options;
391 bool expect_errors = vm.count("expect-errors");
392 int error_count = 0;
393 bool output_specified = false;
394 bool alt_output_specified = false;
395
396 if (vm.count("help")) {
397 std::ostringstream description_text;
398 description_text << desc;
399
400 quickbook::detail::out() << description_text.str() << "\n";
401
402 return 0;
403 }
404
405 if (vm.count("version")) {
406 std::string boost_version = BOOST_LIB_VERSION;
407 boost::replace(boost_version, '_', '.');
408
409 quickbook::detail::out() << QUICKBOOK_VERSION << " (Boost "
410 << boost_version << ")" << std::endl;
411 return 0;
412 }
413
414 quickbook::detail::set_ms_errors(vm.count("ms-errors"));
415
416 if (vm.count("no-pretty-print")) options.pretty_print = false;
417
418 options.strict_mode = !!vm.count("strict");
419
420 if (vm.count("indent")) options.indent = vm["indent"].as<int>();
421
422 if (vm.count("linewidth"))
423 options.linewidth = vm["linewidth"].as<int>();
424
425 if (vm.count("output-format")) {
426 output_specified = true;
427 std::string format = quickbook::detail::command_line_to_utf8(
428 vm["output-format"].as<command_line_string>());
429 if (format == "html") {
430 options.format = quickbook::parse_document_options::html;
431 options.style =
432 quickbook::parse_document_options::output_chunked;
433 }
434 else if (format == "onehtml") {
435 options.format = quickbook::parse_document_options::html;
436 options.style = quickbook::parse_document_options::output_file;
437 }
438 else if (format == "boostbook") {
439 options.format = quickbook::parse_document_options::boostbook;
440 options.style = quickbook::parse_document_options::output_file;
441 }
442 else {
443 quickbook::detail::outerr()
444 << "Unknown output format: " << format << std::endl;
445
446 ++error_count;
447 }
448 }
449
450 quickbook::self_linked_headers =
451 options.format != parse_document_options::html &&
452 !vm.count("no-self-linked-headers");
453
454 if (vm.count("debug")) {
455 static tm timeinfo;
456 timeinfo.tm_year = 2000 - 1900;
457 timeinfo.tm_mon = 12 - 1;
458 timeinfo.tm_mday = 20;
459 timeinfo.tm_hour = 12;
460 timeinfo.tm_min = 0;
461 timeinfo.tm_sec = 0;
462 timeinfo.tm_isdst = -1;
463 mktime(&timeinfo);
464 quickbook::current_time = &timeinfo;
465 quickbook::current_gm_time = &timeinfo;
466 quickbook::debug_mode = true;
467 }
468 else {
469 time_t t = std::time(0);
470 static tm lt = *localtime(&t);
471 static tm gmt = *gmtime(&t);
472 quickbook::current_time = <
473 quickbook::current_gm_time = &gmt;
474 quickbook::debug_mode = false;
475 }
476
477 quickbook::include_path.clear();
478 if (vm.count("include-path")) {
479 boost::transform(
480 vm["include-path"].as<std::vector<command_line_string> >(),
481 std::back_inserter(quickbook::include_path),
482 quickbook::detail::command_line_to_path);
483 }
484
485 quickbook::preset_defines.clear();
486 if (vm.count("define")) {
487 boost::transform(
488 vm["define"].as<std::vector<command_line_string> >(),
489 std::back_inserter(quickbook::preset_defines),
490 quickbook::detail::command_line_to_utf8);
491 }
492
493 if (vm.count("input-file")) {
494 fs::path filein = quickbook::detail::command_line_to_path(
495 vm["input-file"].as<command_line_string>());
496
497 if (!fs::exists(filein)) {
498 quickbook::detail::outerr()
499 << "file not found: " << filein << std::endl;
500 ++error_count;
501 }
502
503 if (vm.count("output-deps")) {
504 alt_output_specified = true;
505 options.deps_out = quickbook::detail::command_line_to_path(
506 vm["output-deps"].as<command_line_string>());
507 }
508
509 if (vm.count("output-deps-format")) {
510 std::string format_flags =
511 quickbook::detail::command_line_to_utf8(
512 vm["output-deps-format"].as<command_line_string>());
513
514 std::vector<std::string> flag_names;
515 boost::algorithm::split(
516 flag_names, format_flags, boost::algorithm::is_any_of(", "),
517 boost::algorithm::token_compress_on);
518
519 unsigned flags = 0;
520
521 QUICKBOOK_FOR (std::string const& flag, flag_names) {
522 if (flag == "checked") {
523 flags |= quickbook::dependency_tracker::checked;
524 }
525 else if (flag == "escaped") {
526 flags |= quickbook::dependency_tracker::escaped;
527 }
528 else if (!flag.empty()) {
529 quickbook::detail::outerr()
530 << "Unknown dependency format flag: " << flag
531 << std::endl;
532
533 ++error_count;
534 }
535 }
536
537 options.deps_out_flags =
538 quickbook::dependency_tracker::flags(flags);
539 }
540
541 if (vm.count("output-checked-locations")) {
542 alt_output_specified = true;
543 options.locations_out = quickbook::detail::command_line_to_path(
544 vm["output-checked-locations"].as<command_line_string>());
545 }
546
547 if (vm.count("boost-root-path")) {
548 // TODO: Check that it's a directory?
549 options.html_ops.boost_root_path =
550 vm["boost-root-path"].as<command_line_string>();
551 }
552 // Could possibly default it:
553 // 'boost:' links will use this anyway, but setting a default
554 // would also result in default css and graphics paths.
555 //
556 // else {
557 // options.html_ops.boost_root_path =
558 // quickbook::detail::path_or_url::url(
559 // "http://www.boost.org/doc/libs/release/");
560 //}
561
562 if (vm.count("css-path")) {
563 options.html_ops.css_path =
564 vm["css-path"].as<command_line_string>();
565 }
566 else if (options.html_ops.boost_root_path) {
567 options.html_ops.css_path =
568 options.html_ops.boost_root_path / "doc/src/boostbook.css";
569 }
570
571 if (vm.count("graphics-path")) {
572 options.html_ops.graphics_path =
573 vm["graphics-path"].as<command_line_string>();
574 }
575 else if (options.html_ops.boost_root_path) {
576 options.html_ops.graphics_path =
577 options.html_ops.boost_root_path / "doc/src/images";
578 }
579
580 if (vm.count("output-file")) {
581 output_specified = true;
582 switch (options.style) {
583 case quickbook::parse_document_options::output_file: {
584 options.output_path =
585 quickbook::detail::command_line_to_path(
586 vm["output-file"].as<command_line_string>());
587
588 fs::path parent = options.output_path.parent_path();
589 if (!parent.empty() && !fs::is_directory(parent)) {
590 quickbook::detail::outerr()
591 << "parent directory not found for output file"
592 << std::endl;
593 ++error_count;
594 }
595 break;
596 }
597 case quickbook::parse_document_options::output_chunked:
598 quickbook::detail::outerr()
599 << "output-file give for chunked output" << std::endl;
600 ++error_count;
601 break;
602 case quickbook::parse_document_options::output_none:
603 quickbook::detail::outerr()
604 << "output-file given for no output" << std::endl;
605 ++error_count;
606 break;
607 default:
608 assert(false);
609 }
610 }
611
612 if (vm.count("output-dir")) {
613 output_specified = true;
614 switch (options.style) {
615 case quickbook::parse_document_options::output_chunked: {
616 options.output_path =
617 quickbook::detail::command_line_to_path(
618 vm["output-dir"].as<command_line_string>());
619
620 if (!fs::is_directory(options.output_path.parent_path())) {
621 quickbook::detail::outerr()
622 << "parent directory not found for output directory"
623 << std::endl;
624 ++error_count;
625 }
626 }
627 case quickbook::parse_document_options::output_file:
628 quickbook::detail::outerr()
629 << "output-dir give for file output" << std::endl;
630 ++error_count;
631 break;
632 case quickbook::parse_document_options::output_none:
633 quickbook::detail::outerr()
634 << "output-dir given for no output" << std::endl;
635 ++error_count;
636 break;
637 default:
638 assert(false);
639 }
640 }
641
642 if (!vm.count("output-file") && !vm.count("output-dir")) {
643 if (!output_specified && alt_output_specified) {
644 options.style =
645 quickbook::parse_document_options::output_none;
646 }
647 else {
648 fs::path path = filein;
649 switch (options.style) {
650 case quickbook::parse_document_options::output_chunked:
651 path = path.parent_path() / "html";
652 options.style =
653 quickbook::parse_document_options::output_chunked;
654 options.output_path = path;
655 break;
656 case quickbook::parse_document_options::output_file:
657 switch (options.format) {
658 case quickbook::parse_document_options::html:
659 path.replace_extension(".html");
660 break;
661 case quickbook::parse_document_options::boostbook:
662 path.replace_extension(".xml");
663 break;
664 default:
665 assert(false);
666 path.replace_extension(".xml");
667 }
668 options.output_path = path;
669 break;
670 default:
671 assert(false);
672 options.style =
673 quickbook::parse_document_options::output_none;
674 }
675 }
676 }
677
678 if (vm.count("xinclude-base")) {
679 options.xinclude_base = quickbook::detail::command_line_to_path(
680 vm["xinclude-base"].as<command_line_string>());
681
682 // I'm not sure if this error check is necessary.
683 // There might be valid reasons to use a path that doesn't
684 // exist yet, or a path that just generates valid relative
685 // paths.
686 if (!fs::is_directory(options.xinclude_base)) {
687 quickbook::detail::outerr()
688 << "xinclude-base is not a directory" << std::endl;
689 ++error_count;
690 }
691 }
692 else {
693 options.xinclude_base =
694 options.style == parse_document_options::output_chunked
695 ? options.output_path
696 : options.output_path.parent_path();
697 if (options.xinclude_base.empty()) {
698 options.xinclude_base = ".";
699 }
700
701 // If output_path was implicitly created from filein, then it
702 // should be in filein's directory.
703 // If output_path was explicitly specified, then it's already
704 // been checked.
705 assert(error_count || fs::is_directory(options.xinclude_base));
706 }
707
708 if (vm.count("image-location")) {
709 quickbook::image_location =
710 quickbook::detail::command_line_to_path(
711 vm["image-location"].as<command_line_string>());
712 }
713 else {
714 quickbook::image_location = filein.parent_path() / "html";
715 }
716
717 // Set duplicated html_options.
718 // TODO: Clean this up?
719 if (options.style == parse_document_options::output_chunked) {
720 options.html_ops.home_path = options.output_path / "index.html";
721 options.html_ops.chunked_output = true;
722 }
723 else {
724 options.html_ops.home_path = options.output_path;
725 options.html_ops.chunked_output = false;
726 }
727 options.html_ops.pretty_print = options.pretty_print;
728
729 if (!error_count) {
730 switch (options.style) {
731 case parse_document_options::output_file:
732 quickbook::detail::out()
733 << "Generating output file: " << options.output_path
734 << std::endl;
735 break;
736 case parse_document_options::output_chunked:
737 quickbook::detail::out()
738 << "Generating output path: " << options.output_path
739 << std::endl;
740 break;
741 case parse_document_options::output_none:
742 break;
743 default:
744 assert(false);
745 }
746
747 error_count += quickbook::parse_document(filein, options);
748 }
749
750 if (expect_errors) {
751 if (!error_count)
752 quickbook::detail::outerr()
753 << "No errors detected for --expect-errors."
754 << std::endl;
755 return !error_count;
756 }
757 else {
758 return error_count;
759 }
760 }
761 else {
762 std::ostringstream description_text;
763 description_text << desc;
764
765 quickbook::detail::outerr() << "No filename given\n\n"
766 << description_text.str() << std::endl;
767 return 1;
768 }
769 }
770
771 catch (std::exception& e) {
772 quickbook::detail::outerr() << e.what() << "\n";
773 return 1;
774 }
775
776 catch (...) {
777 quickbook::detail::outerr() << "Exception of unknown type caught\n";
778 return 1;
779 }
780
781 return 0;
782 }
783