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 "grammar.hpp"
11 #include "quickbook.hpp"
12 #include "state.hpp"
13 #include "actions.hpp"
14 #include "post_process.hpp"
15 #include "utils.hpp"
16 #include "files.hpp"
17 #include "native_text.hpp"
18 #include "document_state.hpp"
19 #include <boost/program_options.hpp>
20 #include <boost/filesystem/path.hpp>
21 #include <boost/filesystem/operations.hpp>
22 #include <boost/filesystem/fstream.hpp>
23 #include <boost/range/algorithm.hpp>
24 #include <boost/ref.hpp>
25 #include <boost/version.hpp>
26 #include <boost/foreach.hpp>
27 #include <boost/algorithm/string/split.hpp>
28 #include <boost/algorithm/string/classification.hpp>
29
30 #include <stdexcept>
31 #include <vector>
32 #include <iterator>
33
34 #if defined(_WIN32)
35 #include <windows.h>
36 #include <shellapi.h>
37 #endif
38
39 #if (defined(BOOST_MSVC) && (BOOST_MSVC <= 1310))
40 #pragma warning(disable:4355)
41 #endif
42
43 #define QUICKBOOK_VERSION "Quickbook Version 1.6.1"
44
45 namespace quickbook
46 {
47 namespace cl = boost::spirit::classic;
48 namespace fs = boost::filesystem;
49
50 tm* current_time; // the current time
51 tm* current_gm_time; // the current UTC time
52 bool debug_mode; // for quickbook developers only
53 bool self_linked_headers;
54 bool ms_errors = false; // output errors/warnings as if for VS
55 std::vector<fs::path> include_path;
56 std::vector<std::string> preset_defines;
57 fs::path image_location;
58
set_macros(quickbook::state & state)59 static void set_macros(quickbook::state& state)
60 {
61 for(std::vector<std::string>::const_iterator
62 it = preset_defines.begin(),
63 end = preset_defines.end();
64 it != end; ++it)
65 {
66 boost::string_ref val(*it);
67 parse_iterator first(val.begin());
68 parse_iterator last(val.end());
69
70 cl::parse_info<parse_iterator> info =
71 cl::parse(first, last, state.grammar().command_line_macro);
72
73 if (!info.full) {
74 detail::outerr()
75 << "Error parsing command line definition: '"
76 << *it
77 << "'"
78 << std::endl;
79 ++state.error_count;
80 }
81 }
82 }
83
84 ///////////////////////////////////////////////////////////////////////////
85 //
86 // Parse a file
87 //
88 ///////////////////////////////////////////////////////////////////////////
parse_file(quickbook::state & state,value include_doc_id,bool nested_file)89 void parse_file(quickbook::state& state, value include_doc_id, bool nested_file)
90 {
91 parse_iterator first(state.current_file->source().begin());
92 parse_iterator last(state.current_file->source().end());
93
94 cl::parse_info<parse_iterator> info = cl::parse(first, last, state.grammar().doc_info);
95 assert(info.hit);
96
97 if (!state.error_count)
98 {
99 parse_iterator pos = info.stop;
100 std::string doc_type = pre(state, pos, include_doc_id, nested_file);
101
102 info = cl::parse(info.hit ? info.stop : first, last, state.grammar().block_start);
103
104 post(state, doc_type);
105
106 if (!info.full)
107 {
108 file_position const& pos = state.current_file->position_of(info.stop.base());
109 detail::outerr(state.current_file->path, pos.line)
110 << "Syntax Error near column " << pos.column << ".\n";
111 ++state.error_count;
112 }
113 }
114 }
115
116 struct parse_document_options
117 {
parse_document_optionsquickbook::parse_document_options118 parse_document_options() :
119 indent(-1),
120 linewidth(-1),
121 pretty_print(true),
122 deps_out_flags(quickbook::dependency_tracker::default_)
123 {}
124
125 int indent;
126 int linewidth;
127 bool pretty_print;
128 fs::path deps_out;
129 quickbook::dependency_tracker::flags deps_out_flags;
130 fs::path locations_out;
131 fs::path xinclude_base;
132 };
133
134 static int
parse_document(fs::path const & filein_,fs::path const & fileout_,parse_document_options const & options_)135 parse_document(
136 fs::path const& filein_
137 , fs::path const& fileout_
138 , parse_document_options const& options_)
139 {
140 string_stream buffer;
141 document_state output;
142
143 int result = 0;
144
145 try {
146 quickbook::state state(filein_, options_.xinclude_base, buffer, output);
147 set_macros(state);
148
149 if (state.error_count == 0) {
150 state.dependencies.add_dependency(filein_);
151 state.current_file = load(filein_); // Throws load_error
152
153 parse_file(state);
154
155 if(state.error_count) {
156 detail::outerr()
157 << "Error count: " << state.error_count << ".\n";
158 }
159 }
160
161 result = state.error_count ? 1 : 0;
162
163 if (!options_.deps_out.empty())
164 {
165 state.dependencies.write_dependencies(options_.deps_out,
166 options_.deps_out_flags);
167 }
168
169 if (!options_.locations_out.empty())
170 {
171 fs::ofstream out(options_.locations_out);
172 state.dependencies.write_dependencies(options_.locations_out,
173 dependency_tracker::checked);
174 }
175 }
176 catch (load_error& e) {
177 detail::outerr(filein_) << e.what() << std::endl;
178 result = 1;
179 }
180 catch (std::runtime_error& e) {
181 detail::outerr() << e.what() << std::endl;
182 result = 1;
183 }
184
185 if (!fileout_.empty() && result == 0)
186 {
187 std::string stage2 = output.replace_placeholders(buffer.str());
188
189 fs::ofstream fileout(fileout_);
190
191 if (fileout.fail()) {
192 ::quickbook::detail::outerr()
193 << "Error opening output file "
194 << fileout_
195 << std::endl;
196
197 return 1;
198 }
199
200 if (options_.pretty_print)
201 {
202 try
203 {
204 fileout << post_process(stage2, options_.indent,
205 options_.linewidth);
206 }
207 catch (quickbook::post_process_failure&)
208 {
209 // fallback!
210 ::quickbook::detail::outerr()
211 << "Post Processing Failed."
212 << std::endl;
213 fileout << stage2;
214 return 1;
215 }
216 }
217 else
218 {
219 fileout << stage2;
220 }
221
222 if (fileout.fail()) {
223 ::quickbook::detail::outerr()
224 << "Error writing to output file "
225 << fileout_
226 << std::endl;
227
228 return 1;
229 }
230 }
231
232 return result;
233 }
234 }
235
236 ///////////////////////////////////////////////////////////////////////////
237 //
238 // Main program
239 //
240 ///////////////////////////////////////////////////////////////////////////
241 int
main(int argc,char * argv[])242 main(int argc, char* argv[])
243 {
244 try
245 {
246 namespace fs = boost::filesystem;
247 namespace po = boost::program_options;
248
249 using boost::program_options::options_description;
250 using boost::program_options::variables_map;
251 using boost::program_options::store;
252 using boost::program_options::parse_command_line;
253 using boost::program_options::wcommand_line_parser;
254 using boost::program_options::command_line_parser;
255 using boost::program_options::notify;
256 using boost::program_options::positional_options_description;
257
258 using quickbook::detail::command_line_string;
259
260 // First thing, the filesystem should record the current working directory.
261 fs::initial_path<fs::path>();
262
263 // Various initialisation methods
264 quickbook::detail::initialise_output();
265 quickbook::detail::initialise_markups();
266
267 // Declare the program options
268
269 options_description desc("Allowed options");
270 options_description hidden("Hidden options");
271 options_description all("All options");
272
273 #if QUICKBOOK_WIDE_PATHS
274 #define PO_VALUE po::wvalue
275 #else
276 #define PO_VALUE po::value
277 #endif
278
279 desc.add_options()
280 ("help", "produce help message")
281 ("version", "print version string")
282 ("no-pretty-print", "disable XML pretty printing")
283 ("no-self-linked-headers", "stop headers linking to themselves")
284 ("indent", PO_VALUE<int>(), "indent spaces")
285 ("linewidth", PO_VALUE<int>(), "line width")
286 ("input-file", PO_VALUE<command_line_string>(), "input file")
287 ("output-file", PO_VALUE<command_line_string>(), "output file")
288 ("output-deps", PO_VALUE<command_line_string>(), "output dependency file")
289 ("debug", "debug mode (for developers)")
290 ("ms-errors", "use Microsoft Visual Studio style error & warn message format")
291 ("include-path,I", PO_VALUE< std::vector<command_line_string> >(), "include path")
292 ("define,D", PO_VALUE< std::vector<command_line_string> >(), "define macro")
293 ("image-location", PO_VALUE<command_line_string>(), "image location")
294 ;
295
296 hidden.add_options()
297 ("expect-errors",
298 "Succeed if the input file contains a correctly handled "
299 "error, fail otherwise.")
300 ("xinclude-base", PO_VALUE<command_line_string>(),
301 "Generate xincludes as if generating for this target "
302 "directory.")
303 ("output-deps-format", PO_VALUE<command_line_string>(),
304 "Comma separated list of formatting options for output-deps, "
305 "options are: escaped, checked")
306 ("output-checked-locations", PO_VALUE<command_line_string>(),
307 "Writes a file listing all the file locations that were "
308 "checked, starting with '+' if they were found, or '-' "
309 "if they weren't.\n"
310 "This is deprecated, use 'output-deps-format=checked' to "
311 "write the deps file in this format.")
312 ;
313
314 all.add(desc).add(hidden);
315
316 positional_options_description p;
317 p.add("input-file", -1);
318
319 // Read option from the command line
320
321 variables_map vm;
322
323 #if QUICKBOOK_WIDE_PATHS
324 quickbook::ignore_variable(&argc);
325 quickbook::ignore_variable(&argv);
326
327 int wide_argc;
328 LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &wide_argc);
329 if (!wide_argv)
330 {
331 quickbook::detail::outerr() << "Error getting argument values." << std::endl;
332 return 1;
333 }
334
335 store(
336 wcommand_line_parser(wide_argc, wide_argv)
337 .options(all)
338 .positional(p)
339 .run(), vm);
340
341 LocalFree(wide_argv);
342 #else
343 store(command_line_parser(argc, argv)
344 .options(all)
345 .positional(p)
346 .run(), vm);
347 #endif
348
349 notify(vm);
350
351 // Process the command line options
352
353 quickbook::parse_document_options parse_document_options;
354 bool expect_errors = vm.count("expect-errors");
355 int error_count = 0;
356
357 if (vm.count("help"))
358 {
359 std::ostringstream description_text;
360 description_text << desc;
361
362 quickbook::detail::out() << description_text.str() << "\n";
363
364 return 0;
365 }
366
367 if (vm.count("version"))
368 {
369 std::string boost_version = BOOST_LIB_VERSION;
370 boost::replace(boost_version, '_', '.');
371
372 quickbook::detail::out()
373 << QUICKBOOK_VERSION
374 << " (Boost "
375 << boost_version
376 << ")"
377 << std::endl;
378 return 0;
379 }
380
381 if (vm.count("ms-errors"))
382 quickbook::ms_errors = true;
383
384 if (vm.count("no-pretty-print"))
385 parse_document_options.pretty_print = false;
386
387 quickbook::self_linked_headers = !vm.count("no-self-link-headers");
388
389 if (vm.count("indent"))
390 parse_document_options.indent = vm["indent"].as<int>();
391
392 if (vm.count("linewidth"))
393 parse_document_options.linewidth = vm["linewidth"].as<int>();
394
395 if (vm.count("debug"))
396 {
397 static tm timeinfo;
398 timeinfo.tm_year = 2000 - 1900;
399 timeinfo.tm_mon = 12 - 1;
400 timeinfo.tm_mday = 20;
401 timeinfo.tm_hour = 12;
402 timeinfo.tm_min = 0;
403 timeinfo.tm_sec = 0;
404 timeinfo.tm_isdst = -1;
405 mktime(&timeinfo);
406 quickbook::current_time = &timeinfo;
407 quickbook::current_gm_time = &timeinfo;
408 quickbook::debug_mode = true;
409 }
410 else
411 {
412 time_t t = std::time(0);
413 static tm lt = *localtime(&t);
414 static tm gmt = *gmtime(&t);
415 quickbook::current_time = <
416 quickbook::current_gm_time = &gmt;
417 quickbook::debug_mode = false;
418 }
419
420 quickbook::include_path.clear();
421 if (vm.count("include-path"))
422 {
423 boost::transform(
424 vm["include-path"].as<std::vector<command_line_string> >(),
425 std::back_inserter(quickbook::include_path),
426 quickbook::detail::command_line_to_path);
427 }
428
429 quickbook::preset_defines.clear();
430 if (vm.count("define"))
431 {
432 boost::transform(
433 vm["define"].as<std::vector<command_line_string> >(),
434 std::back_inserter(quickbook::preset_defines),
435 quickbook::detail::command_line_to_utf8);
436 }
437
438 if (vm.count("input-file"))
439 {
440 fs::path filein = quickbook::detail::command_line_to_path(
441 vm["input-file"].as<command_line_string>());
442 fs::path fileout;
443
444 bool default_output = true;
445
446 if (vm.count("output-deps"))
447 {
448 parse_document_options.deps_out =
449 quickbook::detail::command_line_to_path(
450 vm["output-deps"].as<command_line_string>());
451 default_output = false;
452 }
453
454 if (vm.count("output-deps-format"))
455 {
456 std::string format_flags =
457 quickbook::detail::command_line_to_utf8(
458 vm["output-deps-format"].as<command_line_string>());
459
460 std::vector<std::string> flag_names;
461 boost::algorithm::split(flag_names, format_flags,
462 boost::algorithm::is_any_of(", "),
463 boost::algorithm::token_compress_on);
464
465 unsigned flags = 0;
466
467 BOOST_FOREACH(std::string const& flag, flag_names) {
468 if (flag == "checked") {
469 flags |= quickbook::dependency_tracker::checked;
470 }
471 else if (flag == "escaped") {
472 flags |= quickbook::dependency_tracker::escaped;
473 }
474 else if (!flag.empty()) {
475 quickbook::detail::outerr()
476 << "Unknown dependency format flag: "
477 << flag
478 <<std::endl;
479
480 ++error_count;
481 }
482 }
483
484 parse_document_options.deps_out_flags =
485 quickbook::dependency_tracker::flags(flags);
486 }
487
488 if (vm.count("output-checked-locations"))
489 {
490 parse_document_options.locations_out =
491 quickbook::detail::command_line_to_path(
492 vm["output-checked-locations"].as<command_line_string>());
493 default_output = false;
494 }
495
496 if (vm.count("output-file"))
497 {
498 fileout = quickbook::detail::command_line_to_path(
499 vm["output-file"].as<command_line_string>());
500 }
501 else if (default_output)
502 {
503 fileout = filein;
504 fileout.replace_extension(".xml");
505 }
506
507 if (vm.count("xinclude-base"))
508 {
509 parse_document_options.xinclude_base =
510 quickbook::detail::command_line_to_path(
511 vm["xinclude-base"].as<command_line_string>());
512 }
513 else
514 {
515 parse_document_options.xinclude_base = fileout.parent_path();
516 if (parse_document_options.xinclude_base.empty())
517 parse_document_options.xinclude_base = ".";
518 }
519
520 if (!fs::is_directory(parse_document_options.xinclude_base))
521 {
522 quickbook::detail::outerr()
523 << (vm.count("xinclude-base") ?
524 "xinclude-base is not a directory" :
525 "parent directory not found for output file");
526 ++error_count;
527 }
528
529 if (vm.count("image-location"))
530 {
531 quickbook::image_location = quickbook::detail::command_line_to_path(
532 vm["image-location"].as<command_line_string>());
533 }
534 else
535 {
536 quickbook::image_location = filein.parent_path() / "html";
537 }
538
539 if (!fileout.empty()) {
540 quickbook::detail::out() << "Generating Output File: "
541 << fileout
542 << std::endl;
543 }
544
545 if (!error_count)
546 error_count += quickbook::parse_document(
547 filein, fileout, parse_document_options);
548
549 if (expect_errors)
550 {
551 if (!error_count) quickbook::detail::outerr() << "No errors detected for --expect-errors." << std::endl;
552 return !error_count;
553 }
554 else
555 {
556 return error_count;
557 }
558 }
559 else
560 {
561 std::ostringstream description_text;
562 description_text << desc;
563
564 quickbook::detail::outerr() << "No filename given\n\n"
565 << description_text.str() << std::endl;
566 return 1;
567 }
568 }
569
570 catch(std::exception& e)
571 {
572 quickbook::detail::outerr() << e.what() << "\n";
573 return 1;
574 }
575
576 catch(...)
577 {
578 quickbook::detail::outerr() << "Exception of unknown type caught\n";
579 return 1;
580 }
581
582 return 0;
583 }
584