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(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 | |
69 | static void (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 | |
117 | static 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 | |
127 | static 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 | |
148 | static 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 | |
176 | static 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 | |
226 | static 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 | |
282 | static void genMarkdown(const ClangDocContext &CDCtx, const TypedefInfo &I, |
283 | llvm::raw_ostream &OS) { |
284 | // TODO support typedefs in markdown. |
285 | } |
286 | |
287 | static 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 | |
294 | static 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 | |
317 | static 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. |
361 | class MDGenerator : public Generator { |
362 | public: |
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 | |
373 | const char *MDGenerator::Format = "md" ; |
374 | |
375 | llvm::Error |
376 | MDGenerator::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 | |
422 | llvm::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 | |
447 | llvm::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 | |
461 | static 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. |
466 | volatile int MDGeneratorAnchorSource = 0; |
467 | |
468 | } // namespace doc |
469 | } // namespace clang |
470 | |