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 = &lt;
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