mirror of
https://github.com/Kitware/CMake.git
synced 2025-06-23 04:44:05 +08:00

The documentation for CPack generators previously lived in their respective internal CMake modules. This setup was misleading, because it implied that you should include the modules in your own code, which is not the case. Moving the documentation into a separate section does a better job of hiding the internal modules, which are just an implementation detail. The generator documentation has also been modified to remove any references to the module name. The CPackIFW module is a special exception: since it has user-facing macros, the documentation for these macros has been kept in the module page, while all other documentation related to the IFW generator has been moved into the new section. To make it easier to find the new documentation, the old help pages for the CPack*.cmake modules have not been deleted, but have been replaced with a link to their respective help page in the new documentation section.
464 lines
14 KiB
C++
464 lines
14 KiB
C++
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
|
file Copyright.txt or https://cmake.org/licensing for details. */
|
|
#include "cmRST.h"
|
|
|
|
#include "cmAlgorithms.h"
|
|
#include "cmSystemTools.h"
|
|
#include "cmVersion.h"
|
|
|
|
#include "cmsys/FStream.hxx"
|
|
#include <algorithm>
|
|
#include <ctype.h>
|
|
#include <iterator>
|
|
#include <stddef.h>
|
|
#include <utility>
|
|
|
|
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|cpack_gen|generator|variable|envvar|module|policy|"
|
|
"prop_cache|prop_dir|prop_gbl|prop_inst|prop_sf|"
|
|
"prop_test|prop_tgt|"
|
|
"manual"
|
|
"):`(<*([^`<]|[^` \t]<)*)([ \t]+<[^`]*>)?`")
|
|
, InlineLink("`(<*([^`<]|[^` \t]<)*)([ \t]+<[^`]*>)?`_")
|
|
, InlineLiteral("``([^`]*)``")
|
|
, 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 == std::string::npos) {
|
|
this->ProcessLine(line);
|
|
} else {
|
|
if (line[0] != '#') {
|
|
this->ProcessLine(line.substr(0, pos));
|
|
}
|
|
rst.clear();
|
|
this->Reset();
|
|
this->OutputLinePending = true;
|
|
}
|
|
} else {
|
|
// Line mode: check for .rst start (bracket or line)
|
|
if (rst == "#") {
|
|
if (line == "#") {
|
|
this->ProcessLine("");
|
|
continue;
|
|
}
|
|
if (line.substr(0, 2) == "# ") {
|
|
this->ProcessLine(line.substr(2));
|
|
continue;
|
|
}
|
|
rst.clear();
|
|
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) == std::string::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;
|
|
for (;;) {
|
|
std::string::size_type* first = nullptr;
|
|
std::string::size_type role_start = std::string::npos;
|
|
std::string::size_type link_start = std::string::npos;
|
|
std::string::size_type lit_start = std::string::npos;
|
|
if (this->CMakeRole.find(line.c_str() + pos)) {
|
|
role_start = this->CMakeRole.start();
|
|
first = &role_start;
|
|
}
|
|
if (this->InlineLiteral.find(line.c_str() + pos)) {
|
|
lit_start = this->InlineLiteral.start();
|
|
if (!first || lit_start < *first) {
|
|
first = &lit_start;
|
|
}
|
|
}
|
|
if (this->InlineLink.find(line.c_str() + pos)) {
|
|
link_start = this->InlineLink.start();
|
|
if (!first || link_start < *first) {
|
|
first = &link_start;
|
|
}
|
|
}
|
|
if (first == &role_start) {
|
|
this->OS << line.substr(pos, role_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("()") == std::string::npos) {
|
|
text += "()";
|
|
}
|
|
this->OS << "``" << text << "``";
|
|
pos += this->CMakeRole.end();
|
|
} else if (first == &lit_start) {
|
|
this->OS << line.substr(pos, lit_start);
|
|
std::string text = this->InlineLiteral.match(1);
|
|
pos += this->InlineLiteral.end();
|
|
this->OS << "``" << text << "``";
|
|
} else if (first == &link_start) {
|
|
this->OS << line.substr(pos, link_start);
|
|
std::string text = this->InlineLink.match(1);
|
|
bool escaped = false;
|
|
for (char c : text) {
|
|
if (escaped) {
|
|
escaped = false;
|
|
this->OS << c;
|
|
} else if (c == '\\') {
|
|
escaped = true;
|
|
} else {
|
|
this->OS << c;
|
|
}
|
|
}
|
|
pos += this->InlineLink.end();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
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<std::string, std::string>::iterator replace =
|
|
this->Replace.find(substitute);
|
|
if (replace != this->Replace.end()) {
|
|
std::pair<std::set<std::string>::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 (auto line : this->MarkupLines) {
|
|
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.clear();
|
|
}
|
|
|
|
void cmRST::ProcessDirectiveTocTree()
|
|
{
|
|
// Process documents referenced by toctree directive.
|
|
for (std::string const& line : this->MarkupLines) {
|
|
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<std::string>& 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<std::string>::const_iterator it = lines.begin();
|
|
size_t leadingEmpty = std::distance(it, cmFindNot(lines, std::string()));
|
|
|
|
std::vector<std::string>::const_reverse_iterator rit = lines.rbegin();
|
|
size_t trailingEmpty =
|
|
std::distance(rit, cmFindNot(cmReverseRange(lines), std::string()));
|
|
|
|
std::vector<std::string>::iterator contentEnd = cmRotate(
|
|
lines.begin(), lines.begin() + leadingEmpty, lines.end() - trailingEmpty);
|
|
lines.erase(contentEnd, lines.end());
|
|
}
|