1 //===-- MDGenerator.cpp - Markdown Generator --------------------*- C++ -*-===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8
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"
14 #include <string>
15
16 using namespace llvm;
17
18 namespace clang {
19 namespace doc {
20
21 // Markdown generation
22
genItalic(const Twine & Text)23 static std::string genItalic(const Twine &Text) {
24 return "*" + Text.str() + "*";
25 }
26
genEmphasis(const Twine & Text)27 static std::string genEmphasis(const Twine &Text) {
28 return "**" + Text.str() + "**";
29 }
30
31 static std::string
genReferenceList(const llvm::SmallVectorImpl<Reference> & Refs)32 genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {
33 std::string Buffer;
34 llvm::raw_string_ostream Stream(Buffer);
35 for (const auto &R : Refs) {
36 if (&R != Refs.begin())
37 Stream << ", ";
38 Stream << R.Name;
39 }
40 return Stream.str();
41 }
42
writeLine(const Twine & Text,raw_ostream & OS)43 static void writeLine(const Twine &Text, raw_ostream &OS) {
44 OS << Text << "\n\n";
45 }
46
writeNewLine(raw_ostream & OS)47 static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
48
writeHeader(const Twine & Text,unsigned int Num,raw_ostream & OS)49 static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
50 OS << std::string(Num, '#') + " " + Text << "\n\n";
51 }
52
writeFileDefinition(const ClangDocContext & CDCtx,const Location & L,raw_ostream & OS)53 static void writeFileDefinition(const ClangDocContext &CDCtx, const Location &L,
54 raw_ostream &OS) {
55
56 if (!CDCtx.RepositoryUrl) {
57 OS << "*Defined at " << L.Filename << "#" << std::to_string(L.LineNumber)
58 << "*";
59 } else {
60 OS << "*Defined at [" << L.Filename << "#" << std::to_string(L.LineNumber)
61 << "](" << StringRef{CDCtx.RepositoryUrl.getValue()}
62 << llvm::sys::path::relative_path(L.Filename) << "#"
63 << std::to_string(L.LineNumber) << ")"
64 << "*";
65 }
66 OS << "\n\n";
67 }
68
writeDescription(const CommentInfo & I,raw_ostream & OS)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);
76 writeNewLine(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 << "\n\n";
86 } else if (I.Kind == "TParamCommandComment") {
87 std::string Direction = I.Explicit ? (" " + I.Direction).str() : "";
88 OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n";
89 } else if (I.Kind == "VerbatimBlockComment") {
90 for (const auto &Child : I.Children)
91 writeDescription(*Child, OS);
92 } else if (I.Kind == "VerbatimBlockLineComment") {
93 OS << I.Text;
94 writeNewLine(OS);
95 } else if (I.Kind == "VerbatimLineComment") {
96 OS << I.Text;
97 writeNewLine(OS);
98 } else if (I.Kind == "HTMLStartTagComment") {
99 if (I.AttrKeys.size() != I.AttrValues.size())
100 return;
101 std::string Buffer;
102 llvm::raw_string_ostream Attrs(Buffer);
103 for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx)
104 Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"";
105
106 std::string CloseTag = I.SelfClosing ? "/>" : ">";
107 writeLine("<" + I.Name + Attrs.str() + CloseTag, OS);
108 } else if (I.Kind == "HTMLEndTagComment") {
109 writeLine("</" + I.Name + ">", OS);
110 } else if (I.Kind == "TextComment") {
111 OS << I.Text;
112 } else {
113 OS << "Unknown comment kind: " << I.Kind << ".\n\n";
114 }
115 }
116
writeNameLink(const StringRef & CurrentPath,const Reference & R,llvm::raw_ostream & OS)117 static void writeNameLink(const StringRef &CurrentPath, const Reference &R,
118 llvm::raw_ostream &OS) {
119 llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath);
120 // Paths in Markdown use POSIX separators.
121 llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
122 llvm::sys::path::append(Path, llvm::sys::path::Style::posix,
123 R.getFileBaseName() + ".md");
124 OS << "[" << R.Name << "](" << Path << ")";
125 }
126
genMarkdown(const ClangDocContext & CDCtx,const EnumInfo & I,llvm::raw_ostream & OS)127 static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I,
128 llvm::raw_ostream &OS) {
129 if (I.Scoped)
130 writeLine("| enum class " + I.Name + " |", OS);
131 else
132 writeLine("| enum " + I.Name + " |", OS);
133 writeLine("--", OS);
134
135 std::string Buffer;
136 llvm::raw_string_ostream Members(Buffer);
137 if (!I.Members.empty())
138 for (const auto &N : I.Members)
139 Members << "| " << N << " |\n";
140 writeLine(Members.str(), OS);
141 if (I.DefLoc)
142 writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
143
144 for (const auto &C : I.Description)
145 writeDescription(C, OS);
146 }
147
genMarkdown(const ClangDocContext & CDCtx,const FunctionInfo & I,llvm::raw_ostream & OS)148 static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I,
149 llvm::raw_ostream &OS) {
150 std::string Buffer;
151 llvm::raw_string_ostream Stream(Buffer);
152 bool First = true;
153 for (const auto &N : I.Params) {
154 if (!First)
155 Stream << ", ";
156 Stream << N.Type.Name + " " + N.Name;
157 First = false;
158 }
159 writeHeader(I.Name, 3, OS);
160 std::string Access = getAccessSpelling(I.Access).str();
161 if (Access != "")
162 writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name +
163 "(" + Stream.str() + ")"),
164 OS);
165 else
166 writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" +
167 Stream.str() + ")"),
168 OS);
169 if (I.DefLoc)
170 writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
171
172 for (const auto &C : I.Description)
173 writeDescription(C, OS);
174 }
175
genMarkdown(const ClangDocContext & CDCtx,const NamespaceInfo & I,llvm::raw_ostream & OS)176 static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I,
177 llvm::raw_ostream &OS) {
178 if (I.Name == "")
179 writeHeader("Global Namespace", 1, OS);
180 else
181 writeHeader("namespace " + I.Name, 1, OS);
182 writeNewLine(OS);
183
184 if (!I.Description.empty()) {
185 for (const auto &C : I.Description)
186 writeDescription(C, OS);
187 writeNewLine(OS);
188 }
189
190 llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
191
192 if (!I.ChildNamespaces.empty()) {
193 writeHeader("Namespaces", 2, OS);
194 for (const auto &R : I.ChildNamespaces) {
195 OS << "* ";
196 writeNameLink(BasePath, R, OS);
197 OS << "\n";
198 }
199 writeNewLine(OS);
200 }
201
202 if (!I.ChildRecords.empty()) {
203 writeHeader("Records", 2, OS);
204 for (const auto &R : I.ChildRecords) {
205 OS << "* ";
206 writeNameLink(BasePath, R, OS);
207 OS << "\n";
208 }
209 writeNewLine(OS);
210 }
211
212 if (!I.ChildFunctions.empty()) {
213 writeHeader("Functions", 2, OS);
214 for (const auto &F : I.ChildFunctions)
215 genMarkdown(CDCtx, F, OS);
216 writeNewLine(OS);
217 }
218 if (!I.ChildEnums.empty()) {
219 writeHeader("Enums", 2, OS);
220 for (const auto &E : I.ChildEnums)
221 genMarkdown(CDCtx, E, OS);
222 writeNewLine(OS);
223 }
224 }
225
genMarkdown(const ClangDocContext & CDCtx,const RecordInfo & I,llvm::raw_ostream & OS)226 static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I,
227 llvm::raw_ostream &OS) {
228 writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
229 if (I.DefLoc)
230 writeFileDefinition(CDCtx, I.DefLoc.getValue(), OS);
231
232 if (!I.Description.empty()) {
233 for (const auto &C : I.Description)
234 writeDescription(C, OS);
235 writeNewLine(OS);
236 }
237
238 std::string Parents = genReferenceList(I.Parents);
239 std::string VParents = genReferenceList(I.VirtualParents);
240 if (!Parents.empty() || !VParents.empty()) {
241 if (Parents.empty())
242 writeLine("Inherits from " + VParents, OS);
243 else if (VParents.empty())
244 writeLine("Inherits from " + Parents, OS);
245 else
246 writeLine("Inherits from " + Parents + ", " + VParents, OS);
247 writeNewLine(OS);
248 }
249
250 if (!I.Members.empty()) {
251 writeHeader("Members", 2, OS);
252 for (const auto &Member : I.Members) {
253 std::string Access = getAccessSpelling(Member.Access).str();
254 if (Access != "")
255 writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS);
256 else
257 writeLine(Member.Type.Name + " " + Member.Name, OS);
258 }
259 writeNewLine(OS);
260 }
261
262 if (!I.ChildRecords.empty()) {
263 writeHeader("Records", 2, OS);
264 for (const auto &R : I.ChildRecords)
265 writeLine(R.Name, OS);
266 writeNewLine(OS);
267 }
268 if (!I.ChildFunctions.empty()) {
269 writeHeader("Functions", 2, OS);
270 for (const auto &F : I.ChildFunctions)
271 genMarkdown(CDCtx, F, OS);
272 writeNewLine(OS);
273 }
274 if (!I.ChildEnums.empty()) {
275 writeHeader("Enums", 2, OS);
276 for (const auto &E : I.ChildEnums)
277 genMarkdown(CDCtx, E, OS);
278 writeNewLine(OS);
279 }
280 }
281
serializeReference(llvm::raw_fd_ostream & OS,Index & I,int Level)282 static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {
283 // Write out the heading level starting at ##
284 OS << "##" << std::string(Level, '#') << " ";
285 writeNameLink("", I, OS);
286 OS << "\n";
287 }
288
serializeIndex(ClangDocContext & CDCtx)289 static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
290 std::error_code FileErr;
291 llvm::SmallString<128> FilePath;
292 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
293 llvm::sys::path::append(FilePath, "all_files.md");
294 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
295 if (FileErr)
296 return llvm::createStringError(llvm::inconvertibleErrorCode(),
297 "error creating index file: " +
298 FileErr.message());
299
300 CDCtx.Idx.sort();
301 OS << "# All Files";
302 if (!CDCtx.ProjectName.empty())
303 OS << " for " << CDCtx.ProjectName;
304 OS << "\n\n";
305
306 for (auto C : CDCtx.Idx.Children)
307 serializeReference(OS, C, 0);
308
309 return llvm::Error::success();
310 }
311
genIndex(ClangDocContext & CDCtx)312 static llvm::Error genIndex(ClangDocContext &CDCtx) {
313 std::error_code FileErr;
314 llvm::SmallString<128> FilePath;
315 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
316 llvm::sys::path::append(FilePath, "index.md");
317 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
318 if (FileErr)
319 return llvm::createStringError(llvm::inconvertibleErrorCode(),
320 "error creating index file: " +
321 FileErr.message());
322 CDCtx.Idx.sort();
323 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
324 for (auto C : CDCtx.Idx.Children) {
325 if (!C.Children.empty()) {
326 const char *Type;
327 switch (C.RefType) {
328 case InfoType::IT_namespace:
329 Type = "Namespace";
330 break;
331 case InfoType::IT_record:
332 Type = "Type";
333 break;
334 case InfoType::IT_enum:
335 Type = "Enum";
336 break;
337 case InfoType::IT_function:
338 Type = "Function";
339 break;
340 case InfoType::IT_default:
341 Type = "Other";
342 }
343 OS << "* " << Type << ": [" << C.Name << "](";
344 if (!C.Path.empty())
345 OS << C.Path << "/";
346 OS << C.Name << ")\n";
347 }
348 }
349 return llvm::Error::success();
350 }
351 /// Generator for Markdown documentation.
352 class MDGenerator : public Generator {
353 public:
354 static const char *Format;
355
356 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
357 const ClangDocContext &CDCtx) override;
358 llvm::Error createResources(ClangDocContext &CDCtx) override;
359 };
360
361 const char *MDGenerator::Format = "md";
362
generateDocForInfo(Info * I,llvm::raw_ostream & OS,const ClangDocContext & CDCtx)363 llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
364 const ClangDocContext &CDCtx) {
365 switch (I->IT) {
366 case InfoType::IT_namespace:
367 genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
368 break;
369 case InfoType::IT_record:
370 genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
371 break;
372 case InfoType::IT_enum:
373 genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
374 break;
375 case InfoType::IT_function:
376 genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
377 break;
378 case InfoType::IT_default:
379 return createStringError(llvm::inconvertibleErrorCode(),
380 "unexpected InfoType");
381 }
382 return llvm::Error::success();
383 }
384
createResources(ClangDocContext & CDCtx)385 llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
386 // Write an all_files.md
387 auto Err = serializeIndex(CDCtx);
388 if (Err)
389 return Err;
390
391 // Generate the index page.
392 Err = genIndex(CDCtx);
393 if (Err)
394 return Err;
395
396 return llvm::Error::success();
397 }
398
399 static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
400 "Generator for MD output.");
401
402 // This anchor is used to force the linker to link in the generated object
403 // file and thus register the generator.
404 volatile int MDGeneratorAnchorSource = 0;
405
406 } // namespace doc
407 } // namespace clang
408