/* 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 // IWYU pragma: keep #include #include #include #include #include #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; }