/*============================================================================= Copyright (c) 2006 Joel de Guzman http://spirit.sourceforge.net/ Use, modification and distribution is subject to the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) =============================================================================*/ #include #include #include #include #include #include #include "template_stack.hpp" #include "actions.hpp" #include "values.hpp" namespace quickbook { namespace cl = boost::spirit::classic; struct code_snippet_actions { code_snippet_actions(std::vector& storage, std::string const& filename, char const* source_type) : callout_id(0) , storage(storage) , filename(filename) , source_type(source_type) {} void pass_thru_char(char); void pass_thru(iterator first, iterator last); void escaped_comment(iterator first, iterator last); void start_snippet(iterator first, iterator last); void end_snippet(iterator first, iterator last); void callout(iterator first, iterator last); void append_code(); void close_code(); struct snippet_data { snippet_data(std::string const& id, int callout_base_id) : id(id) , callout_base_id(callout_base_id) , content() , start_code(false) , end_code(false) {} std::string id; int callout_base_id; std::string content; bool start_code; bool end_code; value_builder callouts; boost::shared_ptr next; }; void push_snippet_data(std::string const& id, int callout_base_id) { boost::shared_ptr new_snippet( new snippet_data(id, callout_base_id)); new_snippet->next = snippet_stack; snippet_stack = new_snippet; } boost::shared_ptr pop_snippet_data() { boost::shared_ptr snippet(snippet_stack); snippet_stack = snippet->next; snippet->next.reset(); return snippet; } int callout_id; boost::shared_ptr snippet_stack; std::string code; std::string id; std::vector& storage; fs::path filename; char const* const source_type; }; struct python_code_snippet_grammar : cl::grammar { typedef code_snippet_actions actions_type; python_code_snippet_grammar(actions_type & actions) : actions(actions) {} template struct definition { typedef code_snippet_actions actions_type; definition(python_code_snippet_grammar const& self) { actions_type& actions = self.actions; start_ = *code_elements; identifier = (cl::alpha_p | '_') >> *(cl::alnum_p | '_') ; code_elements = start_snippet [boost::bind(&actions_type::start_snippet, &actions, _1, _2)] | end_snippet [boost::bind(&actions_type::end_snippet, &actions, _1, _2)] | escaped_comment | pass_thru_comment | ignore | cl::anychar_p [boost::bind(&actions_type::pass_thru_char, &actions, _1)] ; start_snippet = "#[" >> *cl::space_p >> identifier [cl::assign_a(actions.id)] ; end_snippet = cl::str_p("#]") ; ignore = cl::confix_p( *cl::blank_p >> "#<-", *cl::anychar_p, "#->" >> *cl::blank_p >> (cl::eol_p | cl::end_p) ) | cl::confix_p( "\"\"\"<-\"\"\"", *cl::anychar_p, "\"\"\"->\"\"\"" ) | cl::confix_p( "\"\"\"<-", *cl::anychar_p, "->\"\"\"" ) ; escaped_comment = cl::confix_p( *cl::space_p >> "#`", (*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)], (cl::eol_p | cl::end_p) ) | cl::confix_p( *cl::space_p >> "\"\"\"`", (*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)], "\"\"\"" ) ; // Note: Unlike escaped_comment and ignore, this doesn't // swallow preceeding whitespace. pass_thru_comment = "#=" >> ( *(cl::anychar_p - cl::eol_p) >> (cl::eol_p | cl::end_p) ) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)] | cl::confix_p( "\"\"\"=", (*cl::anychar_p) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)], "\"\"\"" ) ; } cl::rule start_, identifier, code_elements, start_snippet, end_snippet, escaped_comment, pass_thru_comment, ignore; cl::rule const& start() const { return start_; } }; actions_type& actions; }; struct cpp_code_snippet_grammar : cl::grammar { typedef code_snippet_actions actions_type; cpp_code_snippet_grammar(actions_type & actions) : actions(actions) {} template struct definition { definition(cpp_code_snippet_grammar const& self) { actions_type& actions = self.actions; start_ = *code_elements; identifier = (cl::alpha_p | '_') >> *(cl::alnum_p | '_') ; code_elements = start_snippet [boost::bind(&actions_type::start_snippet, &actions, _1, _2)] | end_snippet [boost::bind(&actions_type::end_snippet, &actions, _1, _2)] | escaped_comment | ignore | pass_thru_comment | line_callout | inline_callout | cl::anychar_p [boost::bind(&actions_type::pass_thru_char, &actions, _1)] ; start_snippet = "//[" >> *cl::space_p >> identifier [cl::assign_a(actions.id)] | "/*[" >> *cl::space_p >> identifier [cl::assign_a(actions.id)] >> *cl::space_p >> "*/" ; end_snippet = cl::str_p("//]") | "/*]*/" ; inline_callout = cl::confix_p( "/*<" >> *cl::space_p, (*cl::anychar_p) [boost::bind(&actions_type::callout, &actions, _1, _2)], ">*/" ) ; line_callout = cl::confix_p( "/*<<" >> *cl::space_p, (*cl::anychar_p) [boost::bind(&actions_type::callout, &actions, _1, _2)], ">>*/" ) >> *cl::space_p ; ignore = cl::confix_p( *cl::blank_p >> "//<-", *cl::anychar_p, "//->" ) >> *cl::blank_p >> cl::eol_p | cl::confix_p( "/*<-*/", *cl::anychar_p, "/*->*/" ) | cl::confix_p( "/*<-", *cl::anychar_p, "->*/" ) ; escaped_comment = cl::confix_p( *cl::space_p >> "//`", (*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)], (cl::eol_p | cl::end_p) ) | cl::confix_p( *cl::space_p >> "/*`", (*cl::anychar_p) [boost::bind(&actions_type::escaped_comment, &actions, _1, _2)], "*/" ) ; // Note: Unlike escaped_comment and ignore, this doesn't // swallow preceeding whitespace. pass_thru_comment = "//=" >> ( *(cl::anychar_p - cl::eol_p) >> (cl::eol_p | cl::end_p) ) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)] | cl::confix_p( "/*`", (*cl::anychar_p) [boost::bind(&actions_type::pass_thru, &actions, _1, _2)], "*/" ) ; } cl::rule start_, identifier, code_elements, start_snippet, end_snippet, escaped_comment, pass_thru_comment, inline_callout, line_callout, ignore; cl::rule const& start() const { return start_; } }; actions_type& actions; }; int load_snippets( std::string const& file , std::vector& storage // snippets are stored in a // vector of template_symbols , std::string const& extension) { std::string code; int err = detail::load(file, code); if (err != 0) return err; // return early on error iterator first(code.begin()); iterator last(code.end()); bool is_python = extension == ".py"; code_snippet_actions a(storage, file, is_python ? "[python]" : "[c++]"); // TODO: Should I check that parse succeeded? if(is_python) { boost::spirit::classic::parse(first, last, python_code_snippet_grammar(a)); } else { boost::spirit::classic::parse(first, last, cpp_code_snippet_grammar(a)); } return 0; } void code_snippet_actions::append_code() { if(!snippet_stack) return; snippet_data& snippet = *snippet_stack; if (!code.empty()) { detail::unindent(code); // remove all indents if(snippet.content.empty()) { snippet.start_code = true; } else if(!snippet.end_code) { snippet.content += "\n\n"; snippet.content += source_type; snippet.content += "```\n"; } snippet.content += code; snippet.end_code = true; code.clear(); } } void code_snippet_actions::close_code() { if(!snippet_stack) return; snippet_data& snippet = *snippet_stack; if(snippet.end_code) { snippet.content += "```\n\n"; snippet.end_code = false; } } void code_snippet_actions::pass_thru(iterator first, iterator last) { if(!snippet_stack) return; code.append(first, last); } void code_snippet_actions::pass_thru_char(char c) { if(!snippet_stack) return; code += c; } void code_snippet_actions::callout(iterator first, iterator last) { if(!snippet_stack) return; code += "``[[callout" + boost::lexical_cast(callout_id) + "]]``"; snippet_stack->callouts.insert(qbk_value(first, last, template_tags::block)); ++callout_id; } void code_snippet_actions::escaped_comment(iterator first, iterator last) { if(!snippet_stack) return; snippet_data& snippet = *snippet_stack; append_code(); close_code(); std::string temp(first, last); detail::unindent(temp); // remove all indents if (temp.size() != 0) { snippet.content += "\n" + temp; // add a linebreak to allow block markups } } void code_snippet_actions::start_snippet(iterator, iterator) { append_code(); push_snippet_data(id, callout_id); id.clear(); } void code_snippet_actions::end_snippet(iterator first, iterator) { // TODO: Error? if(!snippet_stack) return; append_code(); boost::shared_ptr snippet = pop_snippet_data(); value callouts = snippet->callouts.release(); std::string body; if(snippet->start_code) { body += "\n\n"; body += source_type; body += "```\n"; } body += snippet->content; if(snippet->end_code) { body += "```\n\n"; } std::vector params; int i = 0; for(value::iterator it = callouts.begin(); it != callouts.end(); ++it) { params.push_back("[callout" + boost::lexical_cast(snippet->callout_base_id + i) + "]"); ++i; } // TODO: Save position in start_snippet template_symbol symbol(snippet->id, params, qbk_value(body, first.get_position(), template_tags::block), filename); symbol.callouts = callouts; storage.push_back(symbol); // Merge the snippet into its parent if(snippet_stack) { snippet_data& next = *snippet_stack; if(!snippet->content.empty()) { if(!snippet->start_code) { close_code(); } else if(!next.end_code) { next.content += "\n\n"; next.content += source_type; next.content += "```\n"; } next.content += snippet->content; next.end_code = snippet->end_code; } next.callouts.extend(callouts); } } }