1 //===-- MDGenerator.cpp - Markdown Generator --------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "Generators.h"
10 #include "Representation.h"
11 #include "llvm/ADT/StringRef.h"
12 #include "llvm/Support/FileSystem.h"
13 #include "llvm/Support/Path.h"
21 // Markdown generation
23 static std::string
genItalic(const Twine
&Text
) {
24 return "*" + Text
.str() + "*";
27 static std::string
genEmphasis(const Twine
&Text
) {
28 return "**" + Text
.str() + "**";
32 genReferenceList(const llvm::SmallVectorImpl
<Reference
> &Refs
) {
34 llvm::raw_string_ostream
Stream(Buffer
);
35 for (const auto &R
: Refs
) {
36 if (&R
!= Refs
.begin())
43 static void writeLine(const Twine
&Text
, raw_ostream
&OS
) {
47 static void writeNewLine(raw_ostream
&OS
) { OS
<< "\n\n"; }
49 static void writeHeader(const Twine
&Text
, unsigned int Num
, raw_ostream
&OS
) {
50 OS
<< std::string(Num
, '#') + " " + Text
<< "\n\n";
53 static void writeFileDefinition(const ClangDocContext
&CDCtx
, const Location
&L
,
56 if (!CDCtx
.RepositoryUrl
) {
57 OS
<< "*Defined at " << L
.Filename
<< "#" << std::to_string(L
.LineNumber
)
60 OS
<< "*Defined at [" << L
.Filename
<< "#" << std::to_string(L
.LineNumber
)
61 << "](" << StringRef
{*CDCtx
.RepositoryUrl
}
62 << llvm::sys::path::relative_path(L
.Filename
) << "#"
63 << std::to_string(L
.LineNumber
) << ")"
69 static void writeDescription(const CommentInfo
&I
, raw_ostream
&OS
) {
70 if (I
.Kind
== "FullComment") {
71 for (const auto &Child
: I
.Children
)
72 writeDescription(*Child
, OS
);
73 } else if (I
.Kind
== "ParagraphComment") {
74 for (const auto &Child
: I
.Children
)
75 writeDescription(*Child
, OS
);
77 } else if (I
.Kind
== "BlockCommandComment") {
78 OS
<< genEmphasis(I
.Name
);
79 for (const auto &Child
: I
.Children
)
80 writeDescription(*Child
, OS
);
81 } else if (I
.Kind
== "InlineCommandComment") {
82 OS
<< genEmphasis(I
.Name
) << " " << I
.Text
;
83 } else if (I
.Kind
== "ParamCommandComment") {
84 std::string Direction
= I
.Explicit
? (" " + I
.Direction
).str() : "";
85 OS
<< genEmphasis(I
.ParamName
) << I
.Text
<< Direction
;
86 for (const auto &Child
: I
.Children
)
87 writeDescription(*Child
, OS
);
88 } else if (I
.Kind
== "TParamCommandComment") {
89 std::string Direction
= I
.Explicit
? (" " + I
.Direction
).str() : "";
90 OS
<< genEmphasis(I
.ParamName
) << I
.Text
<< Direction
;
91 for (const auto &Child
: I
.Children
)
92 writeDescription(*Child
, OS
);
93 } else if (I
.Kind
== "VerbatimBlockComment") {
94 for (const auto &Child
: I
.Children
)
95 writeDescription(*Child
, OS
);
96 } else if (I
.Kind
== "VerbatimBlockLineComment") {
99 } else if (I
.Kind
== "VerbatimLineComment") {
102 } else if (I
.Kind
== "HTMLStartTagComment") {
103 if (I
.AttrKeys
.size() != I
.AttrValues
.size())
106 llvm::raw_string_ostream
Attrs(Buffer
);
107 for (unsigned Idx
= 0; Idx
< I
.AttrKeys
.size(); ++Idx
)
108 Attrs
<< " \"" << I
.AttrKeys
[Idx
] << "=" << I
.AttrValues
[Idx
] << "\"";
110 std::string CloseTag
= I
.SelfClosing
? "/>" : ">";
111 writeLine("<" + I
.Name
+ Attrs
.str() + CloseTag
, OS
);
112 } else if (I
.Kind
== "HTMLEndTagComment") {
113 writeLine("</" + I
.Name
+ ">", OS
);
114 } else if (I
.Kind
== "TextComment") {
117 OS
<< "Unknown comment kind: " << I
.Kind
<< ".\n\n";
121 static void writeNameLink(const StringRef
&CurrentPath
, const Reference
&R
,
122 llvm::raw_ostream
&OS
) {
123 llvm::SmallString
<64> Path
= R
.getRelativeFilePath(CurrentPath
);
124 // Paths in Markdown use POSIX separators.
125 llvm::sys::path::native(Path
, llvm::sys::path::Style::posix
);
126 llvm::sys::path::append(Path
, llvm::sys::path::Style::posix
,
127 R
.getFileBaseName() + ".md");
128 OS
<< "[" << R
.Name
<< "](" << Path
<< ")";
131 static void genMarkdown(const ClangDocContext
&CDCtx
, const EnumInfo
&I
,
132 llvm::raw_ostream
&OS
) {
134 writeLine("| enum class " + I
.Name
+ " |", OS
);
136 writeLine("| enum " + I
.Name
+ " |", OS
);
140 llvm::raw_string_ostream
Members(Buffer
);
141 if (!I
.Members
.empty())
142 for (const auto &N
: I
.Members
)
143 Members
<< "| " << N
.Name
<< " |\n";
144 writeLine(Members
.str(), OS
);
146 writeFileDefinition(CDCtx
, *I
.DefLoc
, OS
);
148 for (const auto &C
: I
.Description
)
149 writeDescription(C
, OS
);
152 static void genMarkdown(const ClangDocContext
&CDCtx
, const FunctionInfo
&I
,
153 llvm::raw_ostream
&OS
) {
155 llvm::raw_string_ostream
Stream(Buffer
);
157 for (const auto &N
: I
.Params
) {
160 Stream
<< N
.Type
.Name
+ " " + N
.Name
;
163 writeHeader(I
.Name
, 3, OS
);
164 std::string Access
= getAccessSpelling(I
.Access
).str();
166 writeLine(genItalic(Access
+ " " + I
.ReturnType
.Type
.Name
+ " " + I
.Name
+
167 "(" + Stream
.str() + ")"),
170 writeLine(genItalic(I
.ReturnType
.Type
.Name
+ " " + I
.Name
+ "(" +
174 writeFileDefinition(CDCtx
, *I
.DefLoc
, OS
);
176 for (const auto &C
: I
.Description
)
177 writeDescription(C
, OS
);
180 static void genMarkdown(const ClangDocContext
&CDCtx
, const NamespaceInfo
&I
,
181 llvm::raw_ostream
&OS
) {
183 writeHeader("Global Namespace", 1, OS
);
185 writeHeader("namespace " + I
.Name
, 1, OS
);
188 if (!I
.Description
.empty()) {
189 for (const auto &C
: I
.Description
)
190 writeDescription(C
, OS
);
194 llvm::SmallString
<64> BasePath
= I
.getRelativeFilePath("");
196 if (!I
.Children
.Namespaces
.empty()) {
197 writeHeader("Namespaces", 2, OS
);
198 for (const auto &R
: I
.Children
.Namespaces
) {
200 writeNameLink(BasePath
, R
, OS
);
206 if (!I
.Children
.Records
.empty()) {
207 writeHeader("Records", 2, OS
);
208 for (const auto &R
: I
.Children
.Records
) {
210 writeNameLink(BasePath
, R
, OS
);
216 if (!I
.Children
.Functions
.empty()) {
217 writeHeader("Functions", 2, OS
);
218 for (const auto &F
: I
.Children
.Functions
)
219 genMarkdown(CDCtx
, F
, OS
);
222 if (!I
.Children
.Enums
.empty()) {
223 writeHeader("Enums", 2, OS
);
224 for (const auto &E
: I
.Children
.Enums
)
225 genMarkdown(CDCtx
, E
, OS
);
230 static void genMarkdown(const ClangDocContext
&CDCtx
, const RecordInfo
&I
,
231 llvm::raw_ostream
&OS
) {
232 writeHeader(getTagType(I
.TagType
) + " " + I
.Name
, 1, OS
);
234 writeFileDefinition(CDCtx
, *I
.DefLoc
, OS
);
236 if (!I
.Description
.empty()) {
237 for (const auto &C
: I
.Description
)
238 writeDescription(C
, OS
);
242 std::string Parents
= genReferenceList(I
.Parents
);
243 std::string VParents
= genReferenceList(I
.VirtualParents
);
244 if (!Parents
.empty() || !VParents
.empty()) {
246 writeLine("Inherits from " + VParents
, OS
);
247 else if (VParents
.empty())
248 writeLine("Inherits from " + Parents
, OS
);
250 writeLine("Inherits from " + Parents
+ ", " + VParents
, OS
);
254 if (!I
.Members
.empty()) {
255 writeHeader("Members", 2, OS
);
256 for (const auto &Member
: I
.Members
) {
257 std::string Access
= getAccessSpelling(Member
.Access
).str();
259 writeLine(Access
+ " " + Member
.Type
.Name
+ " " + Member
.Name
, OS
);
261 writeLine(Member
.Type
.Name
+ " " + Member
.Name
, OS
);
266 if (!I
.Children
.Records
.empty()) {
267 writeHeader("Records", 2, OS
);
268 for (const auto &R
: I
.Children
.Records
)
269 writeLine(R
.Name
, OS
);
272 if (!I
.Children
.Functions
.empty()) {
273 writeHeader("Functions", 2, OS
);
274 for (const auto &F
: I
.Children
.Functions
)
275 genMarkdown(CDCtx
, F
, OS
);
278 if (!I
.Children
.Enums
.empty()) {
279 writeHeader("Enums", 2, OS
);
280 for (const auto &E
: I
.Children
.Enums
)
281 genMarkdown(CDCtx
, E
, OS
);
286 static void genMarkdown(const ClangDocContext
&CDCtx
, const TypedefInfo
&I
,
287 llvm::raw_ostream
&OS
) {
288 // TODO support typedefs in markdown.
291 static void serializeReference(llvm::raw_fd_ostream
&OS
, Index
&I
, int Level
) {
292 // Write out the heading level starting at ##
293 OS
<< "##" << std::string(Level
, '#') << " ";
294 writeNameLink("", I
, OS
);
298 static llvm::Error
serializeIndex(ClangDocContext
&CDCtx
) {
299 std::error_code FileErr
;
300 llvm::SmallString
<128> FilePath
;
301 llvm::sys::path::native(CDCtx
.OutDirectory
, FilePath
);
302 llvm::sys::path::append(FilePath
, "all_files.md");
303 llvm::raw_fd_ostream
OS(FilePath
, FileErr
, llvm::sys::fs::OF_None
);
305 return llvm::createStringError(llvm::inconvertibleErrorCode(),
306 "error creating index file: " +
311 if (!CDCtx
.ProjectName
.empty())
312 OS
<< " for " << CDCtx
.ProjectName
;
315 for (auto C
: CDCtx
.Idx
.Children
)
316 serializeReference(OS
, C
, 0);
318 return llvm::Error::success();
321 static llvm::Error
genIndex(ClangDocContext
&CDCtx
) {
322 std::error_code FileErr
;
323 llvm::SmallString
<128> FilePath
;
324 llvm::sys::path::native(CDCtx
.OutDirectory
, FilePath
);
325 llvm::sys::path::append(FilePath
, "index.md");
326 llvm::raw_fd_ostream
OS(FilePath
, FileErr
, llvm::sys::fs::OF_None
);
328 return llvm::createStringError(llvm::inconvertibleErrorCode(),
329 "error creating index file: " +
332 OS
<< "# " << CDCtx
.ProjectName
<< " C/C++ Reference\n\n";
333 for (auto C
: CDCtx
.Idx
.Children
) {
334 if (!C
.Children
.empty()) {
337 case InfoType::IT_namespace
:
340 case InfoType::IT_record
:
343 case InfoType::IT_enum
:
346 case InfoType::IT_function
:
349 case InfoType::IT_typedef
:
352 case InfoType::IT_default
:
355 OS
<< "* " << Type
<< ": [" << C
.Name
<< "](";
358 OS
<< C
.Name
<< ")\n";
361 return llvm::Error::success();
364 /// Generator for Markdown documentation.
365 class MDGenerator
: public Generator
{
367 static const char *Format
;
369 llvm::Error
generateDocs(StringRef RootDir
,
370 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
371 const ClangDocContext
&CDCtx
) override
;
372 llvm::Error
createResources(ClangDocContext
&CDCtx
) override
;
373 llvm::Error
generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
374 const ClangDocContext
&CDCtx
) override
;
377 const char *MDGenerator::Format
= "md";
380 MDGenerator::generateDocs(StringRef RootDir
,
381 llvm::StringMap
<std::unique_ptr
<doc::Info
>> Infos
,
382 const ClangDocContext
&CDCtx
) {
383 // Track which directories we already tried to create.
384 llvm::StringSet
<> CreatedDirs
;
386 // Collect all output by file name and create the necessary directories.
387 llvm::StringMap
<std::vector
<doc::Info
*>> FileToInfos
;
388 for (const auto &Group
: Infos
) {
389 doc::Info
*Info
= Group
.getValue().get();
391 llvm::SmallString
<128> Path
;
392 llvm::sys::path::native(RootDir
, Path
);
393 llvm::sys::path::append(Path
, Info
->getRelativeFilePath(""));
394 if (!CreatedDirs
.contains(Path
)) {
395 if (std::error_code Err
= llvm::sys::fs::create_directories(Path
);
396 Err
!= std::error_code()) {
397 return llvm::createStringError(Err
, "Failed to create directory '%s'.",
400 CreatedDirs
.insert(Path
);
403 llvm::sys::path::append(Path
, Info
->getFileBaseName() + ".md");
404 FileToInfos
[Path
].push_back(Info
);
407 for (const auto &Group
: FileToInfos
) {
408 std::error_code FileErr
;
409 llvm::raw_fd_ostream
InfoOS(Group
.getKey(), FileErr
,
410 llvm::sys::fs::OF_None
);
412 return llvm::createStringError(FileErr
, "Error opening file '%s'",
413 Group
.getKey().str().c_str());
416 for (const auto &Info
: Group
.getValue()) {
417 if (llvm::Error Err
= generateDocForInfo(Info
, InfoOS
, CDCtx
)) {
423 return llvm::Error::success();
426 llvm::Error
MDGenerator::generateDocForInfo(Info
*I
, llvm::raw_ostream
&OS
,
427 const ClangDocContext
&CDCtx
) {
429 case InfoType::IT_namespace
:
430 genMarkdown(CDCtx
, *static_cast<clang::doc::NamespaceInfo
*>(I
), OS
);
432 case InfoType::IT_record
:
433 genMarkdown(CDCtx
, *static_cast<clang::doc::RecordInfo
*>(I
), OS
);
435 case InfoType::IT_enum
:
436 genMarkdown(CDCtx
, *static_cast<clang::doc::EnumInfo
*>(I
), OS
);
438 case InfoType::IT_function
:
439 genMarkdown(CDCtx
, *static_cast<clang::doc::FunctionInfo
*>(I
), OS
);
441 case InfoType::IT_typedef
:
442 genMarkdown(CDCtx
, *static_cast<clang::doc::TypedefInfo
*>(I
), OS
);
444 case InfoType::IT_default
:
445 return createStringError(llvm::inconvertibleErrorCode(),
446 "unexpected InfoType");
448 return llvm::Error::success();
451 llvm::Error
MDGenerator::createResources(ClangDocContext
&CDCtx
) {
452 // Write an all_files.md
453 auto Err
= serializeIndex(CDCtx
);
457 // Generate the index page.
458 Err
= genIndex(CDCtx
);
462 return llvm::Error::success();
465 static GeneratorRegistry::Add
<MDGenerator
> MD(MDGenerator::Format
,
466 "Generator for MD output.");
468 // This anchor is used to force the linker to link in the generated object
469 // file and thus register the generator.
470 volatile int MDGeneratorAnchorSource
= 0;