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

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