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