| /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying | 
 |    file Copyright.txt or https://cmake.org/licensing for details.  */ | 
 | #include "cmDocumentationFormatter.h" | 
 |  | 
 | #include <algorithm> // IWYU pragma: keep | 
 | #include <cassert> | 
 | #include <iomanip> | 
 | #include <ostream> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "cmDocumentationEntry.h" | 
 | #include "cmDocumentationSection.h" | 
 |  | 
 | namespace { | 
 | const char* skipSpaces(const char* ptr) | 
 | { | 
 |   assert(ptr); | 
 |   for (; *ptr == ' '; ++ptr) { | 
 |     ; | 
 |   } | 
 |   return ptr; | 
 | } | 
 | const char* skipToSpace(const char* ptr) | 
 | { | 
 |   assert(ptr); | 
 |   for (; *ptr && (*ptr != '\n') && (*ptr != ' '); ++ptr) { | 
 |     ; | 
 |   } | 
 |   return ptr; | 
 | } | 
 | } | 
 |  | 
 | void cmDocumentationFormatter::PrintFormatted(std::ostream& os, | 
 |                                               std::string const& text) const | 
 | { | 
 |   if (text.empty()) { | 
 |     return; | 
 |   } | 
 |  | 
 |   struct Buffer | 
 |   { | 
 |     // clang-format off | 
 |     using PrinterFn = void (cmDocumentationFormatter::*)( | 
 |         std::ostream&, std::string const& | 
 |       ) const; | 
 |     // clang-format on | 
 |     std::string collected; | 
 |     const PrinterFn printer; | 
 |   }; | 
 |   // const auto NORMAL_IDX = 0u; | 
 |   const auto PREFORMATTED_IDX = 1u; | 
 |   const auto HANDLERS_SIZE = 2u; | 
 |   Buffer buffers[HANDLERS_SIZE] = { | 
 |     { {}, &cmDocumentationFormatter::PrintParagraph }, | 
 |     { {}, &cmDocumentationFormatter::PrintPreformatted } | 
 |   }; | 
 |  | 
 |   const auto padding = std::string(this->TextIndent, ' '); | 
 |  | 
 |   for (std::size_t pos = 0u, eol = 0u; pos < text.size(); pos = eol) { | 
 |     const auto current_idx = std::size_t(text[pos] == ' '); | 
 |     // size_t(!bool(current_idx)) | 
 |     const auto other_idx = current_idx ^ 1u; | 
 |  | 
 |     // Flush the other buffer if anything has been collected | 
 |     if (!buffers[other_idx].collected.empty()) { | 
 |       // NOTE Whatever the other index is, the current buffered | 
 |       // string expected to be empty. | 
 |       assert(buffers[current_idx].collected.empty()); | 
 |  | 
 |       (this->*buffers[other_idx].printer)(os, buffers[other_idx].collected); | 
 |       buffers[other_idx].collected.clear(); | 
 |     } | 
 |  | 
 |     // ATTENTION The previous implementation had called `PrintParagraph()` | 
 |     // **for every processed (char by char) input line**. | 
 |     // The method unconditionally append the `\n' character after the | 
 |     // printed text. To keep the backward-compatible behavior it's needed to | 
 |     // add the '\n' character to the previously collected line... | 
 |     if (!buffers[current_idx].collected.empty() && | 
 |         current_idx != PREFORMATTED_IDX) { | 
 |       buffers[current_idx].collected += '\n'; | 
 |     } | 
 |  | 
 |     // Lookup EOL | 
 |     eol = text.find('\n', pos); | 
 |     if (current_idx == PREFORMATTED_IDX) { | 
 |       buffers[current_idx].collected.append(padding); | 
 |     } | 
 |     buffers[current_idx].collected.append( | 
 |       text, pos, eol == std::string::npos ? eol : ++eol - pos); | 
 |   } | 
 |  | 
 |   for (auto& buf : buffers) { | 
 |     if (!buf.collected.empty()) { | 
 |       (this->*buf.printer)(os, buf.collected); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void cmDocumentationFormatter::PrintPreformatted(std::ostream& os, | 
 |                                                  std::string const& text) const | 
 | { | 
 |   os << text << '\n'; | 
 | } | 
 |  | 
 | void cmDocumentationFormatter::PrintParagraph(std::ostream& os, | 
 |                                               std::string const& text) const | 
 | { | 
 |   if (this->TextIndent) { | 
 |     os << std::string(this->TextIndent, ' '); | 
 |   } | 
 |   this->PrintColumn(os, text); | 
 |   os << '\n'; | 
 | } | 
 |  | 
 | void cmDocumentationFormatter::PrintColumn(std::ostream& os, | 
 |                                            std::string const& text) const | 
 | { | 
 |   // Print text arranged in an indented column of fixed width. | 
 |   bool newSentence = false; | 
 |   bool firstLine = true; | 
 |  | 
 |   assert(this->TextIndent < this->TextWidth); | 
 |   const std::ptrdiff_t width = this->TextWidth - this->TextIndent; | 
 |   std::ptrdiff_t column = 0; | 
 |  | 
 |   // Loop until the end of the text. | 
 |   for (const char *l = text.c_str(), *r = skipToSpace(text.c_str()); *l; | 
 |        l = skipSpaces(r), r = skipToSpace(l)) { | 
 |     // Does it fit on this line? | 
 |     if (r - l < width - column - std::ptrdiff_t(newSentence)) { | 
 |       // Word fits on this line. | 
 |       if (r > l) { | 
 |         if (column) { | 
 |           // Not first word on line.  Separate from the previous word | 
 |           // by a space, or two if this is a new sentence. | 
 |           os << &("  "[std::size_t(!newSentence)]); | 
 |           column += 1u + std::ptrdiff_t(newSentence); | 
 |         } else if (!firstLine && this->TextIndent) { | 
 |           // First word on line.  Print indentation unless this is the | 
 |           // first line. | 
 |           os << std::string(this->TextIndent, ' '); | 
 |         } | 
 |  | 
 |         // Print the word. | 
 |         os.write(l, r - l); | 
 |         newSentence = (*(r - 1) == '.'); | 
 |       } | 
 |  | 
 |       if (*r == '\n') { | 
 |         // Text provided a newline.  Start a new line. | 
 |         os << '\n'; | 
 |         ++r; | 
 |         column = 0; | 
 |         firstLine = false; | 
 |       } else { | 
 |         // No provided newline.  Continue this line. | 
 |         column += r - l; | 
 |       } | 
 |     } else { | 
 |       // Word does not fit on this line.  Start a new line. | 
 |       os << '\n'; | 
 |       firstLine = false; | 
 |       if (r > l) { | 
 |         os << std::string(this->TextIndent, ' '); | 
 |         os.write(l, r - l); | 
 |         column = r - l; | 
 |         newSentence = (*(r - 1) == '.'); | 
 |       } else { | 
 |         column = 0; | 
 |       } | 
 |     } | 
 |     // Move to beginning of next word.  Skip over whitespace. | 
 |   } | 
 | } | 
 |  | 
 | void cmDocumentationFormatter::PrintSection( | 
 |   std::ostream& os, cmDocumentationSection const& section) | 
 | { | 
 |   const std::size_t PREFIX_SIZE = | 
 |     sizeof(cmDocumentationEntry::CustomNamePrefix) + 1u; | 
 |   // length of the "= " literal (see below) | 
 |   const std::size_t SUFFIX_SIZE = 2u; | 
 |   // legacy magic number ;-) | 
 |   const std::size_t NAME_SIZE = 29u; | 
 |  | 
 |   const std::size_t PADDING_SIZE = PREFIX_SIZE + SUFFIX_SIZE; | 
 |   const std::size_t TITLE_SIZE = NAME_SIZE + PADDING_SIZE; | 
 |  | 
 |   const auto savedIndent = this->TextIndent; | 
 |  | 
 |   os << section.GetName() << '\n'; | 
 |  | 
 |   for (cmDocumentationEntry const& entry : section.GetEntries()) { | 
 |     if (!entry.Name.empty()) { | 
 |       this->TextIndent = TITLE_SIZE; | 
 |       os << std::setw(PREFIX_SIZE) << std::left << entry.CustomNamePrefix | 
 |          << std::setw(int(std::max(NAME_SIZE, entry.Name.size()))) | 
 |          << entry.Name; | 
 |       if (entry.Name.size() > NAME_SIZE) { | 
 |         os << '\n' << std::setw(int(this->TextIndent - PREFIX_SIZE)) << ' '; | 
 |       } | 
 |       os << "= "; | 
 |       this->PrintColumn(os, entry.Brief); | 
 |       os << '\n'; | 
 |     } else { | 
 |       os << '\n'; | 
 |       this->TextIndent = 0u; | 
 |       this->PrintFormatted(os, entry.Brief); | 
 |     } | 
 |   } | 
 |  | 
 |   os << '\n'; | 
 |  | 
 |   this->TextIndent = savedIndent; | 
 | } |