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/FormatVariadic.h" |
14 | #include "llvm/Support/Path.h" |
15 | #include "llvm/Support/raw_ostream.h" |
16 | #include <string> |
17 | |
18 | using namespace llvm; |
19 | |
20 | namespace clang { |
21 | namespace doc { |
22 | |
23 | // Markdown generation |
24 | |
25 | static std::string genItalic(const Twine &Text) { |
26 | return "*" + Text.str() + "*" ; |
27 | } |
28 | |
29 | static std::string genEmphasis(const Twine &Text) { |
30 | return "**" + Text.str() + "**" ; |
31 | } |
32 | |
33 | static std::string |
34 | genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) { |
35 | std::string Buffer; |
36 | llvm::raw_string_ostream Stream(Buffer); |
37 | for (const auto &R : Refs) { |
38 | if (&R != Refs.begin()) |
39 | Stream << ", " ; |
40 | Stream << R.Name; |
41 | } |
42 | return Stream.str(); |
43 | } |
44 | |
45 | static void writeLine(const Twine &Text, raw_ostream &OS) { |
46 | OS << Text << "\n\n" ; |
47 | } |
48 | |
49 | static void writeNewLine(raw_ostream &OS) { OS << "\n\n" ; } |
50 | |
51 | static void (const Twine &Text, unsigned int Num, raw_ostream &OS) { |
52 | OS << std::string(Num, '#') + " " + Text << "\n\n" ; |
53 | } |
54 | |
55 | static void writeSourceFileRef(const ClangDocContext &CDCtx, const Location &L, |
56 | raw_ostream &OS) { |
57 | |
58 | if (!CDCtx.RepositoryUrl) { |
59 | OS << "*Defined at " << L.Filename << "#" |
60 | << std::to_string(val: L.StartLineNumber) << "*" ; |
61 | } else { |
62 | |
63 | OS << formatv(Fmt: "*Defined at [#{0}{1}{2}](#{0}{1}{3})*" , |
64 | Vals: CDCtx.RepositoryLinePrefix.value_or(u: "" ), Vals: L.StartLineNumber, |
65 | Vals: L.Filename, Vals: *CDCtx.RepositoryUrl); |
66 | } |
67 | OS << "\n\n" ; |
68 | } |
69 | |
70 | static void maybeWriteSourceFileRef(llvm::raw_ostream &OS, |
71 | const ClangDocContext &CDCtx, |
72 | const std::optional<Location> &DefLoc) { |
73 | if (DefLoc) |
74 | writeSourceFileRef(CDCtx, L: *DefLoc, OS); |
75 | } |
76 | |
77 | static void (const CommentInfo &I, raw_ostream &OS) { |
78 | switch (I.Kind) { |
79 | case CommentKind::CK_FullComment: |
80 | for (const auto &Child : I.Children) |
81 | writeDescription(I: *Child, OS); |
82 | break; |
83 | |
84 | case CommentKind::CK_ParagraphComment: |
85 | for (const auto &Child : I.Children) |
86 | writeDescription(I: *Child, OS); |
87 | writeNewLine(OS); |
88 | break; |
89 | |
90 | case CommentKind::CK_BlockCommandComment: |
91 | OS << genEmphasis(Text: I.Name); |
92 | for (const auto &Child : I.Children) |
93 | writeDescription(I: *Child, OS); |
94 | break; |
95 | |
96 | case CommentKind::CK_InlineCommandComment: |
97 | OS << genEmphasis(Text: I.Name) << " " << I.Text; |
98 | break; |
99 | |
100 | case CommentKind::CK_ParamCommandComment: |
101 | case CommentKind::CK_TParamCommandComment: { |
102 | std::string Direction = I.Explicit ? (" " + I.Direction).str() : "" ; |
103 | OS << genEmphasis(Text: I.ParamName) << I.Text << Direction; |
104 | for (const auto &Child : I.Children) |
105 | writeDescription(I: *Child, OS); |
106 | break; |
107 | } |
108 | |
109 | case CommentKind::CK_VerbatimBlockComment: |
110 | for (const auto &Child : I.Children) |
111 | writeDescription(I: *Child, OS); |
112 | break; |
113 | |
114 | case CommentKind::CK_VerbatimBlockLineComment: |
115 | case CommentKind::CK_VerbatimLineComment: |
116 | OS << I.Text; |
117 | writeNewLine(OS); |
118 | break; |
119 | |
120 | case CommentKind::CK_HTMLStartTagComment: { |
121 | if (I.AttrKeys.size() != I.AttrValues.size()) |
122 | return; |
123 | std::string Buffer; |
124 | llvm::raw_string_ostream Attrs(Buffer); |
125 | for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx) |
126 | Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\"" ; |
127 | |
128 | std::string CloseTag = I.SelfClosing ? "/>" : ">" ; |
129 | writeLine(Text: "<" + I.Name + Attrs.str() + CloseTag, OS); |
130 | break; |
131 | } |
132 | |
133 | case CommentKind::CK_HTMLEndTagComment: |
134 | writeLine(Text: "</" + I.Name + ">" , OS); |
135 | break; |
136 | |
137 | case CommentKind::CK_TextComment: |
138 | OS << I.Text; |
139 | break; |
140 | |
141 | case CommentKind::CK_Unknown: |
142 | OS << "Unknown comment kind: " << static_cast<int>(I.Kind) << ".\n\n" ; |
143 | break; |
144 | } |
145 | } |
146 | |
147 | static void writeNameLink(const StringRef &CurrentPath, const Reference &R, |
148 | llvm::raw_ostream &OS) { |
149 | llvm::SmallString<64> Path = R.getRelativeFilePath(CurrentPath); |
150 | // Paths in Markdown use POSIX separators. |
151 | llvm::sys::path::native(path&: Path, style: llvm::sys::path::Style::posix); |
152 | llvm::sys::path::append(path&: Path, style: llvm::sys::path::Style::posix, |
153 | a: R.getFileBaseName() + ".md" ); |
154 | OS << "[" << R.Name << "](" << Path << ")" ; |
155 | } |
156 | |
157 | static void genMarkdown(const ClangDocContext &CDCtx, const EnumInfo &I, |
158 | llvm::raw_ostream &OS) { |
159 | if (I.Scoped) |
160 | writeLine(Text: "| enum class " + I.Name + " |" , OS); |
161 | else |
162 | writeLine(Text: "| enum " + I.Name + " |" , OS); |
163 | writeLine(Text: "--" , OS); |
164 | |
165 | std::string Buffer; |
166 | llvm::raw_string_ostream Members(Buffer); |
167 | if (!I.Members.empty()) |
168 | for (const auto &N : I.Members) |
169 | Members << "| " << N.Name << " |\n" ; |
170 | writeLine(Text: Members.str(), OS); |
171 | |
172 | maybeWriteSourceFileRef(OS, CDCtx, DefLoc: I.DefLoc); |
173 | |
174 | for (const auto &C : I.Description) |
175 | writeDescription(I: C, OS); |
176 | } |
177 | |
178 | static void genMarkdown(const ClangDocContext &CDCtx, const FunctionInfo &I, |
179 | llvm::raw_ostream &OS) { |
180 | std::string Buffer; |
181 | llvm::raw_string_ostream Stream(Buffer); |
182 | bool First = true; |
183 | for (const auto &N : I.Params) { |
184 | if (!First) |
185 | Stream << ", " ; |
186 | Stream << N.Type.QualName + " " + N.Name; |
187 | First = false; |
188 | } |
189 | writeHeader(Text: I.Name, Num: 3, OS); |
190 | StringRef Access = getAccessSpelling(AS: I.Access); |
191 | writeLine(Text: genItalic(Text: Twine(Access) + (!Access.empty() ? " " : "" ) + |
192 | (I.IsStatic ? "static " : "" ) + |
193 | I.ReturnType.Type.QualName.str() + " " + I.Name.str() + |
194 | "(" + Twine(Stream.str()) + ")" ), |
195 | OS); |
196 | |
197 | maybeWriteSourceFileRef(OS, CDCtx, DefLoc: I.DefLoc); |
198 | |
199 | for (const auto &C : I.Description) |
200 | writeDescription(I: C, OS); |
201 | } |
202 | |
203 | static void genMarkdown(const ClangDocContext &CDCtx, const NamespaceInfo &I, |
204 | llvm::raw_ostream &OS) { |
205 | if (I.Name == "" ) |
206 | writeHeader(Text: "Global Namespace" , Num: 1, OS); |
207 | else |
208 | writeHeader(Text: "namespace " + I.Name, Num: 1, OS); |
209 | writeNewLine(OS); |
210 | |
211 | if (!I.Description.empty()) { |
212 | for (const auto &C : I.Description) |
213 | writeDescription(I: C, OS); |
214 | writeNewLine(OS); |
215 | } |
216 | |
217 | llvm::SmallString<64> BasePath = I.getRelativeFilePath(CurrentPath: "" ); |
218 | |
219 | if (!I.Children.Namespaces.empty()) { |
220 | writeHeader(Text: "Namespaces" , Num: 2, OS); |
221 | for (const auto &R : I.Children.Namespaces) { |
222 | OS << "* " ; |
223 | writeNameLink(CurrentPath: BasePath, R, OS); |
224 | OS << "\n" ; |
225 | } |
226 | writeNewLine(OS); |
227 | } |
228 | |
229 | if (!I.Children.Records.empty()) { |
230 | writeHeader(Text: "Records" , Num: 2, OS); |
231 | for (const auto &R : I.Children.Records) { |
232 | OS << "* " ; |
233 | writeNameLink(CurrentPath: BasePath, R, OS); |
234 | OS << "\n" ; |
235 | } |
236 | writeNewLine(OS); |
237 | } |
238 | |
239 | if (!I.Children.Functions.empty()) { |
240 | writeHeader(Text: "Functions" , Num: 2, OS); |
241 | for (const auto &F : I.Children.Functions) |
242 | genMarkdown(CDCtx, I: F, OS); |
243 | writeNewLine(OS); |
244 | } |
245 | if (!I.Children.Enums.empty()) { |
246 | writeHeader(Text: "Enums" , Num: 2, OS); |
247 | for (const auto &E : I.Children.Enums) |
248 | genMarkdown(CDCtx, I: E, OS); |
249 | writeNewLine(OS); |
250 | } |
251 | } |
252 | |
253 | static void genMarkdown(const ClangDocContext &CDCtx, const RecordInfo &I, |
254 | llvm::raw_ostream &OS) { |
255 | writeHeader(Text: getTagType(AS: I.TagType) + " " + I.Name, Num: 1, OS); |
256 | |
257 | maybeWriteSourceFileRef(OS, CDCtx, DefLoc: I.DefLoc); |
258 | |
259 | if (!I.Description.empty()) { |
260 | for (const auto &C : I.Description) |
261 | writeDescription(I: C, OS); |
262 | writeNewLine(OS); |
263 | } |
264 | |
265 | std::string Parents = genReferenceList(Refs: I.Parents); |
266 | std::string VParents = genReferenceList(Refs: I.VirtualParents); |
267 | if (!Parents.empty() || !VParents.empty()) { |
268 | if (Parents.empty()) |
269 | writeLine(Text: "Inherits from " + VParents, OS); |
270 | else if (VParents.empty()) |
271 | writeLine(Text: "Inherits from " + Parents, OS); |
272 | else |
273 | writeLine(Text: "Inherits from " + Parents + ", " + VParents, OS); |
274 | writeNewLine(OS); |
275 | } |
276 | |
277 | if (!I.Members.empty()) { |
278 | writeHeader(Text: "Members" , Num: 2, OS); |
279 | for (const auto &Member : I.Members) { |
280 | StringRef Access = getAccessSpelling(AS: Member.Access); |
281 | writeLine(Text: Twine(Access) + (Access.empty() ? "" : " " ) + |
282 | (Member.IsStatic ? "static " : "" ) + |
283 | Member.Type.Name.str() + " " + Member.Name.str(), |
284 | OS); |
285 | } |
286 | writeNewLine(OS); |
287 | } |
288 | |
289 | if (!I.Children.Records.empty()) { |
290 | writeHeader(Text: "Records" , Num: 2, OS); |
291 | for (const auto &R : I.Children.Records) |
292 | writeLine(Text: R.Name, OS); |
293 | writeNewLine(OS); |
294 | } |
295 | if (!I.Children.Functions.empty()) { |
296 | writeHeader(Text: "Functions" , Num: 2, OS); |
297 | for (const auto &F : I.Children.Functions) |
298 | genMarkdown(CDCtx, I: F, OS); |
299 | writeNewLine(OS); |
300 | } |
301 | if (!I.Children.Enums.empty()) { |
302 | writeHeader(Text: "Enums" , Num: 2, OS); |
303 | for (const auto &E : I.Children.Enums) |
304 | genMarkdown(CDCtx, I: E, OS); |
305 | writeNewLine(OS); |
306 | } |
307 | } |
308 | |
309 | static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I, |
310 | llvm::raw_ostream &OS) { |
311 | // TODO support typedefs in markdown. |
312 | } |
313 | |
314 | static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) { |
315 | // Write out the heading level starting at ## |
316 | OS << "##" << std::string(Level, '#') << " " ; |
317 | writeNameLink(CurrentPath: "" , R: I, OS); |
318 | OS << "\n" ; |
319 | } |
320 | |
321 | static llvm::Error serializeIndex(ClangDocContext &CDCtx) { |
322 | std::error_code FileErr; |
323 | llvm::SmallString<128> FilePath; |
324 | llvm::sys::path::native(path: CDCtx.OutDirectory, result&: FilePath); |
325 | llvm::sys::path::append(path&: FilePath, a: "all_files.md" ); |
326 | llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text); |
327 | if (FileErr) |
328 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
329 | S: "error creating index file: " + |
330 | FileErr.message()); |
331 | |
332 | CDCtx.Idx.sort(); |
333 | OS << "# All Files" ; |
334 | if (!CDCtx.ProjectName.empty()) |
335 | OS << " for " << CDCtx.ProjectName; |
336 | OS << "\n\n" ; |
337 | |
338 | for (auto C : CDCtx.Idx.Children) |
339 | serializeReference(OS, I&: C, Level: 0); |
340 | |
341 | return llvm::Error::success(); |
342 | } |
343 | |
344 | static llvm::Error genIndex(ClangDocContext &CDCtx) { |
345 | std::error_code FileErr; |
346 | llvm::SmallString<128> FilePath; |
347 | llvm::sys::path::native(path: CDCtx.OutDirectory, result&: FilePath); |
348 | llvm::sys::path::append(path&: FilePath, a: "index.md" ); |
349 | llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_Text); |
350 | if (FileErr) |
351 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
352 | S: "error creating index file: " + |
353 | FileErr.message()); |
354 | CDCtx.Idx.sort(); |
355 | OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n" ; |
356 | for (auto C : CDCtx.Idx.Children) { |
357 | if (!C.Children.empty()) { |
358 | const char *Type; |
359 | switch (C.RefType) { |
360 | case InfoType::IT_namespace: |
361 | Type = "Namespace" ; |
362 | break; |
363 | case InfoType::IT_record: |
364 | Type = "Type" ; |
365 | break; |
366 | case InfoType::IT_enum: |
367 | Type = "Enum" ; |
368 | break; |
369 | case InfoType::IT_function: |
370 | Type = "Function" ; |
371 | break; |
372 | case InfoType::IT_typedef: |
373 | Type = "Typedef" ; |
374 | break; |
375 | case InfoType::IT_default: |
376 | Type = "Other" ; |
377 | } |
378 | OS << "* " << Type << ": [" << C.Name << "](" ; |
379 | if (!C.Path.empty()) |
380 | OS << C.Path << "/" ; |
381 | OS << C.Name << ")\n" ; |
382 | } |
383 | } |
384 | return llvm::Error::success(); |
385 | } |
386 | |
387 | /// Generator for Markdown documentation. |
388 | class MDGenerator : public Generator { |
389 | public: |
390 | static const char *Format; |
391 | |
392 | llvm::Error generateDocs(StringRef RootDir, |
393 | llvm::StringMap<std::unique_ptr<doc::Info>> Infos, |
394 | const ClangDocContext &CDCtx) override; |
395 | llvm::Error createResources(ClangDocContext &CDCtx) override; |
396 | llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, |
397 | const ClangDocContext &CDCtx) override; |
398 | }; |
399 | |
400 | const char *MDGenerator::Format = "md" ; |
401 | |
402 | llvm::Error |
403 | MDGenerator::generateDocs(StringRef RootDir, |
404 | llvm::StringMap<std::unique_ptr<doc::Info>> Infos, |
405 | const ClangDocContext &CDCtx) { |
406 | // Track which directories we already tried to create. |
407 | llvm::StringSet<> CreatedDirs; |
408 | |
409 | // Collect all output by file name and create the necessary directories. |
410 | llvm::StringMap<std::vector<doc::Info *>> FileToInfos; |
411 | for (const auto &Group : Infos) { |
412 | doc::Info *Info = Group.getValue().get(); |
413 | |
414 | llvm::SmallString<128> Path; |
415 | llvm::sys::path::native(path: RootDir, result&: Path); |
416 | llvm::sys::path::append(path&: Path, a: Info->getRelativeFilePath(CurrentPath: "" )); |
417 | if (!CreatedDirs.contains(key: Path)) { |
418 | if (std::error_code Err = llvm::sys::fs::create_directories(path: Path); |
419 | Err != std::error_code()) { |
420 | return llvm::createStringError(EC: Err, Fmt: "Failed to create directory '%s'." , |
421 | Vals: Path.c_str()); |
422 | } |
423 | CreatedDirs.insert(key: Path); |
424 | } |
425 | |
426 | llvm::sys::path::append(path&: Path, a: Info->getFileBaseName() + ".md" ); |
427 | FileToInfos[Path].push_back(x: Info); |
428 | } |
429 | |
430 | for (const auto &Group : FileToInfos) { |
431 | std::error_code FileErr; |
432 | llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr, |
433 | llvm::sys::fs::OF_Text); |
434 | if (FileErr) { |
435 | return llvm::createStringError(EC: FileErr, Fmt: "Error opening file '%s'" , |
436 | Vals: Group.getKey().str().c_str()); |
437 | } |
438 | |
439 | for (const auto &Info : Group.getValue()) { |
440 | if (llvm::Error Err = generateDocForInfo(I: Info, OS&: InfoOS, CDCtx)) { |
441 | return Err; |
442 | } |
443 | } |
444 | } |
445 | |
446 | return llvm::Error::success(); |
447 | } |
448 | |
449 | llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, |
450 | const ClangDocContext &CDCtx) { |
451 | switch (I->IT) { |
452 | case InfoType::IT_namespace: |
453 | genMarkdown(CDCtx, I: *static_cast<clang::doc::NamespaceInfo *>(I), OS); |
454 | break; |
455 | case InfoType::IT_record: |
456 | genMarkdown(CDCtx, I: *static_cast<clang::doc::RecordInfo *>(I), OS); |
457 | break; |
458 | case InfoType::IT_enum: |
459 | genMarkdown(CDCtx, I: *static_cast<clang::doc::EnumInfo *>(I), OS); |
460 | break; |
461 | case InfoType::IT_function: |
462 | genMarkdown(CDCtx, I: *static_cast<clang::doc::FunctionInfo *>(I), OS); |
463 | break; |
464 | case InfoType::IT_typedef: |
465 | genMarkdown(CDCtx, I: *static_cast<clang::doc::TypedefInfo *>(I), OS); |
466 | break; |
467 | case InfoType::IT_default: |
468 | return createStringError(EC: llvm::inconvertibleErrorCode(), |
469 | S: "unexpected InfoType" ); |
470 | } |
471 | return llvm::Error::success(); |
472 | } |
473 | |
474 | llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) { |
475 | // Write an all_files.md |
476 | auto Err = serializeIndex(CDCtx); |
477 | if (Err) |
478 | return Err; |
479 | |
480 | // Generate the index page. |
481 | Err = genIndex(CDCtx); |
482 | if (Err) |
483 | return Err; |
484 | |
485 | return llvm::Error::success(); |
486 | } |
487 | |
488 | static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format, |
489 | "Generator for MD output." ); |
490 | |
491 | // This anchor is used to force the linker to link in the generated object |
492 | // file and thus register the generator. |
493 | volatile int MDGeneratorAnchorSource = 0; |
494 | |
495 | } // namespace doc |
496 | } // namespace clang |
497 | |