Coverage Report

Created: 2026-10-28 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmDocumentationFormatter.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
#include "cmDocumentationFormatter.h"
4
5
#include <algorithm> // IWYU pragma: keep
6
#include <cassert>
7
#include <iomanip>
8
#include <iterator>
9
#include <ostream>
10
#include <string>
11
#include <vector>
12
13
#include <cm/string_view>
14
#include <cmext/string_view>
15
16
#include "cmDocumentationEntry.h"
17
#include "cmDocumentationSection.h"
18
#include "cmStringAlgorithms.h"
19
20
namespace {
21
auto const EOL = "\n"_s;
22
auto const SPACE = " "_s;
23
auto const TWO_SPACES = "  "_s;
24
auto const MAX_WIDTH_PADDING =
25
  std::string(cmDocumentationFormatter::TEXT_WIDTH, ' ');
26
27
void FormatLine(std::back_insert_iterator<std::vector<cm::string_view>> outIt,
28
                cm::string_view const text, cm::string_view const padding)
29
413k
{
30
413k
  auto tokens = cmTokenizedView(text, ' ', cmTokenizerMode::New);
31
413k
  if (tokens.empty()) {
32
0
    return;
33
0
  }
34
35
  // Push padding in front of a first line
36
413k
  if (!padding.empty()) {
37
413k
    outIt = padding;
38
413k
  }
39
40
413k
  auto currentWidth = padding.size();
41
413k
  auto newSentence = false;
42
43
843k
  for (auto token : tokens) {
44
    // It's no need to add a space if this is a very first
45
    // word on a line.
46
843k
    auto const needSpace = currentWidth > padding.size();
47
    // Evaluate the size of a current token + possibly spaces before it.
48
843k
    auto const tokenWithSpaceSize = token.size() + std::size_t(needSpace) +
49
843k
      std::size_t(needSpace && newSentence);
50
    // Check if a current word fits on a line.
51
    // Also, take in account:
52
    //  - extra space if not a first word on a line
53
    //  - extra space if last token ends w/ a period
54
843k
    if (currentWidth + tokenWithSpaceSize <=
55
843k
        cmDocumentationFormatter::TEXT_WIDTH) {
56
      // If not a first word on a line...
57
796k
      if (needSpace) {
58
        // ... add a space after the last token +
59
        // possibly one more space if the last token
60
        // ends with a period (means, end of a sentence).
61
388k
        outIt = newSentence ? TWO_SPACES : SPACE;
62
388k
      }
63
796k
      outIt = token;
64
796k
      currentWidth += tokenWithSpaceSize;
65
796k
    } else {
66
      // Start a new line!
67
46.9k
      outIt = EOL;
68
46.9k
      if (!padding.empty()) {
69
46.9k
        outIt = padding;
70
46.9k
      }
71
46.9k
      outIt = token;
72
46.9k
      currentWidth = padding.size() + token.size();
73
46.9k
    }
74
75
    // Start a new sentence if the current word ends with period
76
843k
    newSentence = token.back() == '.';
77
843k
  }
78
  // Always add EOL at the end of formatted text
79
413k
  outIt = EOL;
80
413k
}
81
} // anonymous namespace
82
83
std::string cmDocumentationFormatter::Format(cm::string_view text) const
84
37.2k
{
85
  // Exit early on empty text
86
37.2k
  if (text.empty()) {
87
0
    return {};
88
0
  }
89
90
37.2k
  assert(this->TextIndent < this->TEXT_WIDTH);
91
92
37.2k
  auto const padding =
93
37.2k
    cm::string_view(MAX_WIDTH_PADDING.c_str(), this->TextIndent);
94
95
37.2k
  std::vector<cm::string_view> tokens;
96
37.2k
  auto outIt = std::back_inserter(tokens);
97
37.2k
  auto prevWasPreFormatted = false;
98
99
  // NOTE Can't use `cmTokenizedView()` cuz every sequential EOL does matter
100
  // (and `cmTokenizedView()` will squeeze 'em)
101
37.2k
  for ( // clang-format off
102
37.2k
      std::string::size_type start = 0
103
37.2k
    , end = text.find('\n')
104
2.74M
    ; start < text.size()
105
2.70M
    ; start = end + ((end != std::string::npos) ? 1 : 0)
106
2.70M
    , end = text.find('\n', start)
107
37.2k
    ) // clang-format on
108
2.70M
  {
109
2.70M
    auto const isLastLine = end == std::string::npos;
110
2.70M
    auto const line =
111
2.70M
      isLastLine ? text.substr(start) : text.substr(start, end - start);
112
113
2.70M
    if (!line.empty() && line.front() == ' ') {
114
      // Preformatted lines go as is w/ a leading padding
115
37.8k
      if (!padding.empty()) {
116
37.8k
        outIt = padding;
117
37.8k
      }
118
37.8k
      outIt = line;
119
37.8k
      prevWasPreFormatted = true;
120
2.67M
    } else {
121
      // Separate a normal paragraph from a pre-formatted
122
      // w/ an extra EOL
123
2.67M
      if (prevWasPreFormatted) {
124
12.8k
        outIt = EOL;
125
12.8k
      }
126
2.67M
      if (line.empty()) {
127
2.25M
        if (!isLastLine) {
128
2.25M
          outIt = EOL;
129
2.25M
        }
130
2.25M
      } else {
131
413k
        FormatLine(outIt, line, padding);
132
413k
      }
133
2.67M
      prevWasPreFormatted = false;
134
2.67M
    }
135
2.70M
    if (!isLastLine) {
136
2.67M
      outIt = EOL;
137
2.67M
    }
138
2.70M
  }
139
140
37.2k
  if (prevWasPreFormatted) {
141
1.06k
    outIt = EOL;
142
1.06k
  }
143
144
37.2k
  return cmJoinStrings(tokens, {}, {});
145
37.2k
}
146
147
void cmDocumentationFormatter::PrintSection(
148
  std::ostream& os, cmDocumentationSection const& section)
