| /* 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; |
| } |