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 | |
23 | static std::string genItalic(const Twine &Text) { |
24 | return "*" + Text.str() + "*" ; |
25 | } |
26 | |
27 | static std::string genEmphasis(const Twine &Text) { |
28 | return "**" + Text.str() + "**" ; |
29 | } |
30 | |
31 | static std::string |
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 | |
43 | static void writeLine(const Twine &Text, raw_ostream &OS) { |
44 | OS << Text << "\n\n" ; |
45 | } |
46 | |
47 | static void writeNewLine(raw_ostream &OS) { OS << "\n\n" ; } |
48 | |
49 | static void (const Twine &Text, unsigned int Num, raw_ostream &OS) { |
50 | OS << std::string(Num, '#') + " " + Text << "\n\n" ; |
51 | } |
52 | |
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(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 | |
69 | static void (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 | |
121 | static 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 | |
131 | static 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 | |
152 | static 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 | |
180 | static 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 | |
230 | static 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 | |
286 | static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I, |
287 | llvm::raw_ostream &OS) { |
288 | // TODO support typedefs in markdown. |
289 | } |
290 | |
291 | static 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 | |
298 | static 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 | |
321 | static 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. |
365 | class MDGenerator : public Generator { |
366 | public: |
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 | |
377 | const char *MDGenerator::Format = "md" ; |
378 | |
379 | llvm::Error |
380 | MDGenerator::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 | |
426 | llvm::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 | |
451 | llvm::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 | |
465 | static 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. |
470 | volatile int MDGeneratorAnchorSource = 0; |
471 | |
472 | } // namespace doc |
473 | } // namespace clang |
474 | |