149
0
{
150
0
  std::size_t const PREFIX_SIZE =
151
0
    sizeof(cmDocumentationEntry::CustomNamePrefix) + 1u;
152
  // length of the "= " literal (see below)
153
0
  std::size_t const SUFFIX_SIZE = 2u;
154
  // legacy magic number ;-)
155
0
  std::size_t const NAME_SIZE = 29u;
156
157
0
  std::size_t const PADDING_SIZE = PREFIX_SIZE + SUFFIX_SIZE;
158
0
  std::size_t const TITLE_SIZE = NAME_SIZE + PADDING_SIZE;
159
160
0
  auto const savedIndent = this->TextIndent;
161
162
0
  os << section.GetName() << '\n';
163
164
0
  for (cmDocumentationEntry const& entry : section.GetEntries()) {
165
0
    if (!entry.Name.empty()) {
166
0
      this->TextIndent = TITLE_SIZE;
167
0
      os << std::setw(PREFIX_SIZE) << std::left << entry.CustomNamePrefix
168
0
         << std::setw(int(std::max(NAME_SIZE, entry.Name.size())))
169
0
         << entry.Name;
170
0
      if (entry.Name.size() > NAME_SIZE) {
171
0
        os << '\n' << std::setw(int(this->TextIndent - PREFIX_SIZE)) << ' ';
172
0
      }
173
0
      os << "= " << this->Format(entry.Brief).substr(this->TextIndent);
174
0
    } else {
175
0
      this->TextIndent = 0u;
176
0
      os << '\n' << this->Format(entry.Brief);
177
0
    }
178
0
  }
179
180
0
  os << '\n';
181
182
0
  this->TextIndent = savedIndent;
183
0
}
184
185
void cmDocumentationFormatter::PrintFormatted(std::ostream& os,
186
                                              std::string const& text) const
187
37.2k
{
188
37.2k
  os << this->Format(text);
189
37.2k
}