/*============================================================================ CMake - Cross Platform Makefile Generator Copyright 2000-2013 Kitware, Inc., Insight Software Consortium Distributed under the OSI-approved BSD License (the "License"); see accompanying file Copyright.txt for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more information. ============================================================================*/ #include "cmRST.h" #include "cmAlgorithms.h" #include "cmSystemTools.h" #include "cmVersion.h" #include #include //---------------------------------------------------------------------------- cmRST::cmRST(std::ostream& os, std::string const& docroot): OS(os), DocRoot(docroot), IncludeDepth(0), OutputLinePending(false), LastLineEndedInColonColon(false), Markup(MarkupNone), Directive(DirectiveNone), CMakeDirective("^.. (cmake:)?(" "command|variable" ")::[ \t]+([^ \t\n]+)$"), CMakeModuleDirective("^.. cmake-module::[ \t]+([^ \t\n]+)$"), ParsedLiteralDirective("^.. parsed-literal::[ \t]*(.*)$"), CodeBlockDirective("^.. code-block::[ \t]*(.*)$"), ReplaceDirective("^.. (\\|[^|]+\\|) replace::[ \t]*(.*)$"), IncludeDirective("^.. include::[ \t]+([^ \t\n]+)$"), TocTreeDirective("^.. toctree::[ \t]*(.*)$"), ProductionListDirective("^.. productionlist::[ \t]*(.*)$"), NoteDirective("^.. note::[ \t]*(.*)$"), ModuleRST("^#\\[(=*)\\[\\.rst:$"), CMakeRole("(:cmake)?:(" "command|generator|variable|module|policy|" "prop_cache|prop_dir|prop_gbl|prop_inst|prop_sf|" "prop_test|prop_tgt|" "manual" "):`(<*([^`<]|[^` \t]<)*)([ \t]+<[^`]*>)?`"), Substitution("(^|[^A-Za-z0-9_])" "((\\|[^| \t\r\n]([^|\r\n]*[^| \t\r\n])?\\|)(__|_|))" "([^A-Za-z0-9_]|$)"), TocTreeLink("^.*[ \t]+<([^>]+)>$") { this->Replace["|release|"] = cmVersion::GetCMakeVersion(); } //---------------------------------------------------------------------------- bool cmRST::ProcessFile(std::string const& fname, bool isModule) { cmsys::ifstream fin(fname.c_str()); if(fin) { this->DocDir = cmSystemTools::GetFilenamePath(fname); if(isModule) { this->ProcessModule(fin); } else { this->ProcessRST(fin); } this->OutputLinePending = true; return true; } return false; } //---------------------------------------------------------------------------- void cmRST::ProcessRST(std::istream& is) { std::string line; while(cmSystemTools::GetLineFromStream(is, line)) { this->ProcessLine(line); } this->Reset(); } //---------------------------------------------------------------------------- void cmRST::ProcessModule(std::istream& is) { std::string line; std::string rst; while(cmSystemTools::GetLineFromStream(is, line)) { if(!rst.empty() && rst != "#") { // Bracket mode: check for end bracket std::string::size_type pos = line.find(rst); if(pos == line.npos) { this->ProcessLine(line); } else { if(line[0] != '#') { this->ProcessLine(line.substr(0, pos)); } rst = ""; this->Reset(); this->OutputLinePending = true; } } else { // Line mode: check for .rst start (bracket or line) if(rst == "#") { if(line == "#") { this->ProcessLine(""); continue; } else if(line.substr(0, 2) == "# ") { this->ProcessLine(line.substr(2, line.npos)); continue; } else { rst = ""; this->Reset(); this->OutputLinePending = true; } } if(line == "#.rst:") { rst = "#"; } else if(this->ModuleRST.find(line)) { rst = "]" + this->ModuleRST.match(1) + "]"; } } } if(rst == "#") { this->Reset(); } } //---------------------------------------------------------------------------- void cmRST::Reset() { if(!this->MarkupLines.empty()) { this->UnindentLines(this->MarkupLines); } switch(this->Directive) { case DirectiveNone: break; case DirectiveParsedLiteral: this->ProcessDirectiveParsedLiteral(); break; case DirectiveLiteralBlock: this->ProcessDirectiveLiteralBlock(); break; case DirectiveCodeBlock: this->ProcessDirectiveCodeBlock(); break; case DirectiveReplace: this->ProcessDirectiveReplace(); break; case DirectiveTocTree: this->ProcessDirectiveTocTree(); break; } this->Markup = MarkupNone; this->Directive = DirectiveNone; this->MarkupLines.clear(); } //---------------------------------------------------------------------------- void cmRST::ProcessLine(std::string const& line) { bool lastLineEndedInColonColon = this->LastLineEndedInColonColon; this->LastLineEndedInColonColon = false; // A line starting in .. is an explicit markup start. if(line == ".." || (line.size() >= 3 && line[0] == '.' && line[1] == '.' && isspace(line[2]))) { this->Reset(); this->Markup = (line.find_first_not_of(" \t", 2) == line.npos ? MarkupEmpty : MarkupNormal); if(this->CMakeDirective.find(line)) { // Output cmake domain directives and their content normally. this->NormalLine(line); } else if(this->CMakeModuleDirective.find(line)) { // Process cmake-module directive: scan .cmake file comments. std::string file = this->CMakeModuleDirective.match(1); if(file.empty() || !this->ProcessInclude(file, IncludeModule)) { this->NormalLine(line); } } else if(this->ParsedLiteralDirective.find(line)) { // Record the literal lines to output after whole block. this->Directive = DirectiveParsedLiteral; this->MarkupLines.push_back(this->ParsedLiteralDirective.match(1)); } else if(this->CodeBlockDirective.find(line)) { // Record the literal lines to output after whole block. // Ignore the language spec and record the opening line as blank. this->Directive = DirectiveCodeBlock; this->MarkupLines.push_back(""); } else if(this->ReplaceDirective.find(line)) { // Record the replace directive content. this->Directive = DirectiveReplace; this->ReplaceName = this->ReplaceDirective.match(1); this->MarkupLines.push_back(this->ReplaceDirective.match(2)); } else if(this->IncludeDirective.find(line)) { // Process the include directive or output the directive and its // content normally if it fails. std::string file = this->IncludeDirective.match(1); if(file.empty() || !this->ProcessInclude(file, IncludeNormal)) { this->NormalLine(line); } } else if(this->TocTreeDirective.find(line)) { // Record the toctree entries to process after whole block. this->Directive = DirectiveTocTree; this->MarkupLines.push_back(this->TocTreeDirective.match(1)); } else if(this->ProductionListDirective.find(line)) { // Output productionlist directives and their content normally. this->NormalLine(line); } else if(this->NoteDirective.find(line)) { // Output note directives and their content normally. this->NormalLine(line); } } // An explicit markup start followed nothing but whitespace and a // blank line does not consume any indented text following. else if(this->Markup == MarkupEmpty && line.empty()) { this->NormalLine(line); } // Indented lines following an explicit markup start are explicit markup. else if(this->Markup && (line.empty() || isspace(line[0]))) { this->Markup = MarkupNormal; // Record markup lines if the start line was recorded. if(!this->MarkupLines.empty()) { this->MarkupLines.push_back(line); } } // A blank line following a paragraph ending in "::" starts a literal block. else if(lastLineEndedInColonColon && line.empty()) { // Record the literal lines to output after whole block. this->Markup = MarkupNormal; this->Directive = DirectiveLiteralBlock; this->MarkupLines.push_back(""); this->OutputLine("", false); } // Print non-markup lines. else { this->NormalLine(line); this->LastLineEndedInColonColon = (line.size() >= 2 && line[line.size()-2] == ':' && line[line.size()-1] == ':'); } } //---------------------------------------------------------------------------- void cmRST::NormalLine(std::string const& line) { this->Reset(); this->OutputLine(line, true); } //---------------------------------------------------------------------------- void cmRST::OutputLine(std::string const& line_in, bool inlineMarkup) { if(this->OutputLinePending) { this->OS << "\n"; this->OutputLinePending = false; } if(inlineMarkup) { std::string line = this->ReplaceSubstitutions(line_in); std::string::size_type pos = 0; while(this->CMakeRole.find(line.c_str()+pos)) { this->OS << line.substr(pos, this->CMakeRole.start()); std::string text = this->CMakeRole.match(3); // If a command reference has no explicit target and // no explicit "(...)" then add "()" to the text. if(this->CMakeRole.match(2) == "command" && this->CMakeRole.match(5).empty() && text.find_first_of("()") == text.npos) { text += "()"; } this->OS << "``" << text << "``"; pos += this->CMakeRole.end(); } this->OS << line.substr(pos) << "\n"; } else { this->OS << line_in << "\n"; } } //---------------------------------------------------------------------------- std::string cmRST::ReplaceSubstitutions(std::string const& line) { std::string out; std::string::size_type pos = 0; while(this->Substitution.find(line.c_str()+pos)) { std::string::size_type start = this->Substitution.start(2); std::string::size_type end = this->Substitution.end(2); std::string substitute = this->Substitution.match(3); std::map::iterator replace = this->Replace.find(substitute); if(replace != this->Replace.end()) { std::pair::iterator, bool> replaced = this->Replaced.insert(substitute); if(replaced.second) { substitute = this->ReplaceSubstitutions(replace->second); this->Replaced.erase(replaced.first); } } out += line.substr(pos, start); out += substitute; pos += end; } out += line.substr(pos); return out; } //---------------------------------------------------------------------------- void cmRST::OutputMarkupLines(bool inlineMarkup) { for(std::vector::iterator i = this->MarkupLines.begin(); i != this->MarkupLines.end(); ++i) { std::string line = *i; if(!line.empty()) { line = " " + line; } this->OutputLine(line, inlineMarkup); } this->OutputLinePending = true; } //---------------------------------------------------------------------------- bool cmRST::ProcessInclude(std::string file, IncludeType type) { bool found = false; if(this->IncludeDepth < 10) { cmRST r(this->OS, this->DocRoot); r.IncludeDepth = this->IncludeDepth + 1; r.OutputLinePending = this->OutputLinePending; if(type != IncludeTocTree) { r.Replace = this->Replace; } if(file[0] == '/') { file = this->DocRoot + file; } else { file = this->DocDir + "/" + file; } found = r.ProcessFile(file, type == IncludeModule); if(type != IncludeTocTree) { this->Replace = r.Replace; } this->OutputLinePending = r.OutputLinePending; } return found; } //---------------------------------------------------------------------------- void cmRST::ProcessDirectiveParsedLiteral() { this->OutputMarkupLines(true); } //---------------------------------------------------------------------------- void cmRST::ProcessDirectiveLiteralBlock() { this->OutputMarkupLines(false); } //---------------------------------------------------------------------------- void cmRST::ProcessDirectiveCodeBlock() { this->OutputMarkupLines(false); } //---------------------------------------------------------------------------- void cmRST::ProcessDirectiveReplace() { // Record markup lines as replacement text. std::string& replacement = this->Replace[this->ReplaceName]; replacement += cmJoin(this->MarkupLines, " "); this->ReplaceName = ""; } //---------------------------------------------------------------------------- void cmRST::ProcessDirectiveTocTree() { // Process documents referenced by toctree directive. for(std::vector::iterator i = this->MarkupLines.begin(); i != this->MarkupLines.end(); ++i) { std::string const& line = *i; if (!line.empty() && line[0] != ':') { if (this->TocTreeLink.find(line)) { std::string const& link = this->TocTreeLink.match(1); this->ProcessInclude(link + ".rst", IncludeTocTree); } else { this->ProcessInclude(line + ".rst", IncludeTocTree); } } } } //---------------------------------------------------------------------------- void cmRST::UnindentLines(std::vector& lines) { // Remove the common indentation from the second and later lines. std::string indentText; std::string::size_type indentEnd = 0; bool first = true; for(size_t i = 1; i < lines.size(); ++i) { std::string const& line = lines[i]; // Do not consider empty lines. if(line.empty()) { continue; } // Record indentation on first non-empty line. if(first) { first = false; indentEnd = line.find_first_not_of(" \t"); indentText = line.substr(0, indentEnd); continue; } // Truncate indentation to match that on this line. indentEnd = std::min(indentEnd, line.size()); for(std::string::size_type j = 0; j != indentEnd; ++j) { if(line[j] != indentText[j]) { indentEnd = j; break; } } } // Update second and later lines. for(size_t i = 1; i < lines.size(); ++i) { std::string& line = lines[i]; if(!line.empty()) { line = line.substr(indentEnd); } } std::vector::const_iterator it = lines.begin(); size_t leadingEmpty = std::distance(it, cmFindNot(lines, std::string())); std::vector::const_reverse_iterator rit = lines.rbegin(); size_t trailingEmpty = std::distance(rit, cmFindNot(cmReverseRange(lines), std::string())); std::vector::iterator contentEnd = cmRotate(lines.begin(), lines.begin() + leadingEmpty, lines.end() - trailingEmpty); lines.erase(contentEnd, lines.end()); }