diff options
Diffstat (limited to 'Source/cmDocumentationFormatter.cxx')
| -rw-r--r-- | Source/cmDocumentationFormatter.cxx | 234 |
1 files changed, 132 insertions, 102 deletions
diff --git a/Source/cmDocumentationFormatter.cxx b/Source/cmDocumentationFormatter.cxx index 732637e..70ba1fc 100644 --- a/Source/cmDocumentationFormatter.cxx +++ b/Source/cmDocumentationFormatter.cxx @@ -2,7 +2,8 @@ file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmDocumentationFormatter.h" -#include <cstring> +#include <algorithm> +#include <cassert> #include <iomanip> #include <ostream> #include <string> @@ -11,178 +12,207 @@ #include "cmDocumentationEntry.h" #include "cmDocumentationSection.h" -cmDocumentationFormatter::cmDocumentationFormatter() = default; - -cmDocumentationFormatter::~cmDocumentationFormatter() = default; +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, - const char* text) + std::string const& text) const { - if (!text) { + if (text.empty()) { return; } - const char* ptr = text; - while (*ptr) { - // Any ptrs starting in a space are treated as preformatted text. - std::string preformatted; - while (*ptr == ' ') { - for (char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr) { - preformatted.append(1, ch); - } - if (*ptr) { - ++ptr; - preformatted.append(1, '\n'); - } - } - if (!preformatted.empty()) { - this->PrintPreformatted(os, preformatted.c_str()); + + 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(); } - // Other ptrs are treated as paragraphs. - std::string paragraph; - for (char ch = *ptr; ch && ch != '\n'; ++ptr, ch = *ptr) { - paragraph.append(1, ch); + // 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'; } - if (*ptr) { - ++ptr; - paragraph.append(1, '\n'); + + // Lookup EOL + eol = text.find('\n', pos); + if (current_idx == PREFORMATTED_IDX) { + buffers[current_idx].collected.append(padding); } - if (!paragraph.empty()) { - this->PrintParagraph(os, paragraph.c_str()); + 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, - const char* text) + std::string const& text) const { - bool newline = true; - for (const char* ptr = text; *ptr; ++ptr) { - if (newline && *ptr != '\n') { - os << this->TextIndent; - newline = false; - } - os << *ptr; - if (*ptr == '\n') { - newline = true; - } - } - os << "\n"; + os << text << '\n'; } void cmDocumentationFormatter::PrintParagraph(std::ostream& os, - const char* text) + std::string const& text) const { - os << this->TextIndent; + if (this->TextIndent) { + os << std::string(this->TextIndent, ' '); + } this->PrintColumn(os, text); - os << "\n"; -} - -void cmDocumentationFormatter::SetIndent(const char* indent) -{ - this->TextIndent = indent; + os << '\n'; } -void cmDocumentationFormatter::PrintColumn(std::ostream& os, const char* text) +void cmDocumentationFormatter::PrintColumn(std::ostream& os, + std::string const& text) const { // Print text arranged in an indented column of fixed width. - const char* l = text; - long column = 0; bool newSentence = false; bool firstLine = true; - int width = this->TextWidth - static_cast<int>(strlen(this->TextIndent)); - // Loop until the end of the text. - while (*l) { - // Parse the next word. - const char* r = l; - while (*r && (*r != '\n') && (*r != ' ')) { - ++r; - } + 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 - (newSentence ? 1 : 0))) { + 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. - if (newSentence) { - os << " "; - column += 2; - } else { - os << " "; - column += 1; - } - } else { + 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 << (firstLine ? "" : this->TextIndent); + os << std::string(this->TextIndent, ' '); } // Print the word. - os.write(l, static_cast<long>(r - l)); + os.write(l, r - l); newSentence = (*(r - 1) == '.'); } if (*r == '\n') { // Text provided a newline. Start a new line. - os << "\n"; + os << '\n'; ++r; column = 0; firstLine = false; } else { // No provided newline. Continue this line. - column += static_cast<long>(r - l); + column += r - l; } } else { // Word does not fit on this line. Start a new line. - os << "\n"; + os << '\n'; firstLine = false; if (r > l) { - os << this->TextIndent; - os.write(l, static_cast<long>(r - l)); - column = static_cast<long>(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. - l = r; - while (*l == ' ') { - ++l; - } } } void cmDocumentationFormatter::PrintSection( std::ostream& os, cmDocumentationSection const& section) { - os << section.GetName() << "\n"; + 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; - const std::vector<cmDocumentationEntry>& entries = section.GetEntries(); - for (cmDocumentationEntry const& entry : entries) { + os << section.GetName() << '\n'; + + for (cmDocumentationEntry const& entry : section.GetEntries()) { if (!entry.Name.empty()) { - os << std::setw(2) << std::left << entry.CustomNamePrefix << entry.Name; - this->TextIndent = " "; - int align = static_cast<int>(strlen(this->TextIndent)) - 4; - for (int i = static_cast<int>(entry.Name.size()); i < align; ++i) { - os << " "; - } - if (entry.Name.size() > strlen(this->TextIndent) - 4) { - os << "\n"; - os.write(this->TextIndent, strlen(this->TextIndent) - 2); + 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.c_str()); - os << "\n"; + this->PrintColumn(os, entry.Brief); + os << '\n'; } else { - os << "\n"; - this->TextIndent = ""; - this->PrintFormatted(os, entry.Brief.c_str()); + os << '\n'; + this->TextIndent = 0u; + this->PrintFormatted(os, entry.Brief); } } - os << "\n"; + + os << '\n'; + + this->TextIndent = savedIndent; } |
