9#include "llvm/ADT/ArrayRef.h"
10#include "llvm/ADT/STLExtras.h"
11#include "llvm/ADT/SmallVector.h"
12#include "llvm/ADT/StringExtras.h"
13#include "llvm/ADT/StringRef.h"
14#include "llvm/Support/Compiler.h"
15#include "llvm/Support/raw_ostream.h"
30bool looksLikeTag(llvm::StringRef Contents) {
33 if (Contents.front() ==
'!' || Contents.front() ==
'?' ||
34 Contents.front() ==
'/')
37 if (!llvm::isAlpha(Contents.front()))
41 .drop_while([](
char C) {
42 return llvm::isAlnum(
C) ||
C ==
'-' ||
C ==
'_' ||
C ==
':';
44 .drop_while(llvm::isSpace);
47 for (; !Contents.empty(); Contents = Contents.drop_front()) {
48 if (llvm::isAlnum(Contents.front()) || llvm::isSpace(Contents.front()))
50 if (Contents.front() ==
'>' || Contents.starts_with(
"/>"))
52 if (Contents.front() ==
'=')
67bool needsLeadingEscape(
char C, llvm::StringRef Before, llvm::StringRef After,
69 assert(Before.take_while(llvm::isSpace).empty());
70 auto RulerLength = [&]() ->
unsigned {
71 if (!StartsLine || !Before.empty())
73 llvm::StringRef A = After.rtrim();
74 return llvm::all_of(A, [
C](
char D) {
return C == D; }) ? 1 + A.size() : 0;
76 auto IsBullet = [&]() {
77 return StartsLine && Before.empty() &&
78 (After.empty() || After.starts_with(
" "));
80 auto SpaceSurrounds = [&]() {
81 return (After.empty() || llvm::isSpace(After.front())) &&
82 (Before.empty() || llvm::isSpace(Before.back()));
84 auto WordSurrounds = [&]() {
85 return (!After.empty() && llvm::isAlnum(After.front())) &&
86 (!Before.empty() && llvm::isAlnum(Before.back()));
97 return StartsLine && Before.empty() && After.starts_with(
"~~");
99 if (!StartsLine || !Before.empty())
101 llvm::StringRef Rest = After.ltrim(
C);
102 return Rest.empty() || Rest.starts_with(
" ");
112 return After.starts_with(
":") || After.starts_with(
"(");
114 return RulerLength() > 0;
116 if (RulerLength() >= 3)
120 return !(SpaceSurrounds() || WordSurrounds());
122 if (RulerLength() > 0)
128 return IsBullet() || RulerLength() >= 3 || !SpaceSurrounds();
130 return looksLikeTag(After);
132 return StartsLine && Before.empty();
134 auto End = After.find(
';');
135 if (End == llvm::StringRef::npos)
137 llvm::StringRef Content = After.substr(0, End);
138 if (Content.consume_front(
"#")) {
139 if (Content.consume_front(
"x") || Content.consume_front(
"X"))
140 return llvm::all_of(Content, llvm::isHexDigit);
141 return llvm::all_of(Content, llvm::isDigit);
143 return llvm::all_of(Content, llvm::isAlpha);
147 return StartsLine && !Before.empty() &&
148 llvm::all_of(Before, llvm::isDigit) && After.starts_with(
" ");
156std::string renderText(llvm::StringRef Input,
bool StartsLine) {
158 for (
unsigned I = 0; I < Input.size(); ++I) {
159 if (needsLeadingEscape(Input[I], Input.substr(0, I), Input.substr(I + 1),
162 R.push_back(Input[I]);
169std::string renderInlineBlock(llvm::StringRef Input) {
172 for (
size_t From = 0; From < Input.size();) {
173 size_t Next = Input.find(
"`", From);
174 R += Input.substr(From, Next - From);
175 if (Next == llvm::StringRef::npos)
183 if (llvm::StringRef(R).starts_with(
"`") || llvm::StringRef(R).ends_with(
"`"))
184 return "` " + std::move(R) +
" `";
188 if (llvm::StringRef(R).starts_with(
" ") && llvm::StringRef(R).ends_with(
" "))
189 return "` " + std::move(R) +
" `";
190 return "`" + std::move(R) +
"`";
196std::string getMarkerForCodeBlock(llvm::StringRef Input) {
199 unsigned MaxBackticks = 0;
200 unsigned Backticks = 0;
201 for (
char C : Input) {
206 MaxBackticks = std::max(MaxBackticks, Backticks);
209 MaxBackticks = std::max(Backticks, MaxBackticks);
211 return std::string(std::max(3u, MaxBackticks + 1),
'`');
215std::string canonicalizeSpaces(llvm::StringRef Input) {
216 llvm::SmallVector<llvm::StringRef> Words;
217 llvm::SplitString(Input, Words);
218 return llvm::join(Words,
" ");
221std::string renderBlocks(llvm::ArrayRef<std::unique_ptr<Block>>
Children,
222 void (
Block::*RenderFunc)(llvm::raw_ostream &)
const) {
224 llvm::raw_string_ostream
OS(R);
228 [](
const std::unique_ptr<Block> &
C) {
return C->isRuler(); });
229 auto Last = llvm::find_if(
231 [](
const std::unique_ptr<Block> &
C) {
return !
C->isRuler(); });
234 bool LastBlockWasRuler =
true;
236 if (
C->isRuler() && LastBlockWasRuler)
238 LastBlockWasRuler =
C->isRuler();
239 ((*C).*RenderFunc)(
OS);
244 std::string AdjustedResult;
245 llvm::StringRef TrimmedText(
OS.str());
246 TrimmedText = TrimmedText.trim();
248 llvm::copy_if(TrimmedText, std::back_inserter(AdjustedResult),
249 [&TrimmedText](
const char &
C) {
250 return !llvm::StringRef(TrimmedText.data(),
251 &
C - TrimmedText.data() + 1)
253 .ends_with(
"\n\n\n");
256 return AdjustedResult;
262class Ruler :
public Block {
264 void renderMarkdown(llvm::raw_ostream &
OS)
const override {
269 void renderPlainText(llvm::raw_ostream &
OS)
const override {
OS <<
'\n'; }
270 std::unique_ptr<Block> clone()
const override {
271 return std::make_unique<Ruler>(*
this);
273 bool isRuler()
const override {
return true; }
276class CodeBlock :
public Block {
278 void renderMarkdown(llvm::raw_ostream &
OS)
const override {
279 std::string Marker = getMarkerForCodeBlock(Contents);
281 OS << Marker << Language <<
'\n' << Contents <<
'\n' << Marker <<
'\n';
284 void renderPlainText(llvm::raw_ostream &
OS)
const override {
286 OS <<
'\n' << Contents <<
"\n\n";
289 std::unique_ptr<Block> clone()
const override {
290 return std::make_unique<CodeBlock>(*
this);
293 CodeBlock(std::string Contents, std::string Language)
294 : Contents(std::move(Contents)), Language(std::move(Language)) {}
297 std::string Contents;
298 std::string Language;
303std::string indentLines(llvm::StringRef Input) {
304 assert(!Input.ends_with(
"\n") &&
"Input should've been trimmed.");
305 std::string IndentedR;
307 IndentedR.reserve(Input.size() + Input.count(
'\n') * 2);
308 for (
char C : Input) {
311 IndentedR.append(
" ");
318 Heading(
size_t Level) : Level(Level) {}
319 void renderMarkdown(llvm::raw_ostream &
OS)
const override {
320 OS << std::string(Level,
'#') <<
' ';
332 llvm::raw_string_ostream
OS(R);
334 return llvm::StringRef(
OS.str()).trim().str();
339 llvm::raw_string_ostream
OS(R);
341 return llvm::StringRef(
OS.str()).trim().str();
345 bool NeedsSpace =
false;
346 bool HasChunks =
false;
347 for (
auto &
C : Chunks) {
348 if (
C.SpaceBefore || NeedsSpace)
351 case Chunk::PlainText:
352 OS << renderText(
C.Contents, !HasChunks);
354 case Chunk::InlineCode:
355 OS << renderInlineBlock(
C.Contents);
359 NeedsSpace =
C.SpaceAfter;
368 return std::make_unique<Paragraph>(*
this);
374 llvm::StringRef
Text) {
376 for (llvm::StringRef S : Options)
377 if (
Text.find_first_of(S) == llvm::StringRef::npos)
379 return Options.front();
383 bool NeedsSpace =
false;
384 for (
auto &
C : Chunks) {
385 if (
C.SpaceBefore || NeedsSpace)
387 llvm::StringRef Marker =
"";
388 if (
C.Preserve &&
C.Kind == Chunk::InlineCode)
390 OS << Marker <<
C.Contents << Marker;
391 NeedsSpace =
C.SpaceAfter;
400 for (
auto &D : Items) {
403 OS <<
"- " << indentLines(D.asMarkdown()) <<
'\n';
410 for (
auto &D : Items) {
413 OS <<
"- " << indentLines(D.asPlainText()) <<
'\n';
419 Chunks.back().SpaceAfter =
true;
424 std::string Norm = canonicalizeSpaces(
Text);
427 Chunks.emplace_back();
428 Chunk &
C = Chunks.back();
429 C.Contents = std::move(Norm);
430 C.Kind = Chunk::PlainText;
431 C.SpaceBefore = llvm::isSpace(
Text.front());
432 C.SpaceAfter = llvm::isSpace(
Text.back());
438 !Chunks.empty() && Chunks.back().Kind == Chunk::InlineCode;
439 std::string Norm = canonicalizeSpaces(std::move(
Code));
442 Chunks.emplace_back();
443 Chunk &
C = Chunks.back();
444 C.Contents = std::move(Norm);
445 C.Kind = Chunk::InlineCode;
446 C.Preserve = Preserve;
448 C.SpaceBefore = AdjacentCode;
453 return std::make_unique<BulletList>(*
this);
457 Items.emplace_back();
463 for (
const auto &
C : Other.Children)
469 std::move(Other.Children.begin(), Other.Children.end(),
474 Children.push_back(std::make_unique<Paragraph>());
482 std::make_unique<CodeBlock>(std::move(
Code), std::move(Language)));
494 Children.emplace_back(std::make_unique<BulletList>());
500 Children.emplace_back(std::make_unique<Heading>(Level));
std::vector< std::unique_ptr< HTMLNode > > Children
llvm::raw_string_ostream OS
Holds text and knows how to lay it out.
virtual void renderPlainText(llvm::raw_ostream &OS) const =0
std::string asPlainText() const
std::string asMarkdown() const
virtual void renderMarkdown(llvm::raw_ostream &OS) const =0
Represents a sequence of one or more documents.
void renderPlainText(llvm::raw_ostream &OS) const override
class Document & addItem()
std::unique_ptr< Block > clone() const override
void renderMarkdown(llvm::raw_ostream &OS) const override
A format-agnostic representation for structured text.
Paragraph & addParagraph()
Adds a semantical block that will be separate from others.
std::string asMarkdown() const
Doesn't contain any trailing newlines.
Document & operator=(const Document &)
Paragraph & addHeading(size_t Level)
Heading is a special type of paragraph that will be prepended with Level many '#'s in markdown.
void append(Document Other)
void addCodeBlock(std::string Code, std::string Language="cpp")
Adds a block of code.
BulletList & addBulletList()
std::string asPlainText() const
Doesn't contain any trailing newlines.
void addRuler()
Inserts a horizontal separator to the document.
Represents parts of the markup that can contain strings, like inline code, code block or plain text.
void renderMarkdown(llvm::raw_ostream &OS) const override
std::unique_ptr< Block > clone() const override
Paragraph & appendText(llvm::StringRef Text)
Append plain text to the end of the string.
void renderPlainText(llvm::raw_ostream &OS) const override
Paragraph & appendSpace()
Ensure there is space between the surrounding chunks.
Paragraph & appendCode(llvm::StringRef Code, bool Preserve=false)
Append inline code, this translates to the ` block in markdown.
llvm::StringRef chooseMarker(llvm::ArrayRef< llvm::StringRef > Options, llvm::StringRef Text)
Choose a marker to delimit Text from a prioritized list of options.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//