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 llvm::StringRef ParagraphText)
const {
480 llvm::StringRef Line, Rest;
482 for (std::tie(Line, Rest) = ParagraphText.ltrim(
"\n").rtrim().split(
'\n');
483 !(Line.empty() && Rest.empty());
484 std::tie(Line, Rest) = Rest.split(
'\n')) {
507 bool NeedsSpace =
false;
508 bool HasChunks =
false;
509 std::string ParagraphText;
510 ParagraphText.reserve(EstimatedStringSize);
511 llvm::raw_string_ostream ParagraphTextOS(ParagraphText);
512 for (
auto &C : Chunks) {
513 if (C.SpaceBefore || NeedsSpace)
514 ParagraphTextOS <<
" ";
516 case ChunkKind::PlainText:
517 ParagraphTextOS << renderText(C.Contents, !HasChunks,
520 case ChunkKind::InlineCode:
521 ParagraphTextOS << renderInlineBlock(C.Contents);
523 case ChunkKind::Bold:
524 ParagraphTextOS << renderText(
"**" + C.Contents +
"**", !HasChunks,
527 case ChunkKind::Emphasized:
528 ParagraphTextOS << renderText(
"*" + C.Contents +
"*", !HasChunks,
533 NeedsSpace = C.SpaceAfter;
540 renderNewlinesPlaintext(OS, ParagraphText,
true);
547 bool NeedsSpace =
false;
548 bool HasChunks =
false;
549 bool TextEndsWithNewline =
false;
550 std::string ParagraphText;
551 ParagraphText.reserve(EstimatedStringSize);
552 llvm::raw_string_ostream ParagraphTextOS(ParagraphText);
553 for (
auto &C : Chunks) {
555 if (TextEndsWithNewline) {
556 ParagraphTextOS <<
"\n";
557 TextEndsWithNewline =
false;
560 if (C.SpaceBefore || NeedsSpace)
561 ParagraphTextOS <<
" ";
564 case ChunkKind::PlainText:
565 ParagraphTextOS << renderText(C.Contents, !HasChunks,
570 TextEndsWithNewline = llvm::StringRef(C.Contents).ends_with(
"\n");
572 case ChunkKind::InlineCode:
573 ParagraphTextOS << renderInlineBlock(C.Contents);
575 case ChunkKind::Bold:
576 ParagraphTextOS <<
"**"
577 << renderText(C.Contents, !HasChunks,
581 case ChunkKind::Emphasized:
582 ParagraphTextOS <<
"*"
583 << renderText(C.Contents, !HasChunks,
589 NeedsSpace = C.SpaceAfter;
599 return std::make_unique<Paragraph>(*
this);
602llvm::StringRef Paragraph::chooseMarker(llvm::ArrayRef<llvm::StringRef> Options,
603 llvm::StringRef
Text)
const {
605 for (llvm::StringRef S : Options)
606 if (
Text.find_first_of(S) == llvm::StringRef::npos)
608 return Options.front();
611bool Paragraph::punctuationIndicatesLineBreak(llvm::StringRef Line,
612 bool IsMarkdown)
const {
613 constexpr llvm::StringLiteral
Punctuation = R
"txt(.:,;!?)txt";
615 if (!IsMarkdown && Line.ends_with(
" "))
619 return !Line.empty() &&
Punctuation.contains(Line.back());
623 bool IsMarkdown)
const {
627 constexpr llvm::StringLiteral LinebreakIndicatorsPlainText =
631 constexpr llvm::StringLiteral LinebreakIndicatorsMarkdown = R
"txt(@\)txt";
633 Rest = Rest.ltrim(" \t");
638 return LinebreakIndicatorsMarkdown.contains(Rest.front());
640 if (LinebreakIndicatorsPlainText.contains(Rest.front()))
643 if (llvm::isDigit(Rest.front())) {
644 llvm::StringRef AfterDigit = Rest.drop_while(llvm::isDigit);
645 if (AfterDigit.starts_with(
".") || AfterDigit.starts_with(
")"))
652 bool IsMarkdown)
const {
654 return punctuationIndicatesLineBreak(Line, IsMarkdown) ||
658void Paragraph::renderNewlinesPlaintext(llvm::raw_ostream &OS,
659 llvm::StringRef ParagraphText,
660 bool UseMarkdownLinebreaks)
const {
661 llvm::StringRef Line, Rest;
663 for (std::tie(Line, Rest) = ParagraphText.trim().split(
'\n');
664 !(Line.empty() && Rest.empty());
665 std::tie(Line, Rest) = Rest.split(
'\n')) {
690 if (UseMarkdownLinebreaks)
704 bool NeedsSpace =
false;
705 std::string ParagraphText;
706 ParagraphText.reserve(EstimatedStringSize);
708 llvm::raw_string_ostream ParagraphTextOS(ParagraphText);
710 for (
auto &C : Chunks) {
712 if (C.Kind == ChunkKind::PlainText) {
713 if (C.SpaceBefore || NeedsSpace)
714 ParagraphTextOS <<
' ';
716 ParagraphTextOS << C.Contents;
717 NeedsSpace = llvm::isSpace(C.Contents.back()) || C.SpaceAfter;
721 if (C.SpaceBefore || NeedsSpace)
722 ParagraphTextOS <<
' ';
723 llvm::StringRef Marker =
"";
724 if (C.Preserve && C.Kind == ChunkKind::InlineCode)
725 Marker = chooseMarker({
"`",
"'",
"\""}, C.Contents);
726 else if (C.Kind == ChunkKind::Bold)
728 else if (C.Kind == ChunkKind::Emphasized)
730 ParagraphTextOS << Marker << C.Contents << Marker;
731 NeedsSpace = C.SpaceAfter;
734 renderNewlinesPlaintext(OS, ParagraphText,
false);
740BulletList::BulletList() =
default;
741BulletList::~BulletList() =
default;
743void BulletList::renderEscapedMarkdown(llvm::raw_ostream &OS)
const {
744 for (
auto &D : Items) {
745 std::string M = D.asEscapedMarkdown();
754void BulletList::renderMarkdown(llvm::raw_ostream &OS)
const {
755 for (
auto &D : Items) {
756 std::string M = D.asMarkdown();
765void BulletList::renderPlainText(llvm::raw_ostream &OS)
const {
766 for (
auto &D : Items) {
776 Chunks.back().SpaceAfter =
true;
780Paragraph &Paragraph::appendChunk(llvm::StringRef Contents, ChunkKind K) {
781 if (Contents.empty())
783 Chunks.emplace_back();
784 Chunk &
C = Chunks.back();
785 C.Contents = Contents;
788 EstimatedStringSize += Contents.size();
793 if (!Chunks.empty() && Chunks.back().Kind == ChunkKind::PlainText) {
794 Chunks.back().Contents += std::move(
Text);
798 return appendChunk(std::move(
Text), ChunkKind::PlainText);
803 ChunkKind::Emphasized);
810Paragraph &Paragraph::appendCode(llvm::StringRef Code,
bool Preserve) {
812 !Chunks.empty() && Chunks.back().Kind == ChunkKind::InlineCode;
816 EstimatedStringSize += Norm.size();
817 Chunks.emplace_back();
818 Chunk &C = Chunks.back();
819 C.Contents = std::move(Norm);
820 C.Kind = ChunkKind::InlineCode;
821 C.Preserve = Preserve;
823 C.SpaceBefore = AdjacentCode;
828std::unique_ptr<Block> BulletList::clone()
const {
829 return std::make_unique<BulletList>(*
this);
832class Document &BulletList::addItem() {
833 Items.emplace_back();
837Document &Document::operator=(
const Document &Other) {
839 for (
const auto &C : Other.Children)
844void Document::append(Document Other) {
845 std::move(Other.Children.begin(), Other.Children.end(),
850 Children.push_back(std::make_unique<Paragraph>());
854void Document::addRuler() {
Children.push_back(std::make_unique<Ruler>()); }
856void Document::addCodeBlock(std::string Code, std::string Language) {
858 std::make_unique<CodeBlock>(std::move(Code), std::move(Language)));
861std::string Document::asEscapedMarkdown()
const {
865std::string Document::asMarkdown()
const {
869std::string Document::asPlainText()
const {
873BulletList &Document::addBulletList() {
874 Children.emplace_back(std::make_unique<BulletList>());
875 return *
static_cast<BulletList *
>(
Children.back().get());
878Paragraph &Document::addHeading(
size_t Level) {
880 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
void renderNewlinesMarkdown(llvm::raw_ostream &OS, llvm::StringRef ParagraphText) const
Go through the contents line by line to handle the newlines and required spacing correctly for markdo...
std::unique_ptr< Block > clone() const override
bool isHardLineBreakAfter(llvm::StringRef Line, llvm::StringRef Rest, bool IsMarkdown) const
Checks if a hard line break should be added after the given line.
void renderMarkdown(llvm::raw_ostream &OS) const override
Paragraph & appendBoldText(llvm::StringRef Text)
Append bold text, this translates to the ** block in markdown.
bool isHardLineBreakIndicator(llvm::StringRef Rest, bool IsMarkdown) const
Append inline code, 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)