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);
212std::string renderText(llvm::StringRef Input,
bool StartsLine,
213 bool EscapeMarkdown) {
215 R.reserve(Input.size());
218 llvm::StringRef Line, Rest;
220 bool IsFirstLine =
true;
224 bool InCodeSpan =
false;
226 for (std::tie(Line, Rest) = Input.split(
'\n');
227 !(Line.empty() && Rest.empty());
228 std::tie(Line, Rest) = Rest.split(
'\n')) {
230 bool StartsLineIntern = IsFirstLine ? StartsLine :
true;
234 StringRef LeadingSpaces = Line.take_while(llvm::isSpace);
235 if (!LeadingSpaces.empty()) {
236 R.append(LeadingSpaces);
243 bool UserEscape =
false;
244 for (
unsigned I = LeadingSpaces.size(); I < Line.size(); ++I) {
246 if (!EscapeMarkdown && !UserEscape && Line[I] ==
'`')
247 InCodeSpan = !InCodeSpan;
250 needsLeadingEscape(Line[I], Line.substr(LeadingSpaces.size(), I),
251 Line.substr(I + 1), StartsLineIntern,
254 R.push_back(Line[I]);
257 UserEscape = !UserEscape;
272std::string renderInlineBlock(llvm::StringRef Input) {
274 R.reserve(Input.size());
276 for (
size_t From = 0; From < Input.size();) {
277 size_t Next = Input.find(
"`", From);
278 R += Input.substr(From, Next - From);
279 if (Next == llvm::StringRef::npos)
287 if (llvm::StringRef(R).starts_with(
"`") || llvm::StringRef(R).ends_with(
"`"))
288 return "` " + std::move(R) +
" `";
292 if (llvm::StringRef(R).starts_with(
" ") && llvm::StringRef(R).ends_with(
" "))
293 return "` " + std::move(R) +
" `";
294 return "`" + std::move(R) +
"`";
300std::string getMarkerForCodeBlock(llvm::StringRef Input) {
305 for (
char C : Input) {
315 return std::string(std::max(3u,
MaxBackticks + 1),
'`');
320 llvm::SmallVector<llvm::StringRef> Words;
321 llvm::SplitString(Input, Words);
322 return llvm::join(Words,
" ");
326 void (
Block::*RenderFunc)(llvm::raw_ostream &)
const) {
328 llvm::raw_string_ostream OS(R);
332 [](
const std::unique_ptr<Block> &C) {
return C->isRuler(); });
333 auto Last = llvm::find_if(
335 [](
const std::unique_ptr<Block> &C) {
return !C->isRuler(); });
338 bool LastBlockWasRuler =
true;
340 if (C->isRuler() && LastBlockWasRuler)
342 LastBlockWasRuler = C->isRuler();
343 ((*C).*RenderFunc)(OS);
348 std::string AdjustedResult;
349 llvm::StringRef TrimmedText(OS.str());
350 TrimmedText = TrimmedText.trim();
352 llvm::copy_if(TrimmedText, std::back_inserter(AdjustedResult),
353 [&TrimmedText](
const char &C) {
354 return !llvm::StringRef(TrimmedText.data(),
355 &C - TrimmedText.data() + 1)
357 .ends_with(
"\n\n\n");
360 return AdjustedResult;
377 std::unique_ptr<Block>
clone()
const override {
378 return std::make_unique<Ruler>(*
this);
380 bool isRuler()
const override {
return true; }
389 std::string Marker = getMarkerForCodeBlock(Contents);
391 OS << Marker << Language <<
'\n' << Contents;
392 if (!Contents.empty() && Contents.back() !=
'\n')
396 OS << Marker <<
"\n\n";
401 OS <<
'\n' << Contents <<
"\n\n";
404 std::unique_ptr<Block>
clone()
const override {
405 return std::make_unique<CodeBlock>(*
this);
409 : Contents(std::
move(Contents)), Language(std::
move(Language)) {}
412 std::string Contents;
413 std::string Language;
419 assert(!Input.ends_with(
"\n") &&
"Input should've been trimmed.");
420 std::string IndentedR;
423 IndentedR.reserve(Input.size() + Input.count(
'\n') * 2);
424 for (
size_t I = 0; I < Input.size(); ++I) {
427 if (C ==
'\n' && (((I + 1) < Input.size()) && (Input[I + 1] !=
'\n')))
428 IndentedR.append(
" ");
438 insertHeadingMarkers(OS);
439 Paragraph::renderEscapedMarkdown(OS);
443 insertHeadingMarkers(OS);
444 Paragraph::renderMarkdown(OS);
450 void insertHeadingMarkers(llvm::raw_ostream &OS)
const {
451 OS << std::string(Level,
'#') <<
' ';
459 llvm::raw_string_ostream OS(R);
461 return llvm::StringRef(OS.str()).trim().str();
466 llvm::raw_string_ostream OS(R);
468 return llvm::StringRef(OS.str()).trim().str();
473 llvm::raw_string_ostream OS(R);
475 return llvm::StringRef(OS.str()).trim().str();
479 bool NeedsSpace =
false;
480 bool HasChunks =
false;
481 for (
auto &C : Chunks) {
482 if (C.SpaceBefore || NeedsSpace)
485 case ChunkKind::PlainText:
486 OS << renderText(C.Contents, !HasChunks,
true);
488 case ChunkKind::InlineCode:
489 OS << renderInlineBlock(C.Contents);
491 case ChunkKind::Bold:
492 OS << renderText(
"**" + C.Contents +
"**", !HasChunks,
495 case ChunkKind::Emphasized:
496 OS << renderText(
"*" + C.Contents +
"*", !HasChunks,
501 NeedsSpace = C.SpaceAfter;
508 bool NeedsSpace =
false;
509 bool HasChunks =
false;
510 for (
auto &C : Chunks) {
511 if (C.SpaceBefore || NeedsSpace)
514 case ChunkKind::PlainText:
515 OS << renderText(C.Contents, !HasChunks,
false);
517 case ChunkKind::InlineCode:
518 OS << renderInlineBlock(C.Contents);
520 case ChunkKind::Bold:
521 OS <<
"**" << renderText(C.Contents, !HasChunks,
false)
524 case ChunkKind::Emphasized:
525 OS <<
"*" << renderText(C.Contents, !HasChunks,
false)
530 NeedsSpace = C.SpaceAfter;
537 return std::make_unique<Paragraph>(*
this);
542llvm::StringRef Paragraph::chooseMarker(llvm::ArrayRef<llvm::StringRef> Options,
543 llvm::StringRef
Text)
const {
545 for (llvm::StringRef S : Options)
546 if (
Text.find_first_of(S) == llvm::StringRef::npos)
548 return Options.front();
551bool Paragraph::punctuationIndicatesLineBreak(llvm::StringRef Line)
const {
552 constexpr llvm::StringLiteral
Punctuation = R
"txt(.:,;!?)txt";
555 return !Line.empty() &&
Punctuation.contains(Line.back());
558bool Paragraph::isHardLineBreakIndicator(llvm::StringRef Rest)
const {
561 constexpr llvm::StringLiteral LinebreakIndicators = R
"txt(-*@>#`)txt";
563 Rest = Rest.ltrim(" \t");
567 if (LinebreakIndicators.contains(Rest.front()))
570 if (llvm::isDigit(Rest.front())) {
571 llvm::StringRef AfterDigit = Rest.drop_while(llvm::isDigit);
572 if (AfterDigit.starts_with(
".") || AfterDigit.starts_with(
")"))
578bool Paragraph::isHardLineBreakAfter(llvm::StringRef Line,
579 llvm::StringRef Rest)
const {
583 return Line.ends_with(
" ") || punctuationIndicatesLineBreak(Line) ||
584 isHardLineBreakIndicator(Rest);
588 bool NeedsSpace =
false;
589 std::string ConcatenatedText;
590 ConcatenatedText.reserve(EstimatedStringSize);
592 llvm::raw_string_ostream ConcatenatedOS(ConcatenatedText);
594 for (
auto &C : Chunks) {
596 if (C.Kind == ChunkKind::PlainText) {
597 if (C.SpaceBefore || NeedsSpace)
598 ConcatenatedOS <<
' ';
600 ConcatenatedOS << C.Contents;
601 NeedsSpace = llvm::isSpace(C.Contents.back()) || C.SpaceAfter;
605 if (C.SpaceBefore || NeedsSpace)
606 ConcatenatedOS <<
' ';
607 llvm::StringRef Marker =
"";
608 if (C.Preserve && C.Kind == ChunkKind::InlineCode)
609 Marker = chooseMarker({
"`",
"'",
"\""}, C.Contents);
610 else if (C.Kind == ChunkKind::Bold)
612 else if (C.Kind == ChunkKind::Emphasized)
614 ConcatenatedOS << Marker << C.Contents << Marker;
615 NeedsSpace = C.SpaceAfter;
632 llvm::StringRef Line, Rest;
634 for (std::tie(Line, Rest) =
635 llvm::StringRef(ConcatenatedText).trim().split(
'\n');
636 !(Line.empty() && Rest.empty());
637 std::tie(Line, Rest) = Rest.split(
'\n')) {
656 if (isHardLineBreakAfter(Line, Rest))
658 else if (!Rest.empty())
669BulletList::BulletList() =
default;
670BulletList::~BulletList() =
default;
672void BulletList::renderEscapedMarkdown(llvm::raw_ostream &OS)
const {
673 for (
auto &D : Items) {
674 std::string M = D.asEscapedMarkdown();
683void BulletList::renderMarkdown(llvm::raw_ostream &OS)
const {
684 for (
auto &D : Items) {
685 std::string M = D.asMarkdown();
694void BulletList::renderPlainText(llvm::raw_ostream &OS)
const {
695 for (
auto &D : Items) {
705 Chunks.back().SpaceAfter =
true;
709Paragraph &Paragraph::appendChunk(llvm::StringRef Contents, ChunkKind K) {
710 if (Contents.empty())
712 Chunks.emplace_back();
713 Chunk &
C = Chunks.back();
714 C.Contents = Contents;
717 EstimatedStringSize += Contents.size();
722 if (!Chunks.empty() && Chunks.back().Kind == ChunkKind::PlainText) {
723 Chunks.back().Contents += std::move(
Text);
727 return appendChunk(std::move(
Text), ChunkKind::PlainText);
732 ChunkKind::Emphasized);
739Paragraph &Paragraph::appendCode(llvm::StringRef Code,
bool Preserve) {
741 !Chunks.empty() && Chunks.back().Kind == ChunkKind::InlineCode;
745 EstimatedStringSize += Norm.size();
746 Chunks.emplace_back();
747 Chunk &C = Chunks.back();
748 C.Contents = std::move(Norm);
749 C.Kind = ChunkKind::InlineCode;
750 C.Preserve = Preserve;
752 C.SpaceBefore = AdjacentCode;
757std::unique_ptr<Block> BulletList::clone()
const {
758 return std::make_unique<BulletList>(*
this);
761class Document &BulletList::addItem() {
762 Items.emplace_back();
766Document &Document::operator=(
const Document &Other) {
768 for (
const auto &C : Other.Children)
773void Document::append(Document Other) {
774 std::move(Other.Children.begin(), Other.Children.end(),
779 Children.push_back(std::make_unique<Paragraph>());
783void Document::addRuler() {
Children.push_back(std::make_unique<Ruler>()); }
785void Document::addCodeBlock(std::string Code, std::string Language) {
787 std::make_unique<CodeBlock>(std::move(Code), std::move(Language)));
790std::string Document::asEscapedMarkdown()
const {
794std::string Document::asMarkdown()
const {
798std::string Document::asPlainText()
const {
802BulletList &Document::addBulletList() {
803 Children.emplace_back(std::make_unique<BulletList>());
804 return *
static_cast<BulletList *
>(
Children.back().get());
807Paragraph &Document::addHeading(
size_t Level) {
809 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)