
``` git grep -lz 'Copyright.txt or https://cmake.org/licensing ' | while IFS= read -r -d $'\0' f ; do sed -i '/Copyright.txt or https:\/\/cmake.org\/licensing / { s/Copyright.txt/LICENSE.rst/ }' "$f" ; done ```
185 lines
5.3 KiB
C++
185 lines
5.3 KiB
C++
/* 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(std::string 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
|
|
? cm::string_view{ text.c_str() + start }
|
|
: cm::string_view{ text.c_str() + 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;
|
|
}
|