resolve cyclic dependency with zstd
[platform/upstream/cmake.git] / Source / cmRST.cxx
1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #include "cmRST.h"
4
5 #include <algorithm>
6 #include <cctype>
7 #include <cstddef>
8 #include <iterator>
9 #include <utility>
10
11 #include "cmsys/FStream.hxx"
12
13 #include "cmAlgorithms.h"
14 #include "cmRange.h"
15 #include "cmStringAlgorithms.h"
16 #include "cmSystemTools.h"
17 #include "cmVersion.h"
18
19 cmRST::cmRST(std::ostream& os, std::string docroot)
20   : OS(os)
21   , DocRoot(std::move(docroot))
22   , CMakeDirective("^.. (cmake:)?("
23                    "command|envvar|genex|variable"
24                    ")::[ \t]+([^ \t\n]+)$")
25   , CMakeModuleDirective("^.. cmake-module::[ \t]+([^ \t\n]+)$")
26   , ParsedLiteralDirective("^.. parsed-literal::[ \t]*(.*)$")
27   , CodeBlockDirective("^.. code-block::[ \t]*(.*)$")
28   , ReplaceDirective("^.. (\\|[^|]+\\|) replace::[ \t]*(.*)$")
29   , IncludeDirective("^.. include::[ \t]+([^ \t\n]+)$")
30   , TocTreeDirective("^.. toctree::[ \t]*(.*)$")
31   , ProductionListDirective("^.. productionlist::[ \t]*(.*)$")
32   , NoteDirective("^.. note::[ \t]*(.*)$")
33   , VersionDirective("^.. version(added|changed)::[ \t]*(.*)$")
34   , ModuleRST(R"(^#\[(=*)\[\.rst:$)")
35   , CMakeRole("(:cmake)?:("
36               "command|cpack_gen|generator|genex|"
37               "variable|envvar|module|policy|"
38               "prop_cache|prop_dir|prop_gbl|prop_inst|prop_sf|"
39               "prop_test|prop_tgt|"
40               "manual"
41               "):`(<*([^`<]|[^` \t]<)*)([ \t]+<[^`]*>)?`")
42   , InlineLink("`(<*([^`<]|[^` \t]<)*)([ \t]+<[^`]*>)?`_")
43   , InlineLiteral("``([^`]*)``")
44   , Substitution("(^|[^A-Za-z0-9_])"
45                  "((\\|[^| \t\r\n]([^|\r\n]*[^| \t\r\n])?\\|)(__|_|))"
46                  "([^A-Za-z0-9_]|$)")
47   , TocTreeLink("^.*[ \t]+<([^>]+)>$")
48 {
49   this->Replace["|release|"] = cmVersion::GetCMakeVersion();
50 }
51
52 bool cmRST::ProcessFile(std::string const& fname, bool isModule)
53 {
54   cmsys::ifstream fin(fname.c_str());
55   if (fin) {
56     this->DocDir = cmSystemTools::GetFilenamePath(fname);
57     if (isModule) {
58       this->ProcessModule(fin);
59     } else {
60       this->ProcessRST(fin);
61     }
62     this->OutputLinePending = true;
63     return true;
64   }
65   return false;
66 }
67
68 void cmRST::ProcessRST(std::istream& is)
69 {
70   std::string line;
71   while (cmSystemTools::GetLineFromStream(is, line)) {
72     this->ProcessLine(line);
73   }
74   this->Reset();
75 }
76
77 void cmRST::ProcessModule(std::istream& is)
78 {
79   std::string line;
80   std::string rst;
81   while (cmSystemTools::GetLineFromStream(is, line)) {
82     if (!rst.empty() && rst != "#") {
83       // Bracket mode: check for end bracket
84       std::string::size_type pos = line.find(rst);
85       if (pos == std::string::npos) {
86         this->ProcessLine(line);
87       } else {
88         if (line[0] != '#') {
89           line.resize(pos);
90           this->ProcessLine(line);
91         }
92         rst.clear();
93         this->Reset();
94         this->OutputLinePending = true;
95       }
96     } else {
97       // Line mode: check for .rst start (bracket or line)
98       if (rst == "#") {
99         if (line == "#") {
100           this->ProcessLine("");
101           continue;
102         }
103         if (cmHasLiteralPrefix(line, "# ")) {
104           line.erase(0, 2);
105           this->ProcessLine(line);
106           continue;
107         }
108         rst.clear();
109         this->Reset();
110         this->OutputLinePending = true;
111       }
112       if (line == "#.rst:") {
113         rst = "#";
114       } else if (this->ModuleRST.find(line)) {
115         rst = "]" + this->ModuleRST.match(1) + "]";
116       }
117     }
118   }
119   if (rst == "#") {
120     this->Reset();
121   }
122 }
123
124 void cmRST::Reset()
125 {
126   if (!this->MarkupLines.empty()) {
127     cmRST::UnindentLines(this->MarkupLines);
128   }
129   switch (this->Directive) {
130     case DirectiveNone:
131       break;
132     case DirectiveParsedLiteral:
133       this->ProcessDirectiveParsedLiteral();
134       break;
135     case DirectiveLiteralBlock:
136       this->ProcessDirectiveLiteralBlock();
137       break;
138     case DirectiveCodeBlock:
139       this->ProcessDirectiveCodeBlock();
140       break;
141     case DirectiveReplace:
142       this->ProcessDirectiveReplace();
143       break;
144     case DirectiveTocTree:
145       this->ProcessDirectiveTocTree();
146       break;
147   }
148   this->Markup = MarkupNone;
149   this->Directive = DirectiveNone;
150   this->MarkupLines.clear();
151 }
152
153 void cmRST::ProcessLine(std::string const& line)
154 {
155   bool lastLineEndedInColonColon = this->LastLineEndedInColonColon;
156   this->LastLineEndedInColonColon = false;
157
158   // A line starting in .. is an explicit markup start.
159   if (line == ".." ||
160       (line.size() >= 3 && line[0] == '.' && line[1] == '.' &&
161        isspace(line[2]))) {
162     this->Reset();
163     this->Markup =
164       (line.find_first_not_of(" \t", 2) == std::string::npos ? MarkupEmpty
165                                                              : MarkupNormal);
166     // XXX(clang-tidy): https://bugs.llvm.org/show_bug.cgi?id=44165
167     // NOLINTNEXTLINE(bugprone-branch-clone)
168     if (this->CMakeDirective.find(line)) {
169       // Output cmake domain directives and their content normally.
170       this->NormalLine(line);
171     } else if (this->CMakeModuleDirective.find(line)) {
172       // Process cmake-module directive: scan .cmake file comments.
173       std::string file = this->CMakeModuleDirective.match(1);
174       if (file.empty() || !this->ProcessInclude(file, IncludeModule)) {
175         this->NormalLine(line);
176       }
177     } else if (this->ParsedLiteralDirective.find(line)) {
178       // Record the literal lines to output after whole block.
179       this->Directive = DirectiveParsedLiteral;
180       this->MarkupLines.push_back(this->ParsedLiteralDirective.match(1));
181     } else if (this->CodeBlockDirective.find(line)) {
182       // Record the literal lines to output after whole block.
183       // Ignore the language spec and record the opening line as blank.
184       this->Directive = DirectiveCodeBlock;
185       this->MarkupLines.emplace_back();
186     } else if (this->ReplaceDirective.find(line)) {
187       // Record the replace directive content.
188       this->Directive = DirectiveReplace;
189       this->ReplaceName = this->ReplaceDirective.match(1);
190       this->MarkupLines.push_back(this->ReplaceDirective.match(2));
191     } else if (this->IncludeDirective.find(line)) {
192       // Process the include directive or output the directive and its
193       // content normally if it fails.
194       std::string file = this->IncludeDirective.match(1);
195       if (file.empty() || !this->ProcessInclude(file, IncludeNormal)) {
196         this->NormalLine(line);
197       }
198     } else if (this->TocTreeDirective.find(line)) {
199       // Record the toctree entries to process after whole block.
200       this->Directive = DirectiveTocTree;
201       this->MarkupLines.push_back(this->TocTreeDirective.match(1));
202     } else if (this->ProductionListDirective.find(line)) {
203       // Output productionlist directives and their content normally.
204       this->NormalLine(line);
205     } else if (this->NoteDirective.find(line)) {
206       // Output note directives and their content normally.
207       this->NormalLine(line);
208     } else if (this->VersionDirective.find(line)) {
209       // Output versionadded and versionchanged directives and their content
210       // normally.
211       this->NormalLine(line);
212     }
213   }
214   // An explicit markup start followed nothing but whitespace and a
215   // blank line does not consume any indented text following.
216   else if (this->Markup == MarkupEmpty && line.empty()) {
217     this->NormalLine(line);
218   }
219   // Indented lines following an explicit markup start are explicit markup.
220   else if (this->Markup && (line.empty() || isspace(line[0]))) {
221     this->Markup = MarkupNormal;
222     // Record markup lines if the start line was recorded.
223     if (!this->MarkupLines.empty()) {
224       this->MarkupLines.push_back(line);
225     }
226   }
227   // A blank line following a paragraph ending in "::" starts a literal block.
228   else if (lastLineEndedInColonColon && line.empty()) {
229     // Record the literal lines to output after whole block.
230     this->Markup = MarkupNormal;
231     this->Directive = DirectiveLiteralBlock;
232     this->MarkupLines.emplace_back();
233     this->OutputLine("", false);
234   }
235   // Print non-markup lines.
236   else {
237     this->NormalLine(line);
238     this->LastLineEndedInColonColon =
239       (line.size() >= 2 && line[line.size() - 2] == ':' && line.back() == ':');
240   }
241 }
242
243 void cmRST::NormalLine(std::string const& line)
244 {
245   this->Reset();
246   this->OutputLine(line, true);
247 }
248
249 void cmRST::OutputLine(std::string const& line_in, bool inlineMarkup)
250 {
251   if (this->OutputLinePending) {
252     this->OS << "\n";
253     this->OutputLinePending = false;
254   }
255   if (inlineMarkup) {
256     std::string line = this->ReplaceSubstitutions(line_in);
257     std::string::size_type pos = 0;
258     for (;;) {
259       std::string::size_type* first = nullptr;
260       std::string::size_type role_start = std::string::npos;
261       std::string::size_type link_start = std::string::npos;
262       std::string::size_type lit_start = std::string::npos;
263       if (this->CMakeRole.find(line.c_str() + pos)) {
264         role_start = this->CMakeRole.start();
265         first = &role_start;
266       }
267       if (this->InlineLiteral.find(line.c_str() + pos)) {
268         lit_start = this->InlineLiteral.start();
269         if (!first || lit_start < *first) {
270           first = &lit_start;
271         }
272       }
273       if (this->InlineLink.find(line.c_str() + pos)) {
274         link_start = this->InlineLink.start();
275         if (!first || link_start < *first) {
276           first = &link_start;
277         }
278       }
279       if (first == &role_start) {
280         this->OS << line.substr(pos, role_start);
281         std::string text = this->CMakeRole.match(3);
282         // If a command reference has no explicit target and
283         // no explicit "(...)" then add "()" to the text.
284         if (this->CMakeRole.match(2) == "command" &&
285             this->CMakeRole.match(5).empty() &&
286             text.find_first_of("()") == std::string::npos) {
287           text += "()";
288         }
289         this->OS << "``" << text << "``";
290         pos += this->CMakeRole.end();
291       } else if (first == &lit_start) {
292         this->OS << line.substr(pos, lit_start);
293         std::string text = this->InlineLiteral.match(1);
294         pos += this->InlineLiteral.end();
295         this->OS << "``" << text << "``";
296       } else if (first == &link_start) {
297         this->OS << line.substr(pos, link_start);
298         std::string text = this->InlineLink.match(1);
299         bool escaped = false;
300         for (char c : text) {
301           if (escaped) {
302             escaped = false;
303             this->OS << c;
304           } else if (c == '\\') {
305             escaped = true;
306           } else {
307             this->OS << c;
308           }
309         }
310         pos += this->InlineLink.end();
311       } else {
312         break;
313       }
314     }
315     this->OS << line.substr(pos) << "\n";
316   } else {
317     this->OS << line_in << "\n";
318   }
319 }
320
321 std::string cmRST::ReplaceSubstitutions(std::string const& line)
322 {
323   std::string out;
324   std::string::size_type pos = 0;
325   while (this->Substitution.find(line.c_str() + pos)) {
326     std::string::size_type start = this->Substitution.start(2);
327     std::string::size_type end = this->Substitution.end(2);
328     std::string substitute = this->Substitution.match(3);
329     auto replace = this->Replace.find(substitute);
330     if (replace != this->Replace.end()) {
331       std::pair<std::set<std::string>::iterator, bool> replaced =
332         this->Replaced.insert(substitute);
333       if (replaced.second) {
334         substitute = this->ReplaceSubstitutions(replace->second);
335         this->Replaced.erase(replaced.first);
336       }
337     }
338     out += line.substr(pos, start);
339     out += substitute;
340     pos += end;
341   }
342   out += line.substr(pos);
343   return out;
344 }
345
346 void cmRST::OutputMarkupLines(bool inlineMarkup)
347 {
348   for (auto line : this->MarkupLines) {
349     if (!line.empty()) {
350       line = cmStrCat(" ", line);
351     }
352     this->OutputLine(line, inlineMarkup);
353   }
354   this->OutputLinePending = true;
355 }
356
357 bool cmRST::ProcessInclude(std::string file, IncludeType type)
358 {
359   bool found = false;
360   if (this->IncludeDepth < 10) {
361     cmRST r(this->OS, this->DocRoot);
362     r.IncludeDepth = this->IncludeDepth + 1;
363     r.OutputLinePending = this->OutputLinePending;
364     if (type != IncludeTocTree) {
365       r.Replace = this->Replace;
366     }
367     if (file[0] == '/') {
368       file = this->DocRoot + file;
369     } else {
370       file = this->DocDir + "/" + file;
371     }
372     found = r.ProcessFile(file, type == IncludeModule);
373     if (type != IncludeTocTree) {
374       this->Replace = r.Replace;
375     }
376     this->OutputLinePending = r.OutputLinePending;
377   }
378   return found;
379 }
380
381 void cmRST::ProcessDirectiveParsedLiteral()
382 {
383   this->OutputMarkupLines(true);
384 }
385
386 void cmRST::ProcessDirectiveLiteralBlock()
387 {
388   this->OutputMarkupLines(false);
389 }
390
391 void cmRST::ProcessDirectiveCodeBlock()
392 {
393   this->OutputMarkupLines(false);
394 }
395
396 void cmRST::ProcessDirectiveReplace()
397 {
398   // Record markup lines as replacement text.
399   std::string& replacement = this->Replace[this->ReplaceName];
400   replacement += cmJoin(this->MarkupLines, " ");
401   this->ReplaceName.clear();
402 }
403
404 void cmRST::ProcessDirectiveTocTree()
405 {
406   // Process documents referenced by toctree directive.
407   for (std::string const& line : this->MarkupLines) {
408     if (!line.empty() && line[0] != ':') {
409       if (this->TocTreeLink.find(line)) {
410         std::string const& link = this->TocTreeLink.match(1);
411         this->ProcessInclude(link + ".rst", IncludeTocTree);
412       } else {
413         this->ProcessInclude(line + ".rst", IncludeTocTree);
414       }
415     }
416   }
417 }
418
419 void cmRST::UnindentLines(std::vector<std::string>& lines)
420 {
421   // Remove the common indentation from the second and later lines.
422   std::string indentText;
423   std::string::size_type indentEnd = 0;
424   bool first = true;
425   for (size_t i = 1; i < lines.size(); ++i) {
426     std::string const& line = lines[i];
427
428     // Do not consider empty lines.
429     if (line.empty()) {
430       continue;
431     }
432
433     // Record indentation on first non-empty line.
434     if (first) {
435       first = false;
436       indentEnd = line.find_first_not_of(" \t");
437       indentText = line.substr(0, indentEnd);
438       continue;
439     }
440
441     // Truncate indentation to match that on this line.
442     indentEnd = std::min(indentEnd, line.size());
443     for (std::string::size_type j = 0; j != indentEnd; ++j) {
444       if (line[j] != indentText[j]) {
445         indentEnd = j;
446         break;
447       }
448     }
449   }
450
451   // Update second and later lines.
452   for (size_t i = 1; i < lines.size(); ++i) {
453     std::string& line = lines[i];
454     if (!line.empty()) {
455       line = line.substr(indentEnd);
456     }
457   }
458
459   auto it = lines.cbegin();
460   size_t leadingEmpty = std::distance(it, cmFindNot(lines, std::string()));
461
462   auto rit = lines.crbegin();
463   size_t trailingEmpty =
464     std::distance(rit, cmFindNot(cmReverseRange(lines), std::string()));
465
466   if ((leadingEmpty + trailingEmpty) >= lines.size()) {
467     // All lines are empty.  The markup block is empty.  Leave only one.
468     lines.resize(1);
469     return;
470   }
471
472   auto contentEnd = cmRotate(lines.begin(), lines.begin() + leadingEmpty,
473                              lines.end() - trailingEmpty);
474   lines.erase(contentEnd, lines.end());
475 }