| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file LICENSE.rst or https://cmake.org/licensing for details. */
- #include "cmDocumentationFormatter.h"
- #include <algorithm> // IWYU pragma: keep
- #include <cassert>
- #include <iomanip>
- #include <iterator>
- #include <ostream>
- #include <string>
- #include <vector>
- #include <cm/string_view>
- #include <cmext/string_view>
- #include "cmDocumentationEntry.h"
- #include "cmDocumentationSection.h"
- #include "cmStringAlgorithms.h"
- namespace {
- auto const EOL = "\n"_s;
- auto const SPACE = " "_s;
- auto const TWO_SPACES = " "_s;
- auto const MAX_WIDTH_PADDING =
- std::string(cmDocumentationFormatter::TEXT_WIDTH, ' ');
- void FormatLine(std::back_insert_iterator<std::vector<cm::string_view>> outIt,
- cm::string_view const text, cm::string_view const padding)
- {
- auto tokens = cmTokenizedView(text, ' ', cmTokenizerMode::New);
- if (tokens.empty()) {
- return;
- }
- // Push padding in front of a first line
- if (!padding.empty()) {
- outIt = padding;
- }
- auto currentWidth = padding.size();
- auto newSentence = false;
- for (auto token : tokens) {
- // It's no need to add a space if this is a very first
- // word on a line.
- auto const needSpace = currentWidth > padding.size();
- // Evaluate the size of a current token + possibly spaces before it.
- auto const tokenWithSpaceSize = token.size() + std::size_t(needSpace) +
- std::size_t(needSpace && newSentence);
- // Check if a current word fits on a line.
- // Also, take in account:
- // - extra space if not a first word on a line
- // - extra space if last token ends w/ a period
- if (currentWidth + tokenWithSpaceSize <=
- cmDocumentationFormatter::TEXT_WIDTH) {
- // If not a first word on a line...
- if (needSpace) {
- // ... add a space after the last token +
- // possibly one more space if the last token
- // ends with a period (means, end of a sentence).
- outIt = newSentence ? TWO_SPACES : SPACE;
- }
- outIt = token;
- currentWidth += tokenWithSpaceSize;
- } else {
- // Start a new line!
- outIt = EOL;
- if (!padding.empty()) {
- outIt = padding;
- }
- outIt = token;
- currentWidth = padding.size() + token.size();
- }
- // Start a new sentence if the current word ends with period
- newSentence = token.back() == '.';
- }
- // Always add EOL at the end of formatted text
- outIt = EOL;
- }
- } // anonymous namespace
- std::string cmDocumentationFormatter::Format(cm::string_view text) const
- {
- // Exit early on empty text
- if (text.empty()) {
- return {};
- }
- assert(this->TextIndent < this->TEXT_WIDTH);
- auto const padding =
- cm::string_view(MAX_WIDTH_PADDING.c_str(), this->TextIndent);
- std::vector<cm::string_view> tokens;
- auto outIt = std::back_inserter(tokens);
- auto prevWasPreFormatted = false;
- // NOTE Can't use `cmTokenizedView()` cuz every sequential EOL does matter
- // (and `cmTokenizedView()` will squeeze 'em)
- for ( // clang-format off
- std::string::size_type start = 0
- , end = text.find('\n')
- ; start < text.size()
- ; start = end + ((end != std::string::npos) ? 1 : 0)
- , end = text.find('\n', start)
- ) // clang-format on
- {
- auto const isLastLine = end == std::string::npos;
- auto const line =
- isLastLine ? text.substr(start) : text.substr(start, end - start);
- if (!line.empty() && line.front() == ' ') {
- // Preformatted lines go as is w/ a leading padding
- if (!padding.empty()) {
- outIt = padding;
- }
- outIt = line;
- prevWasPreFormatted = true;
- } else {
- // Separate a normal paragraph from a pre-formatted
- // w/ an extra EOL
- if (prevWasPreFormatted) {
- outIt = EOL;
- }
- if (line.empty()) {
- if (!isLastLine) {
- outIt = EOL;
- }
- } else {
- FormatLine(outIt, line, padding);
- }
- prevWasPreFormatted = false;
- }
- if (!isLastLine) {
- outIt = EOL;
- }
- }
- if (prevWasPreFormatted) {
- outIt = EOL;
- }
- return cmJoinStrings(tokens, {}, {});
- }
- void cmDocumentationFormatter::PrintSection(
- std::ostream& os, cmDocumentationSection const& section)
- {
- std::size_t const PREFIX_SIZE =
- sizeof(cmDocumentationEntry::CustomNamePrefix) + 1u;
- // length of the "= " literal (see below)
- std::size_t const SUFFIX_SIZE = 2u;
- // legacy magic number ;-)
- std::size_t const NAME_SIZE = 29u;
- std::size_t const PADDING_SIZE = PREFIX_SIZE + SUFFIX_SIZE;
- std::size_t const TITLE_SIZE = NAME_SIZE + PADDING_SIZE;
- auto const 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->Format(entry.Brief).substr(this->TextIndent);
- } else {
- this->TextIndent = 0u;
- os << '\n' << this->Format(entry.Brief);
- }
- }
- os << '\n';
- this->TextIndent = savedIndent;
- }
|