cmRST.cxx 15 KB

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