cmDocumentationFormatter.cxx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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 "cmDocumentationFormatter.h"
  4. #include <algorithm> // IWYU pragma: keep
  5. #include <cassert>
  6. #include <iomanip>
  7. #include <ostream>
  8. #include <string>
  9. #include <vector>
  10. #include "cmDocumentationEntry.h"
  11. #include "cmDocumentationSection.h"
  12. namespace {
  13. const char* skipSpaces(const char* ptr)
  14. {
  15. assert(ptr);
  16. for (; *ptr == ' '; ++ptr) {
  17. ;
  18. }
  19. return ptr;
  20. }
  21. const char* skipToSpace(const char* ptr)
  22. {
  23. assert(ptr);
  24. for (; *ptr && (*ptr != '\n') && (*ptr != ' '); ++ptr) {
  25. ;
  26. }
  27. return ptr;
  28. }
  29. }
  30. void cmDocumentationFormatter::PrintFormatted(std::ostream& os,
  31. std::string const& text) const
  32. {
  33. if (text.empty()) {
  34. return;
  35. }
  36. struct Buffer
  37. {
  38. // clang-format off
  39. using PrinterFn = void (cmDocumentationFormatter::*)(
  40. std::ostream&, std::string const&
  41. ) const;
  42. // clang-format on
  43. std::string collected;
  44. const PrinterFn printer;
  45. };
  46. // const auto NORMAL_IDX = 0u;
  47. const auto PREFORMATTED_IDX = 1u;
  48. const auto HANDLERS_SIZE = 2u;
  49. Buffer buffers[HANDLERS_SIZE] = {
  50. { {}, &cmDocumentationFormatter::PrintParagraph },
  51. { {}, &cmDocumentationFormatter::PrintPreformatted }
  52. };
  53. const auto padding = std::string(this->TextIndent, ' ');
  54. for (std::size_t pos = 0u, eol = 0u; pos < text.size(); pos = eol) {
  55. const auto current_idx = std::size_t(text[pos] == ' ');
  56. // size_t(!bool(current_idx))
  57. const auto other_idx = current_idx ^ 1u;
  58. // Flush the other buffer if anything has been collected
  59. if (!buffers[other_idx].collected.empty()) {
  60. // NOTE Whatever the other index is, the current buffered
  61. // string expected to be empty.
  62. assert(buffers[current_idx].collected.empty());
  63. (this->*buffers[other_idx].printer)(os, buffers[other_idx].collected);
  64. buffers[other_idx].collected.clear();
  65. }
  66. // ATTENTION The previous implementation had called `PrintParagraph()`
  67. // **for every processed (char by char) input line**.
  68. // The method unconditionally append the `\n' character after the
  69. // printed text. To keep the backward-compatible behavior it's needed to
  70. // add the '\n' character to the previously collected line...
  71. if (!buffers[current_idx].collected.empty() &&
  72. current_idx != PREFORMATTED_IDX) {
  73. buffers[current_idx].collected += '\n';
  74. }
  75. // Lookup EOL
  76. eol = text.find('\n', pos);
  77. if (current_idx == PREFORMATTED_IDX) {
  78. buffers[current_idx].collected.append(padding);
  79. }
  80. buffers[current_idx].collected.append(
  81. text, pos, eol == std::string::npos ? eol : ++eol - pos);
  82. }
  83. for (auto& buf : buffers) {
  84. if (!buf.collected.empty()) {
  85. (this->*buf.printer)(os, buf.collected);
  86. }
  87. }
  88. }
  89. void cmDocumentationFormatter::PrintPreformatted(std::ostream& os,
  90. std::string const& text) const
  91. {
  92. os << text << '\n';
  93. }
  94. void cmDocumentationFormatter::PrintParagraph(std::ostream& os,
  95. std::string const& text) const
  96. {
  97. if (this->TextIndent) {
  98. os << std::string(this->TextIndent, ' ');
  99. }
  100. this->PrintColumn(os, text);
  101. os << '\n';
  102. }
  103. void cmDocumentationFormatter::PrintColumn(std::ostream& os,
  104. std::string const& text) const
  105. {
  106. // Print text arranged in an indented column of fixed width.
  107. bool newSentence = false;
  108. bool firstLine = true;
  109. assert(this->TextIndent < this->TextWidth);
  110. const std::ptrdiff_t width = this->TextWidth - this->TextIndent;
  111. std::ptrdiff_t column = 0;
  112. // Loop until the end of the text.
  113. for (const char *l = text.c_str(), *r = skipToSpace(text.c_str()); *l;
  114. l = skipSpaces(r), r = skipToSpace(l)) {
  115. // Does it fit on this line?
  116. if (r - l < width - column - std::ptrdiff_t(newSentence)) {
  117. // Word fits on this line.
  118. if (r > l) {
  119. if (column) {
  120. // Not first word on line. Separate from the previous word
  121. // by a space, or two if this is a new sentence.
  122. os << &(" "[std::size_t(!newSentence)]);
  123. column += 1u + std::ptrdiff_t(newSentence);
  124. } else if (!firstLine && this->TextIndent) {
  125. // First word on line. Print indentation unless this is the
  126. // first line.
  127. os << std::string(this->TextIndent, ' ');
  128. }
  129. // Print the word.
  130. os.write(l, r - l);
  131. newSentence = (*(r - 1) == '.');
  132. }
  133. if (*r == '\n') {
  134. // Text provided a newline. Start a new line.
  135. os << '\n';
  136. ++r;
  137. column = 0;
  138. firstLine = false;
  139. } else {
  140. // No provided newline. Continue this line.
  141. column += r - l;
  142. }
  143. } else {
  144. // Word does not fit on this line. Start a new line.
  145. os << '\n';
  146. firstLine = false;
  147. if (r > l) {
  148. os << std::string(this->TextIndent, ' ');
  149. os.write(l, r - l);
  150. column = r - l;
  151. newSentence = (*(r - 1) == '.');
  152. } else {
  153. column = 0;
  154. }
  155. }
  156. // Move to beginning of next word. Skip over whitespace.
  157. }
  158. }
  159. void cmDocumentationFormatter::PrintSection(
  160. std::ostream& os, cmDocumentationSection const& section)
  161. {
  162. const std::size_t PREFIX_SIZE =
  163. sizeof(cmDocumentationEntry::CustomNamePrefix) + 1u;
  164. // length of the "= " literal (see below)
  165. const std::size_t SUFFIX_SIZE = 2u;
  166. // legacy magic number ;-)
  167. const std::size_t NAME_SIZE = 29u;
  168. const std::size_t PADDING_SIZE = PREFIX_SIZE + SUFFIX_SIZE;
  169. const std::size_t TITLE_SIZE = NAME_SIZE + PADDING_SIZE;
  170. const auto savedIndent = this->TextIndent;
  171. os << section.GetName() << '\n';
  172. for (cmDocumentationEntry const& entry : section.GetEntries()) {
  173. if (!entry.Name.empty()) {
  174. this->TextIndent = TITLE_SIZE;
  175. os << std::setw(PREFIX_SIZE) << std::left << entry.CustomNamePrefix
  176. << std::setw(int(std::max(NAME_SIZE, entry.Name.size())))
  177. << entry.Name;
  178. if (entry.Name.size() > NAME_SIZE) {
  179. os << '\n' << std::setw(int(this->TextIndent - PREFIX_SIZE)) << ' ';
  180. }
  181. os << "= ";
  182. this->PrintColumn(os, entry.Brief);
  183. os << '\n';
  184. } else {
  185. os << '\n';
  186. this->TextIndent = 0u;
  187. this->PrintFormatted(os, entry.Brief);
  188. }
  189. }
  190. os << '\n';
  191. this->TextIndent = savedIndent;
  192. }