9#include "clang/Basic/CharInfo.h"
10#include "llvm/ADT/ArrayRef.h"
11#include "llvm/ADT/STLExtras.h"
12#include "llvm/ADT/SmallVector.h"
13#include "llvm/ADT/StringExtras.h"
14#include "llvm/ADT/StringRef.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 needsLeadingEscapePlaintext(
char C, llvm::StringRef Before,
68 llvm::StringRef After,
bool StartsLine) {
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(
" ");
174bool needsLeadingEscapeMarkdown(
char C, llvm::StringRef After) {
177 return looksLikeTag(After);
179 auto End = After.find(
';');
180 if (End == llvm::StringRef::npos)
182 llvm::StringRef Content = After.substr(0, End);
183 if (Content.consume_front(
"#")) {
184 if (Content.consume_front(
"x") || Content.consume_front(
"X"))
185 return llvm::all_of(Content, llvm::isHexDigit);
186 return llvm::all_of(Content, llvm::isDigit);
188 return llvm::all_of(Content, llvm::isAlpha);
195bool needsLeadingEscape(
char C, llvm::StringRef Before, llvm::StringRef After,
196 bool StartsLine,
bool EscapeMarkdown) {
198 return needsLeadingEscapePlaintext(C, Before, After, StartsLine);
199 return needsLeadingEscapeMarkdown(C, After);
206std::string renderText(llvm::StringRef Input,
bool StartsLine,
207 bool EscapeMarkdown) {
209 R.reserve(Input.size());
212 llvm::StringRef Line, Rest;
214 bool IsFirstLine =
true;
216 for (std::tie(Line, Rest) = Input.split(
'\n');
217 !(Line.empty() && Rest.empty());
218 std::tie(Line, Rest) = Rest.split(
'\n')) {
220 bool StartsLineIntern = IsFirstLine ? StartsLine :
true;
224 StringRef LeadingSpaces = Line.take_while(llvm::isSpace);
225 if (!LeadingSpaces.empty()) {
226 R.append(LeadingSpaces);
229 for (
unsigned I = LeadingSpaces.size(); I < Line.size(); ++I) {
230 if (needsLeadingEscape(Line[I], Line.substr(LeadingSpaces.size(), I),
231 Line.substr(I + 1), StartsLineIntern,
234 R.push_back(Line[I]);
247std::string renderInlineBlock(llvm::StringRef Input) {
249 R.reserve(Input.size());
251 for (
size_t From = 0; From < Input.size();) {
252 size_t Next = Input.find(
"`", From);
253 R += Input.substr(From, Next - From);
254 if (Next == llvm::StringRef::npos)
262 if (llvm::StringRef(R).starts_with(
"`") || llvm::StringRef(R).ends_with(
"`"))
263 return "` " + std::move(R) +
" `";
267 if (llvm::StringRef(R).starts_with(
" ") && llvm::StringRef(R).ends_with(
" "))
268 return "` " + std::move(R) +
" `";
269 return "`" + std::move(R) +
"`";
275std::string getMarkerForCodeBlock(llvm::StringRef Input) {
280 for (
char C : Input) {
290 return std::string(std::max(3u,
MaxBackticks + 1),
'`');
295 llvm::SmallVector<llvm::StringRef> Words;
296 llvm::SplitString(Input, Words);
297 return llvm::join(Words,
" ");
301 void (
Block::*RenderFunc)(llvm::raw_ostream &)
const) {
303 llvm::raw_string_ostream OS(R);
307 [](
const std::unique_ptr<Block> &C) {
return C->isRuler(); });
308 auto Last = llvm::find_if(
310 [](
const std::unique_ptr<Block> &C) {
return !C->isRuler(); });
313 bool LastBlockWasRuler =
true;
315 if (C->isRuler() && LastBlockWasRuler)
317 LastBlockWasRuler = C->isRuler();
318 ((*C).*RenderFunc)(OS);
323 std::string AdjustedResult;
324 llvm::StringRef TrimmedText(OS.str());
325 TrimmedText = TrimmedText.trim();
327 llvm::copy_if(TrimmedText, std::back_inserter(AdjustedResult),
328 [&TrimmedText](
const char &C) {
329 return !llvm::StringRef(TrimmedText.data(),
330 &C - TrimmedText.data() + 1)
332 .ends_with(
"\n\n\n");
335 return AdjustedResult;
352 std::unique_ptr<Block>
clone()
const override {
353 return std::make_unique<Ruler>(*
this);
355 bool isRuler()
const override {
return true; }
364 std::string Marker = getMarkerForCodeBlock(Contents);
366 OS << Marker << Language <<
'\n' << Contents;
367 if (!Contents.empty() && Contents.back() !=
'\n')
371 OS << Marker <<
"\n\n";
376 OS <<
'\n' << Contents <<
"\n\n";
379 std::unique_ptr<Block>
clone()
const override {
380 return std::make_unique<CodeBlock>(*
this);
384 : Contents(std::
move(Contents)), Language(std::
move(Language)) {}
387 std::string Contents;
388 std::string Language;
394 assert(!Input.ends_with(
"\n") &&
"Input should've been trimmed.");
395 std::string IndentedR;
398 IndentedR.reserve(Input.size() + Input.count(
'\n') * 2);
399 for (
size_t I = 0; I < Input.size(); ++I) {
402 if (C ==
'\n' && (((I + 1) < Input.size()) && (Input[I + 1] !=
'\n')))
403 IndentedR.append(
" ");
413 insertHeadingMarkers(OS);
414 Paragraph::renderEscapedMarkdown(OS);
418 insertHeadingMarkers(OS);
419 Paragraph::renderMarkdown(OS);
425 void insertHeadingMarkers(llvm::raw_ostream &OS)
const {
426 OS << std::string(Level,
'#') <<
' ';
434 llvm::raw_string_ostream OS(R);
436 return llvm::StringRef(OS.str()).trim().str();
441 llvm::raw_string_ostream OS(R);
443 return llvm::StringRef(OS.str()).trim().str();
448 llvm::raw_string_ostream OS(R);
450 return llvm::StringRef(OS.str()).trim().str();
454 bool NeedsSpace =
false;
455 bool HasChunks =
false;
456 for (
auto &C : Chunks) {
457 if (C.SpaceBefore || NeedsSpace)
460 case ChunkKind::PlainText:
461 OS << renderText(C.Contents, !HasChunks,
true);
463 case ChunkKind::InlineCode:
464 OS << renderInlineBlock(C.Contents);
466 case ChunkKind::Bold:
467 OS << renderText(
"**" + C.Contents +
"**", !HasChunks,
470 case ChunkKind::Emphasized:
471 OS << renderText(
"*" + C.Contents +
"*", !HasChunks,
476 NeedsSpace = C.SpaceAfter;
483 bool NeedsSpace =
false;
484 bool HasChunks =
false;
485 for (
auto &C : Chunks) {
486 if (C.SpaceBefore || NeedsSpace)
489 case ChunkKind::PlainText:
490 OS << renderText(C.Contents, !HasChunks,
false);
492 case ChunkKind::InlineCode:
493 OS << renderInlineBlock(C.Contents);
495 case ChunkKind::Bold:
496 OS <<
"**" << renderText(C.Contents, !HasChunks,
false)
499 case ChunkKind::Emphasized:
500 OS <<
"*" << renderText(C.Contents, !HasChunks,
false)
505 NeedsSpace = C.SpaceAfter;
512 return std::make_unique<Paragraph>(*
this);
517llvm::StringRef Paragraph::chooseMarker(llvm::ArrayRef<llvm::StringRef> Options,
518 llvm::StringRef
Text)
const {
520 for (llvm::StringRef S : Options)
521 if (
Text.find_first_of(S) == llvm::StringRef::npos)
523 return Options.front();
526bool Paragraph::punctuationIndicatesLineBreak(llvm::StringRef Line)
const {
527 constexpr llvm::StringLiteral
Punctuation = R
"txt(.:,;!?)txt";
530 return !Line.empty() &&
Punctuation.contains(Line.back());
533bool Paragraph::isHardLineBreakIndicator(llvm::StringRef Rest)
const {
536 constexpr llvm::StringLiteral LinebreakIndicators = R
"txt(-*@>#`)txt";
538 Rest = Rest.ltrim(" \t");
542 if (LinebreakIndicators.contains(Rest.front()))
545 if (llvm::isDigit(Rest.front())) {
546 llvm::StringRef AfterDigit = Rest.drop_while(llvm::isDigit);
547 if (AfterDigit.starts_with(
".") || AfterDigit.starts_with(
")"))
553bool Paragraph::isHardLineBreakAfter(llvm::StringRef Line,
554 llvm::StringRef Rest)
const {
558 return Line.ends_with(
" ") || punctuationIndicatesLineBreak(Line) ||
559 isHardLineBreakIndicator(Rest);
563 bool NeedsSpace =
false;
564 std::string ConcatenatedText;
565 ConcatenatedText.reserve(EstimatedStringSize);
567 llvm::raw_string_ostream ConcatenatedOS(ConcatenatedText);
569 for (
auto &C : Chunks) {
571 if (C.Kind == ChunkKind::PlainText) {
572 if (C.SpaceBefore || NeedsSpace)
573 ConcatenatedOS <<
' ';
575 ConcatenatedOS << C.Contents;
576 NeedsSpace = llvm::isSpace(C.Contents.back()) || C.SpaceAfter;
580 if (C.SpaceBefore || NeedsSpace)
581 ConcatenatedOS <<
' ';
582 llvm::StringRef Marker =
"";
583 if (C.Preserve && C.Kind == ChunkKind::InlineCode)
584 Marker = chooseMarker({
"`",
"'",
"\""}, C.Contents);
585 else if (C.Kind == ChunkKind::Bold)
587 else if (C.Kind == ChunkKind::Emphasized)
589 ConcatenatedOS << Marker << C.Contents << Marker;
590 NeedsSpace = C.SpaceAfter;
607 llvm::StringRef Line, Rest;
609 for (std::tie(Line, Rest) =
610 llvm::StringRef(ConcatenatedText).trim().split(
'\n');
611 !(Line.empty() && Rest.empty());
612 std::tie(Line, Rest) = Rest.split(
'\n')) {
631 if (isHardLineBreakAfter(Line, Rest))
633 else if (!Rest.empty())
644BulletList::BulletList() =
default;
645BulletList::~BulletList() =
default;
647void BulletList::renderEscapedMarkdown(llvm::raw_ostream &OS)
const {
648 for (
auto &D : Items) {
649 std::string M = D.asEscapedMarkdown();
658void BulletList::renderMarkdown(llvm::raw_ostream &OS)
const {
659 for (
auto &D : Items) {
660 std::string M = D.asMarkdown();
669void BulletList::renderPlainText(llvm::raw_ostream &OS)
const {
670 for (
auto &D : Items) {
680 Chunks.back().SpaceAfter =
true;
684Paragraph &Paragraph::appendChunk(llvm::StringRef Contents, ChunkKind K) {
685 if (Contents.empty())
687 Chunks.emplace_back();
688 Chunk &
C = Chunks.back();
689 C.Contents = Contents;
692 EstimatedStringSize += Contents.size();
697 if (!Chunks.empty() && Chunks.back().Kind == ChunkKind::PlainText) {
698 Chunks.back().Contents += std::move(
Text);
702 return appendChunk(std::move(
Text), ChunkKind::PlainText);
707 ChunkKind::Emphasized);
714Paragraph &Paragraph::appendCode(llvm::StringRef Code,
bool Preserve) {
716 !Chunks.empty() && Chunks.back().Kind == ChunkKind::InlineCode;
720 EstimatedStringSize += Norm.size();
721 Chunks.emplace_back();
722 Chunk &C = Chunks.back();
723 C.Contents = std::move(Norm);
724 C.Kind = ChunkKind::InlineCode;
725 C.Preserve = Preserve;
727 C.SpaceBefore = AdjacentCode;
732std::unique_ptr<Block> BulletList::clone()
const {
733 return std::make_unique<BulletList>(*
this);
736class Document &BulletList::addItem() {
737 Items.emplace_back();
741Document &Document::operator=(
const Document &Other) {
743 for (
const auto &C : Other.Children)
748void Document::append(Document Other) {
749 std::move(Other.Children.begin(), Other.Children.end(),
754 Children.push_back(std::make_unique<Paragraph>());
758void Document::addRuler() {
Children.push_back(std::make_unique<Ruler>()); }
760void Document::addCodeBlock(std::string Code, std::string Language) {
762 std::make_unique<CodeBlock>(std::move(Code), std::move(Language)));
765std::string Document::asEscapedMarkdown()
const {
769std::string Document::asMarkdown()
const {
773std::string Document::asPlainText()
const {
777BulletList &Document::addBulletList() {
778 Children.emplace_back(std::make_unique<BulletList>());
779 return *
static_cast<BulletList *
>(
Children.back().get());
782Paragraph &Document::addHeading(
size_t Level) {
784 Children.emplace_back(std::make_unique<Heading>(Level));
void renderMarkdown(llvm::raw_ostream &OS) const override
CodeBlock(std::string Contents, std::string Language)
void renderPlainText(llvm::raw_ostream &OS) const override
void renderEscapedMarkdown(llvm::raw_ostream &OS) const override
std::unique_ptr< Block > clone() const override
void renderEscapedMarkdown(llvm::raw_ostream &OS) const override
void renderMarkdown(llvm::raw_ostream &OS) const override
void renderEscapedMarkdown(llvm::raw_ostream &OS) const override
void renderPlainText(llvm::raw_ostream &OS) const override
std::unique_ptr< Block > clone() const override
bool isRuler() const override
void renderMarkdown(llvm::raw_ostream &OS) const override
std::string asEscapedMarkdown() const
virtual void renderPlainText(llvm::raw_ostream &OS) const =0
virtual void renderMarkdown(llvm::raw_ostream &OS) const =0
virtual void renderEscapedMarkdown(llvm::raw_ostream &OS) const =0
std::string asMarkdown() const
std::string asPlainText() const
Represents parts of the markup that can contain strings, like inline code, code block or plain text.
void renderPlainText(llvm::raw_ostream &OS) const override
Paragraph & appendEmphasizedText(llvm::StringRef Text)
Append emphasized text, this translates to the * block in markdown.
Paragraph & appendText(llvm::StringRef Text)
Append plain text to the end of the string.
void renderEscapedMarkdown(llvm::raw_ostream &OS) const override
std::unique_ptr< Block > clone() const override
void renderMarkdown(llvm::raw_ostream &OS) const override
Paragraph & appendBoldText(llvm::StringRef Text)
Append bold text, this translates to the ** block in markdown.
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
std::string renderBlocks(llvm::ArrayRef< std::unique_ptr< Block > > Children, void(Block::*RenderFunc)(llvm::raw_ostream &) const)
std::string indentLines(llvm::StringRef Input)
std::string canonicalizeSpaces(llvm::StringRef Input)