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(L.LineNumber)
58 << "*";
59 } else {
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) << ")"
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(*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
117static 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
127static 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.Name << " |\n";
140 writeLine(Members.str(), OS);
141 if (I.DefLoc)
142 writeFileDefinition(CDCtx, *I.DefLoc, OS);
143
144 for (const auto &C : I.Description)
145 writeDescription(C, OS);
146}
147
148static 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, OS);
171
172 for (const auto &C : I.Description)
173 writeDescription(C, OS);
174}
175
176static 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.Children.Namespaces.empty()) {
193 writeHeader("Namespaces", 2, OS);
194 for (const auto &R : I.Children.Namespaces) {
195 OS << "* ";
196 writeNameLink(BasePath, R, OS);
197 OS << "\n";
198 }
199 writeNewLine(OS);
200 }
201
202 if (!I.Children.Records.empty()) {
203 writeHeader("Records", 2, OS);
204 for (const auto &R : I.Children.Records) {
205 OS << "* ";
206 writeNameLink(BasePath, R, OS);
207 OS << "\n";
208 }
209 writeNewLine(OS);
210 }
211
212 if (!I.Children.Functions.empty()) {
213 writeHeader("Functions", 2, OS);
214 for (const auto &F : I.Children.Functions)
215 genMarkdown(CDCtx, F, OS);
216 writeNewLine(OS);
217 }
218 if (!I.Children.Enums.empty()) {
219 writeHeader("Enums", 2, OS);
220 for (const auto &E : I.Children.Enums)
221 genMarkdown(CDCtx, E, OS);
222 writeNewLine(OS);
223 }
224}
225
226static 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, 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.Children.Records.empty()) {
263 writeHeader("Records", 2, OS);
264 for (const auto &R : I.Children.Records)
265 writeLine(R.Name, OS);
266 writeNewLine(OS);
267 }
268 if (!I.Children.Functions.empty()) {
269 writeHeader("Functions", 2, OS);
270 for (const auto &F : I.Children.Functions)
271 genMarkdown(CDCtx, F, OS);
272 writeNewLine(OS);
273 }
274 if (!I.Children.Enums.empty()) {
275 writeHeader("Enums", 2, OS);
276 for (const auto &E : I.Children.Enums)
277 genMarkdown(CDCtx, E, OS);
278 writeNewLine(OS);
279 }
280}
281
282static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I,
283 llvm::raw_ostream &OS) {
284 // TODO support typedefs in markdown.
285}
286
287static void serializeReference(llvm::raw_fd_ostream &OS, Index &I, int Level) {
288 // Write out the heading level starting at ##
289 OS << "##" << std::string(Level, '#') << " ";
290 writeNameLink("", I, OS);
291 OS << "\n";
292}
293
294static llvm::Error serializeIndex(ClangDocContext &CDCtx) {
295 std::error_code FileErr;
296 llvm::SmallString<128> FilePath;
297 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
298 llvm::sys::path::append(FilePath, "all_files.md");
299 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
300 if (FileErr)
301 return llvm::createStringError(llvm::inconvertibleErrorCode(),
302 "error creating index file: " +
303 FileErr.message());
304
305 CDCtx.Idx.sort();
306 OS << "# All Files";
307 if (!CDCtx.ProjectName.empty())
308 OS << " for " << CDCtx.ProjectName;
309 OS << "\n\n";
310
311 for (auto C : CDCtx.Idx.Children)
312 serializeReference(OS, C, 0);
313
314 return llvm::Error::success();
315}
316
317static llvm::Error genIndex(ClangDocContext &CDCtx) {
318 std::error_code FileErr;
319 llvm::SmallString<128> FilePath;
320 llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
321 llvm::sys::path::append(FilePath, "index.md");
322 llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
323 if (FileErr)
324 return llvm::createStringError(llvm::inconvertibleErrorCode(),
325 "error creating index file: " +
326 FileErr.message());
327 CDCtx.Idx.sort();
328 OS << "# " << CDCtx.ProjectName << " C/C++ Reference\n\n";
329 for (auto C : CDCtx.Idx.Children) {
330 if (!C.Children.empty()) {
331 const char *Type;
332 switch (C.RefType) {
333 case InfoType::IT_namespace:
334 Type = "Namespace";
335 break;
336 case InfoType::IT_record:
337 Type = "Type";
338 break;
339 case InfoType::IT_enum:
340 Type = "Enum";
341 break;
342 case InfoType::IT_function:
343 Type = "Function";
344 break;
345 case InfoType::IT_typedef:
346 Type = "Typedef";
347 break;
348 case InfoType::IT_default:
349 Type = "Other";
350 }
351 OS << "* " << Type << ": [" << C.Name << "](";
352 if (!C.Path.empty())
353 OS << C.Path << "/";
354 OS << C.Name << ")\n";
355 }
356 }
357 return llvm::Error::success();
358}
359
360/// Generator for Markdown documentation.
361class MDGenerator : public Generator {
362public:
363 static const char *Format;
364
365 llvm::Error generateDocs(StringRef RootDir,
366 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
367 const ClangDocContext &CDCtx) override;
368 llvm::Error createResources(ClangDocContext &CDCtx) override;
369 llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
370 const ClangDocContext &CDCtx) override;
371};
372
373const char *MDGenerator::Format = "md";
374
375llvm::Error
376MDGenerator::generateDocs(StringRef RootDir,
377 llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
378 const ClangDocContext &CDCtx) {
379 // Track which directories we already tried to create.
380 llvm::StringSet<> CreatedDirs;
381
382 // Collect all output by file name and create the necessary directories.
383 llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
384 for (const auto &Group : Infos) {
385 doc::Info *Info = Group.getValue().get();
386
387 llvm::SmallString<128> Path;
388 llvm::sys::path::native(RootDir, Path);
389 llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
390 if (!CreatedDirs.contains(Path)) {
391 if (std::error_code Err = llvm::sys::fs::create_directories(Path);
392 Err != std::error_code()) {
393 return llvm::createStringError(Err, "Failed to create directory '%s'.",
394 Path.c_str());
395 }
396 CreatedDirs.insert(Path);
397 }
398
399 llvm::sys::path::append(Path, Info->getFileBaseName() + ".md");
400 FileToInfos[Path].push_back(Info);
401 }
402
403 for (const auto &Group : FileToInfos) {
404 std::error_code FileErr;
405 llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
406 llvm::sys::fs::OF_None);
407 if (FileErr) {
408 return llvm::createStringError(FileErr, "Error opening file '%s'",
409 Group.getKey().str().c_str());
410 }
411
412 for (const auto &Info : Group.getValue()) {
413 if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
414 return Err;
415 }
416 }
417 }
418
419 return llvm::Error::success();
420}
421
422llvm::Error MDGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
423 const ClangDocContext &CDCtx) {
424 switch (I->IT) {
425 case InfoType::IT_namespace:
426 genMarkdown(CDCtx, *static_cast<clang::doc::NamespaceInfo *>(I), OS);
427 break;
428 case InfoType::IT_record:
429 genMarkdown(CDCtx, *static_cast<clang::doc::RecordInfo *>(I), OS);
430 break;
431 case InfoType::IT_enum:
432 genMarkdown(CDCtx, *static_cast<clang::doc::EnumInfo *>(I), OS);
433 break;
434 case InfoType::IT_function:
435 genMarkdown(CDCtx, *static_cast<clang::doc::FunctionInfo *>(I), OS);
436 break;
437 case InfoType::IT_typedef:
438 genMarkdown(CDCtx, *static_cast<clang::doc::TypedefInfo *>(I), OS);
439 break;
440 case InfoType::IT_default:
441 return createStringError(llvm::inconvertibleErrorCode(),
442 "unexpected InfoType");
443 }
444 return llvm::Error::success();
445}
446
447llvm::Error MDGenerator::createResources(ClangDocContext &CDCtx) {
448 // Write an all_files.md
449 auto Err = serializeIndex(CDCtx);
450 if (Err)
451 return Err;
452
453 // Generate the index page.
454 Err = genIndex(CDCtx);
455 if (Err)
456 return Err;
457
458 return llvm::Error::success();
459}
460
461static GeneratorRegistry::Add<MDGenerator> MD(MDGenerator::Format,
462 "Generator for MD output.");
463
464// This anchor is used to force the linker to link in the generated object
465// file and thus register the generator.
466volatile int MDGeneratorAnchorSource = 0;
467
468} // namespace doc
469} // namespace clang
470