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
18using namespace llvm;
19
20namespace clang {
21namespace doc {
22
23// Markdown generation
24
25static std::string genItalic(const Twine &Text) {
26 return "*" + Text.str() + "*";
27}
28
29static std::string genEmphasis(const Twine &Text) {
30 return "**" + Text.str() + "**";
31}
32
33static std::string
34genReferenceList(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
45static void writeLine(const Twine &Text, raw_ostream &OS) {
46 OS << Text << "\n\n";
47}
48
49static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
50
51static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
52 OS << std::string(Num, '#') + " " + Text << "\n\n";
53}
54
55static 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
70static 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
77static void writeDescription(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
147static 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
157static 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
178static 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
203static 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
253static 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
309static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
310 llvm::raw_ostream &OS) {
311 // TODO support typedefs in markdown.
312}
313
314static 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
321static 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
344static 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.
388class MDGenerator : public Generator {
389public:
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
400const char *MDGenerator::Format = "md";
401
402llvm::Error
403MDGenerator::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
449llvm::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
474llvm::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
488static 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.
493volatile int MDGeneratorAnchorSource = 0;
494
495} // namespace doc
496} // namespace clang
497

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of clang-tools-extra/clang-doc/MDGenerator.cpp